Windows网络通信(二):socket异步编程
简述
这里使用的API和同步编程的API是差不多的,只多了一个ioctlsocket和select函数。这里面涉及一个很重要的结构体fd_set。这里用到的API大部分都是windows和linux通用的。
1. ioctlsocket控制socket的IO模型
int ioctlsocket( _In_ SOCKET s, _In_ long cmd, _Inout_ u_long *argp );
s:需要设置的socket
cmd:想要对socket执行的命令,异步编程需要FIONBIO命令
argp:执行命令的参数,FIONBIO的参数如果为0表示阻塞模式,非0表示非阻塞模式
2. select获取一个或多个套接字的状态(可读可写或其他状态)
int select( _In_ int nfds, _Inout_ fd_set *readfds, _Inout_ fd_set *writefds, _Inout_ fd_set *exceptfds, _In_ const struct timeval *timeout );
nfds:无用
readfds:一个fd_set,表示哪些套接字需要判断是否”可读”状态,其中”可读”状态可以是accept,recv或者套接字已经关闭,重置,中断。
writefds:一个fd_set,表示哪些套接字需要判断是否”可写”状态,其中”可写”状态可以是connect成功,send。一般来说send是会立刻返回的,但是当send缓存区被装满了,数据无法放入时就会导致send函数阻塞,异步模式下可以用select判断缓存区是否有空间。
exceptfds:一个fd_set,表示哪些套接字需要判断是否”异常”状态,其中”异常”状态一般是connect失败。
timeout:等待的超时时间,select会等待timeout毫秒,NULL表示无限等待
成功返回所有fd_set响应的套接字数目,错误发生返回SOCKET_ERROR,超时返回0.
3. fd_set就是一个简单的结构体,内部有一个socket数组和一个数组成员个数。
typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
可以使用如下宏操作fd_set
FD_CLR(fd, set):从fd_set中删除指定的socket
FD_SET(fd, set):从fd_set中添加指定的socket
FD_ZERO(set):清空fd_set
FD_ISSET(fd, set):判断指定socket是否在fd_set中
异步通信示例
下面是异步通信服务端的代码,用于回发客户端发过来的消息,单线程中处理多个客户端的通信。
#include <winsock2.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #pragma comment(lib,"ws2_32.lib") #define IP "127.0.0.1" #define DEFAULT_PORT 12345 #define Print_ErrCode(e) fprintf(stderr,"\n[Server]%s 执行失败: %d\n",e,WSAGetLastError()) #define DEFAULT_BACKLOG 5 #define MAX_IO_PEND 10 int curr_size = 0; //当前的句柄数 #define OP_READ 0x10 #define OP_WRITE 0x20 //定义结构体用于储存通信信息 typedef struct _socklist { SOCKET sock; DWORD Op; char name[100]; char Buffer[128]; int bufLen; } Socklist; int main(int argc, char **argv) { int nStartup = 0; struct sockaddr_in clientService; WSADATA wsaData; SOCKET sockListen = INVALID_SOCKET; int nRet = 0; //保存所有的客户端、服务端的SOCKET信息 if (0 != (nStartup = WSAStartup(MAKEWORD(2, 2), &wsaData))) { WSASetLastError(nStartup); //WSAStartup不会自动设置错误代码 Print_ErrCode("WSAStartup()"); return 1; } clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr(IP); clientService.sin_port = htons(DEFAULT_PORT); if (INVALID_SOCKET == (sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) ) { Print_ErrCode("socket()"); return 1; } u_long type = 1; ioctlsocket(sockListen, FIONBIO, &type); if (SOCKET_ERROR == bind(sockListen, (SOCKADDR *)&clientService, sizeof(clientService) )) { Print_ErrCode("bind()"); closesocket(sockListen); return 1; } if (SOCKET_ERROR == listen(sockListen, DEFAULT_BACKLOG)) { Print_ErrCode("listen()"); closesocket(sockListen); } printf("[Server]监听 %s:%d\n", IP, DEFAULT_PORT); //存放所有的socket,包括用于accept的socket。 Socklist sockList[10]; //将监听socket设为socklist第一个元素 sockList[0].sock = sockListen; curr_size = 1; // 一个大循环,不断的接收客户端请求 while(true) { //循环判断是否有请求需要处理 fd_set fdRead, fdWrite; while (true) { FD_ZERO(&fdRead); FD_ZERO(&fdWrite); FD_SET(sockList[0].sock, &fdRead); for (int i = 1; i < curr_size; i++) { //对需要send的客户端连接select if (sockList[i].Op == OP_WRITE) { FD_SET(sockList[i].sock, &fdWrite); } //对所有的客户端连接select FD_SET(sockList[i].sock, &fdRead); } //这个操作会被阻塞 nRet = select(0, &fdRead, &fdWrite, NULL, NULL); if (FD_ISSET(sockList[0].sock, &fdRead)) { //第0个socket可用了,这时accept一定会立刻返回成功或失败 这里需要处理最大连接数 SOCKET sockNewClient = accept(sockListen,NULL,NULL); sockList[curr_size].sock = sockNewClient; sockList[curr_size++].Op = OP_READ; break; } //其他socket可用了,判断哪些能读,哪些能写 if (fdRead.fd_count > 0) { for (int i = 1; i < curr_size; i++) { if (FD_ISSET(sockList[i].sock, &fdRead)) { //开始recv nRet = recv(sockList[i].sock, sockList[i].Buffer, 127, 0); if (nRet == SOCKET_ERROR) { closesocket(sockList[i].sock); //移除sockList for (int j = i; j < curr_size-1; j++) { sockList[i].sock = sockList[i + 1].sock; } curr_size--; } else { sockList[i].Buffer[nRet] = '\0'; sockList[i].bufLen = nRet; sockList[i].Op = OP_WRITE; printf("[Server]接收到:%s\n", sockList[i].Buffer); } } } } if (fdWrite.fd_count > 0) { for (int i = 1; i < curr_size; i++) { if (FD_ISSET(sockList[i].sock, &fdWrite)) { if (sockList[i].Op == OP_WRITE) { //开始send nRet = send(sockList[i].sock, sockList[i].Buffer, sockList[i].bufLen, 0); //事实上,这里可能会有nRet小于bufLen的情况 if (nRet == SOCKET_ERROR) { closesocket(sockList[i].sock); //移除sockList for (int j = i; j < curr_size - 1; j++) { sockList[i].sock = sockList[i + 1].sock; } curr_size--; } else { sockList[i].Op = OP_READ; printf("[Server]已发送:%d\n", nRet); } } } } } } } }