答:解决基本C/S模型中,accept()、recv()、send()阻塞的问题数组
其实select模型解决了 实现多个客户端连接,与多个客户端分别通讯
两个模型都存在recv(),send()执行阻塞问题
做用:定义一个用来装socket的结构体服务器
#ifndef FD_SETSIZE #define FD_SETSIZE 64 /*默认64个*/ #endif /* FD_SETSIZE */ typedef struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set;
默认装socket大小为64,能够经过在winsock2.h头文件前声明宏,给一个更大的值socket
#define FD_SETSIZE 128 #include <WinSock2.h>
由于原理就是不停遍历检测,越多效率越低,延迟越大,因此合适大小最好。
select模型应用,就是小用户量访问。函数
操做宏 | 做用 | 代码 |
---|---|---|
FD_ZERO | 将客户端socket集合清零 | FD_ZERO(&clientSockets); |
FD_SET | 添加一个socket(超过默认值大小再也不处理) | FD_SET(socketListen,&clientSockets); |
FD_CLR | 从集合中删除指定的socket,必定要close,手动释放 | FD_CLR(socketListen, &clientSockets);closesocket(socketListen); |
FD_ISSET | 查询socket是否在集合中,不存在返回0,存在返回非0 | int a = FD_ISSET(socketListen, &clientSockets); |
做用:监视socket集合,若是某个socket发生响应(连接或者收发数据),经过返回值以及参数告诉咱们哪一个socket有响应spa
int WSAAPI select( int nfds, /*填0*/ fd_set *readfds, /*检查是否有可读的socket*/ fd_set *writefds, /*检查是否有可写的socket*/ fd_set *exceptfds, /*检查socket上的异常错误*/ const timeval *timeout );
为了兼容Berkeley sockets线程
用法和参数二、3同样,将有异常错误的socket装进来,反馈给咱们3d
/*获得异常socket上的具体错误码*/ getsockopt(socket, SOL_SOCKET, SO_ERROR, buf, buflen);
若是调用这个函数(针对这个getsockopt函数)没有错误,返回0,不然返回SOCKET_ERROR,而且能够调用WSAGetLastError来获得错误代码。code
一个结构体blog
struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ };
当客户端没有响应时,select能够选择等一段时间,不等,等到有socket响应,三种方式图片
tv_sec | tv_usec | 做用 |
---|---|---|
0 | 0 | 不等待,马上返回 |
3 | 4 | 等待3秒4微秒没有消息再返回 |
NULL :死等,直到有socket响应
fd_set allsockets; //清零 FD_ZERO(&allSockets); //服务器装进去 FD_SET(socketServer, &allSockets); while (1) { fd_set readSockets = allSockets; fd_set writeSockets = allSockets; fd_set errorSockets = allSockets; //时间段 struct timeval st; st.tv_sec = 3; st.tv_usec = 0; //select int nRes = select(0, &readSockets, &writeSockets, &errorSockets, &st); if (0 == nRes) //没有响应的socket { continue; } else if (nRes > 0) { //处理错误 for (u_int i = 0; i < errorSockets.fd_count; i++) { char str[100] = { 0 }; int len = 99; if (SOCKET_ERROR == getsockopt(errorSockets.fd_array[i], SOL_SOCKET, SO_ERROR, str, &len)) { printf("没法获得错误信息\n"); } printf("%s\n", str); } for (u_int i = 0; i < writeSockets.fd_count; i++) { //printf("服务器%d,%d:可写\n", socketServer, writeSockets.fd_array[i]); if (SOCKET_ERROR == send(writeSockets.fd_array[i], "ok", 2, 0)) { int a = WSAGetLastError(); } } //有响应 for (u_int i = 0; i < readSockets.fd_count; i++) { if (readSockets.fd_array[i] == socketServer) { //accept SOCKET socketClient = accept(socketServer, NULL, NULL); if (INVALID_SOCKET == socketClient) { //连接出错 continue; } FD_SET(socketClient, &allSockets); //send } else { char strBuf[1500] = { 0 }; //客户端吧 int nRecv = recv(readSockets.fd_array[i], strBuf, 1500, 0); //send if (0 == nRecv) { //客户端下线了 //从集合中拿掉 SOCKET socketTemp = readSockets.fd_array[i]; FD_CLR(readSockets.fd_array[i], &allSockets); //释放 closesocket(socketTemp); } else if (0 < nRecv) { //接收到了消息 printf(strBuf); } else //SOCK_ERROR { //强制下线也叫出错 10054 int a = WSAGetLastError(); switch (a) { case 10054: { SOCKET socketTemp = readSockets.fd_array[i]; FD_CLR(readSockets.fd_array[i], &allSockets); //释放 closesocket(socketTemp); } } } } } }
将一组socket数组投递给系统,而后在系统里去查询socket是否有信号,过程都是在select函数里面去进行的,再到返回有操做的socket集合
select()函数执行遍历和返回有响应的socket,整个过程当中也是阻塞的。
等待时间 | 阻塞 |
---|---|
不等待 | 执行阻塞 |
半等待 | 执行阻塞+软阻塞 |
全等待 | 执行阻塞+硬阻塞 |
使用CS模型时,当连接了一个客户端,执行完了recv,while循环又回到了accept(),傻等着客户端来连接,没法多客户端连接通讯。
使用select模型时,是select在遍历着socket数组,有响应的socket再取出来,没有就一直遍历,虽然select()函数的执行也是阻塞的。能够理解为,每次都是在处理只有响应的socket,因此能够进行多客户端连接通讯。
当第一个客户端socket来连接时,select()函数将服务端socket从allsocket取出来,将新建的含有客户端socket添加到allsockets数组中,接着又在遍历allsocket,查看着时候有响应,因此不会像CS模型那种,在accept()函数阻塞着,傻等着。