本章将详解send()/WSASend() 、 recv()/WSARecv() 和 函数,然后你就可以编写一个可运行的通讯程序了
程序包括一个服务端和一个客户端,服务端向客户端发送一个Hello World!
对,你没看错,所有程序的开端,Hello World!
程序运行结果
服务端
客户端
send()/WSASend():int send(Socket s, // 即将发送数据的服务端进程const char FAR * buf; // 待发送数据指针int len; // 待发送数据长度int flags // 标志位);
flags 标志位可选:0 | MSG_DONTROUTE | MSG_OOB
可以用 OR 运算符连接
通常用0,后面两个不常用。
函数运行正确返回发送的字节数,错误时返回SOCKET_ERROR,
常见错误码:
- WSAECONNABORTED: 超时,协议错误等
- WSAECONNRESET: 服务器关闭、重启
- WSAEWOULDBLOCK: 特定方法无法被完成,使用了非阻塞、异步socket
- WSAETIMEOUT: 超时,网络不通
当send()返回错误时该socket应该立即关闭,因为不可用了。
int WSASend(SOCKET s,LPWSABUF lpBuffers, // 待发送数据指针DWORD dwBufferCount, // 待发送数据长度LPDWORD lpNumberOfBytesSent, // 发送字节数,函数运行后将设置该值DWORD dwFlags, // 标志位,同send()LPWSAOVERLAPPED lpOverlapped, // 后面两个参数用于重叠I/O,后面会讲到,此处可以无视LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
函数运行正确返回0,函数运行错误返回值类似send()。
recv()/WSARecv()int recv(SOCKET s, // 待接收数据的客户端socketchar FAR * buf, // 准备存储数据的缓冲区int len, // 缓冲区长度int flags // 标志位);
标志位flags可取的值为:0 | MSG_PEEK | MSG_OOB
可以用OR运算符连接
通常用0,MSG_PEEK表示数据将被复制进缓冲区,但并不从输入队列中删除。
函数运行正确返回接收的字节数,函数运行错误返回SOCKET_ERROR。
常见错误码:
- WSAEMSGSIZE:数据过大,超过缓冲区。该错误只会发生在面向消息协议中,不会发生在流式消息中,因为TCP有流量控制机制
int WSARecv(SOCKET s, // 待接收数据的客户端socketLPWSABUF lpBuffers, // 准备存储数据的缓冲区dwBufferCount, // 接收缓冲区大小lpNumberOfBytesRecvd, // 接收字节数,函数运行后将设置该值LPDWORD lpFlags, // 标志位,一般用0LPWSAOVERLAPPED lpOverlapped, // 后面两个参数用于重叠I/O,后面会讲到,此处可以无视LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
另外,还有一对不常用的WSASendDisconnect()/WSARecvDisconnect(),
它们用于发送连接断开数据,运行后会关闭连接。
现在终于可以编写一个完整的 Winsock TCP/IP 通讯程序了!
终于迈出了 Hello World! 的重要一步!
服务端代码
#include <winsock2.h>#include <iostream>#define PORT 5000using namespace std;int main(void){WSADATA wsaData; // Winsock数据结构SOCKET ServerSocket; // 服务端socketSOCKET AcceptSocket; // 从客户端接收到的socketSOCKADDR_IN ServerAddr; // 服务端SOCKADDR地址SOCKADDR_IN ClientAddr; // 客户端SOCKADDR地址int port=PORT; // 端口号int ClientAddrLen; // 客户区地址长度char s[]="Hello World!"; // 要传输的字符串WSAStartup(MAKEWORD(2,2),&wsaData); // 初始化 Winsock 2.2 版本ServerSocket=socket(AF_INET,SOCK_STREAM,0); // 创建一个 socket 来监听客户连接if(ServerSocket!=INVALID_SOCKET) {cout<<"socket()创建ServerSocket成功!\n";}else {cout<<"socket()创建ServerSocket失败!\n"<<WSAGetLastError();}ServerAddr.sin_family=AF_INET; // 填充 SOCKADDR_IN 数据结构ServerAddr.sin_port=htons(port);ServerAddr.sin_addr.s_addr=htonl(INADDR_ANY);if(bind(ServerSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==SOCKET_ERROR) {cout<<"bind()绑定周知地址失败!\n"<<WSAGetLastError();}else {cout<<"bind()绑定周知地址成功!\n";}if(listen(ServerSocket,5)!=SOCKET_ERROR) {cout<<"listen()监听成功!\n";}else {cout<<"listen()监听失败!\n";}ClientAddrLen=sizeof(ClientAddr); // 显示指定ClientAddrLen大小while(1){AcceptSocket=accept(ServerSocket,(SOCKADDR*)&ClientAddr,&ClientAddrLen);// 接受一个到来的连接,注意!最后一个参数需要自己显示指定!/*这里你通过这些socket可以做两件事1.通过ListeningSocket再次调用accept()来接受其他连接2.通过NewConnection来发送/接受数据当你做完这两件事情时必须要关闭这些socketsocket的关闭将在后面介绍*/if(INVALID_SOCKET!=AcceptSocket) {cout<<"accept()接收客户端连接成功!\n";int sendLen=send(AcceptSocket,s,sizeof(s),0); // 发送数据if(sendLen==SOCKET_ERROR) {cout<<"send()发送数据失败!\n"<<WSAGetLastError();}else {cout<<"send()发送数据成功!发送的字节数:"<< sendLen;}closesocket(AcceptSocket); // 关闭该连接break; // 退出循环}closesocket(ServerSocket); // 关闭ServerSocketWSACleanup(); // 关闭Winsockint nothing; // 与程序无关,为了让控制台不直接关闭cin>>nothing;return 0;}
客户端代码
#include <Winsock2.h>#include <iostream>#define BUFFER 1024#define PORT 5000using namespace std;int main(void){WSADATA wsaData; // Winsock数据结构SOCKET ClientSocket; // 客户端socketSOCKADDR_IN ServerAddr; // 服务器地址int port=PORT; // 端口号char buf[BUFFER]; // 接收的字符缓冲区memset(buf,0,sizeof(buf)); // 清空缓存WSAStartup(MAKEWORD(2,2),&wsaData); // 初始化 Winsock 2.2 版本ClientSocket=socket(AF_INET,SOCK_STREAM,0); // 创建客户端socketif(ClientSocket==INVALID_SOCKET) {cout<<"socket()创建ClientSocket失败!\n"<<WSAGetLastError();}else {cout<<"socket()创建ClientSocket成功!\n";}ServerAddr.sin_family=AF_INET; // 填充 SOCKADDR_IN 数据结构ServerAddr.sin_port=htons(port);ServerAddr.sin_addr.s_addr=inet_addr("127.0.0.1");if(connect(ClientSocket,(SOCKADDR*)&ServerAddr,sizeof(ServerAddr))==INVALID_SOCKET) {cout<<"connect()连接服务端失败!\n"<<WSAGetLastError();}else {cout<<"connect()连接服务端成功!\n";}int recvLen=recv(ClientSocket,buf,sizeof(buf),0);if(recvLen==0) {cout<<"接收长度为0!\n";}else if(recvLen==SOCKET_ERROR) {cout<<"recv()接收失败!\n"<<WSAGetLastError();}else {cout<<"recv()接收成功!\n"<<buf<<" 接收数据字节数为:"<<recvLen;}closesocket(ClientSocket); // 关闭socketWSACleanup(); // 关闭Winsockint nothing; // 与程序无关,为了让控制台不直接关闭cin>>nothing;return 0;}
程序必须先运行服务端,再运行客户端,因为先要让服务端开启监听。
程序需要导入必要的库文件,详情参见 Windows网络编程学习笔记(1)。
Hello World!发送时需要算上最后一个/0结束符,因此大小是13个字节。
