本章将详解send()/WSASend() 、 recv()/WSARecv() 和 函数,然后你就可以编写一个可运行的通讯程序了
程序包括一个服务端和一个客户端,服务端向客户端发送一个Hello World!
对,你没看错,所有程序的开端,Hello World!

程序运行结果

服务端

image.png

客户端

  1. send()/WSASend():
  2. int send(
  3. Socket s, // 即将发送数据的服务端进程
  4. const char FAR * buf; // 待发送数据指针
  5. int len; // 待发送数据长度
  6. int flags // 标志位
  7. );

flags 标志位可选:0 | MSG_DONTROUTE | MSG_OOB
可以用 OR 运算符连接
通常用0,后面两个不常用。

函数运行正确返回发送的字节数,错误时返回SOCKET_ERROR,
常见错误码:

  • WSAECONNABORTED: 超时,协议错误等
  • WSAECONNRESET: 服务器关闭、重启
  • WSAEWOULDBLOCK: 特定方法无法被完成,使用了非阻塞、异步socket
  • WSAETIMEOUT: 超时,网络不通

当send()返回错误时该socket应该立即关闭,因为不可用了。

  1. int WSASend(
  2. SOCKET s,
  3. LPWSABUF lpBuffers, // 待发送数据指针
  4. DWORD dwBufferCount, // 待发送数据长度
  5. LPDWORD lpNumberOfBytesSent, // 发送字节数,函数运行后将设置该值
  6. DWORD dwFlags, // 标志位,同send()
  7. LPWSAOVERLAPPED lpOverlapped, // 后面两个参数用于重叠I/O,后面会讲到,此处可以无视
  8. LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
  9. );

函数运行正确返回0,函数运行错误返回值类似send()。

  1. recv()/WSARecv()
  2. int recv
  3. (
  4. SOCKET s, // 待接收数据的客户端socket
  5. char FAR * buf, // 准备存储数据的缓冲区
  6. int len, // 缓冲区长度
  7. int flags // 标志位
  8. );

标志位flags可取的值为:0 | MSG_PEEK | MSG_OOB
可以用OR运算符连接
通常用0,MSG_PEEK表示数据将被复制进缓冲区,但并不从输入队列中删除。
函数运行正确返回接收的字节数,函数运行错误返回SOCKET_ERROR。
常见错误码:

  • WSAEMSGSIZE:数据过大,超过缓冲区。该错误只会发生在面向消息协议中,不会发生在流式消息中,因为TCP有流量控制机制
  1. int WSARecv(
  2. SOCKET s, // 待接收数据的客户端socket
  3. LPWSABUF lpBuffers, // 准备存储数据的缓冲区
  4. dwBufferCount, // 接收缓冲区大小
  5. lpNumberOfBytesRecvd, // 接收字节数,函数运行后将设置该值
  6. LPDWORD lpFlags, // 标志位,一般用0
  7. LPWSAOVERLAPPED lpOverlapped, // 后面两个参数用于重叠I/O,后面会讲到,此处可以无视
  8. LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine
  9. );

另外,还有一对不常用的WSASendDisconnect()/WSARecvDisconnect(),
它们用于发送连接断开数据,运行后会关闭连接。
现在终于可以编写一个完整的 Winsock TCP/IP 通讯程序了!
终于迈出了 Hello World! 的重要一步!

服务端代码

  1. #include <winsock2.h>
  2. #include <iostream>
  3. #define PORT 5000
  4. using namespace std;
  5. int main(void)
  6. {
  7. WSADATA wsaData; // Winsock数据结构
  8. SOCKET ServerSocket; // 服务端socket
  9. SOCKET AcceptSocket; // 从客户端接收到的socket
  10. SOCKADDR_IN ServerAddr; // 服务端SOCKADDR地址
  11. SOCKADDR_IN ClientAddr; // 客户端SOCKADDR地址
  12. int port=PORT; // 端口号
  13. int ClientAddrLen; // 客户区地址长度
  14. char s[]="Hello World!"; // 要传输的字符串
  15. WSAStartup(MAKEWORD(2,2),&wsaData); // 初始化 Winsock 2.2 版本
  16. ServerSocket=socket(AF_INET,SOCK_STREAM,0); // 创建一个 socket 来监听客户连接
  17. if(ServerSocket!=INVALID_SOCKET) {
  18. cout<<"socket()创建ServerSocket成功!\n";
  19. }
  20. else {
  21. cout<<"socket()创建ServerSocket失败!\n"<<WSAGetLastError();
  22. }
  23. ServerAddr.sin_family=AF_INET; // 填充 SOCKADDR_IN 数据结构
  24. ServerAddr.sin_port=htons(port);
  25. ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);
  26. if(bind(ServerSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==SOCKET_ERROR) {
  27. cout<<"bind()绑定周知地址失败!\n"<<WSAGetLastError();
  28. }
  29. else {
  30. cout<<"bind()绑定周知地址成功!\n";
  31. }
  32. if(listen(ServerSocket,5)!=SOCKET_ERROR) {
  33. cout<<"listen()监听成功!\n";
  34. }
  35. else {
  36. cout<<"listen()监听失败!\n";
  37. }
  38. ClientAddrLen=sizeof(ClientAddr); // 显示指定ClientAddrLen大小
  39. while(1)
  40. {
  41. AcceptSocket=accept(ServerSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen);
  42. // 接受一个到来的连接,注意!最后一个参数需要自己显示指定!
  43. /*
  44. 这里你通过这些socket可以做两件事
  45. 1.通过ListeningSocket再次调用accept()来接受其他连接
  46. 2.通过NewConnection来发送/接受数据
  47. 当你做完这两件事情时必须要关闭这些socket
  48. socket的关闭将在后面介绍
  49. */
  50. if(INVALID_SOCKET!=AcceptSocket) {
  51. cout<<"accept()接收客户端连接成功!\n";
  52. int sendLen=send(AcceptSocket,s,sizeof(s),0); // 发送数据
  53. if(sendLen==SOCKET_ERROR) {
  54. cout<<"send()发送数据失败!\n"<<WSAGetLastError();
  55. }
  56. else {
  57. cout<<"send()发送数据成功!发送的字节数:"<< sendLen;
  58. }
  59. closesocket(AcceptSocket); // 关闭该连接
  60. break; // 退出循环
  61. }
  62. closesocket(ServerSocket); // 关闭ServerSocket
  63. WSACleanup(); // 关闭Winsock
  64. int nothing; // 与程序无关,为了让控制台不直接关闭
  65. cin>>nothing;
  66. return 0;
  67. }

客户端代码

  1. #include <Winsock2.h>
  2. #include <iostream>
  3. #define BUFFER 1024
  4. #define PORT 5000
  5. using namespace std;
  6. int main(void)
  7. {
  8. WSADATA wsaData; // Winsock数据结构
  9. SOCKET ClientSocket; // 客户端socket
  10. SOCKADDR_IN ServerAddr; // 服务器地址
  11. int port=PORT; // 端口号
  12. char buf[BUFFER]; // 接收的字符缓冲区
  13. memset(buf,0,sizeof(buf)); // 清空缓存
  14. WSAStartup(MAKEWORD(2,2),&wsaData); // 初始化 Winsock 2.2 版本
  15. ClientSocket=socket(AF_INET,SOCK_STREAM,0); // 创建客户端socket
  16. if(ClientSocket==INVALID_SOCKET) {
  17. cout<<"socket()创建ClientSocket失败!\n"<<WSAGetLastError();
  18. }
  19. else {
  20. cout<<"socket()创建ClientSocket成功!\n";
  21. }
  22. ServerAddr.sin_family=AF_INET; // 填充 SOCKADDR_IN 数据结构
  23. ServerAddr.sin_port=htons(port);
  24. ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");
  25. if(connect(ClientSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==INVALID_SOCKET) {
  26. cout<<"connect()连接服务端失败!\n"<<WSAGetLastError();
  27. }
  28. else {
  29. cout<<"connect()连接服务端成功!\n";
  30. }
  31. int recvLen=recv(ClientSocket,buf,sizeof(buf),0);
  32. if(recvLen==0) {
  33. cout<<"接收长度为0!\n";
  34. }
  35. else if(recvLen==SOCKET_ERROR) {
  36. cout<<"recv()接收失败!\n"<<WSAGetLastError();
  37. }
  38. else {
  39. cout<<"recv()接收成功!\n"<<buf<<" 接收数据字节数为:"<<recvLen;
  40. }
  41. closesocket(ClientSocket); // 关闭socket
  42. WSACleanup(); // 关闭Winsock
  43. int nothing; // 与程序无关,为了让控制台不直接关闭
  44. cin>>nothing;
  45. return 0;
  46. }

程序必须先运行服务端,再运行客户端,因为先要让服务端开启监听。
程序需要导入必要的库文件,详情参见 Windows网络编程学习笔记(1)。
Hello World!发送时需要算上最后一个/0结束符,因此大小是13个字节。