基于select模型的TCP服务器
实验工程文件下载
https://gryffinbit.lanzous.com/iSR7Gjhg68b
实验目的
- 熟悉非阻塞套接字的工作模式
- 理解select模型的基本思路
实验环境
Windows10 x64,Visual Studio 2017
实验要求
将第三章的点对点的C/S模型改用select模型。为了方便理解,我们只是关注可读套接字,也就是select函数的第三个和第四个参数均为NULL。
实验原理(模型)
select的函数
1 2 3 4 5 6 7
| int select( int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout );
|
- fd_set数据类型,代表着一系列特定套接字的集合。
- nfds : 为保持与早期套接字应用程序兼容。
- readfds: 用于检查可读性。该集合包括想要检查是否符合下述任何一个条件的套接字。
- 有数据到达,可以读入
- 连接已关闭、重设或中止
- 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
- wirtefds:用于检查可写性。该集合包括想要检查是否符合下述任何一个条件的套接字。
- 发送缓冲区已空,可以发送数据。
- 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
- exceptfds:用于检查带外数据。该集合包括想要检查是否符合下述任何一个条件的套接字。
- 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
- 有带外(out-of-band,OOB)数据可供读取
- timeout:是一个指向一个timeval结构的指针,用于决定select等待I/O操作完成的最长时间。如果timeout是一个空指针,那么select调用会无限制地等待下去,直到至少有一个套接字符合制定的条件后才结束。
timeval结构的定义
1 2 3 4
| strcut timeval{ long tv_sec; long tv_usec; };
|
select模型的操作步骤
用select操作一个或多个套接字句柄,一般采用下述步骤。
- 使用FD_ZERO宏,初始化自己感兴趣的每一个fd_set集合。
- 使用FD_SET宏,将要检查的套接字句柄添加到自己感兴趣的每哥fd_set集合中,相当于在指定的fd_set集合中,设置好要检查的I/O活动。
- 调用select函数,然后等待。select完成返回后,会修改每个fd_set结构,删除那些不存在待决I/O操作的套接字句柄,在各个fd_set集合中返回符合条件的套接字。
- 根据select的返回值,使用FD_ISSET宏,对每个fd_set集合进行检查,判断一个待定的套接字是否仍在集合中,便可判断出哪些套接字存在着尚未完成的I/O操作。
- 知道了每个集合中未完成的I/O操作之后,对相应的套接字的I/O进行处理,然后返回步骤1,继续进行select处理。
实验过程
将客户端中调用recv函数的部分都删掉,也就是客户端只能给服务器发消息,不能接收消息。
select模型也需要引用头文件库文件,打开网络库,校验版本,创建监听套接字,绑定地址,开始监听,然后创建一个fd_set结构体,并把监听套接字放这个集合中。
在一个死循环中调用select函数,并判断select的返回值。
若返回值为SOCKET_ERROR,则报错并做相应处理;
若返回值为0, 则继续;
若返回值大于0,说明select函数有反应。有反应就要区分是监听套接字还是响应套接字导致的。若是监听套接字导致的,就调用accept函数,创建一个新的响应套接字,并把这个响应套接字放到第2步定义的fd_set结构当中。如果是响应套接字,就调用recv函数接收客户机发来的消息。
最后释放掉所有的套接字,并关闭网络库
测试。先开始运行select模型,然后在客户端的Debug/Release文件夹中双击exe文件即可打开多个客户端,这些客户端均可给服务器发消息。
测试完成后,可以在accept函数之后调用send函数,发消息给客户端提示连接成功。相应的,就需要先在客户端调用recv函数接收此消息。
👉创建服务器端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
| #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <WS2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
int main(void) { WORD wdVersion = MAKEWORD(2, 2); WSADATA wsaData;
if (0 != WSAStartup(wdVersion, &wsaData)) { printf("WSAStartup fail!"); return -1; }
if (2 != HIBYTE(wsaData.wVersion) || 2 != LOBYTE(wsaData.wVersion)) { printf("Version fail!"); WSACleanup(); return -1; }
SOCKET socketListen = socket(AF_INET, SOCK_STREAM, 0); if (SOCKET_ERROR == socketListen) { printf("socket fail!"); WSACleanup(); return -1; }
SOCKADDR_IN sockAddress; sockAddress.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", (void*)&sockAddress.sin_addr); sockAddress.sin_port = htons(51314);
if (SOCKET_ERROR == bind(socketListen, (struct sockaddr*)&sockAddress, sizeof(sockAddress))) { printf("bind fail!"); closesocket(socketListen); WSACleanup(); return -1; }
if (SOCKET_ERROR == listen(socketListen, 5)) { printf("listen fail!"); closesocket(socketListen); WSACleanup(); return -1; }
fd_set allSockets; FD_ZERO(&allSockets); FD_SET(socketListen, &allSockets);
while (true) { fd_set TempSockets = allSockets;
struct timeval st; st.tv_sec = 10; st.tv_usec = 0;
int ret = select(0, &TempSockets, NULL, NULL, &st); if (ret == SOCKET_ERROR) { printf("select fail!"); break; } if (0 == ret) { continue; }
if (ret > 0) { for (u_int i = 0; i < TempSockets.fd_count; i++) { if (TempSockets.fd_array[i] == socketListen) { SOCKET SockAccept = accept(socketListen, NULL, NULL); if (SockAccept == INVALID_SOCKET) { printf("创建响应套接字失败!"); continue; } FD_SET(SockAccept, &allSockets); if (SOCKET_ERROR == send(SockAccept, "我是服务器,您已成功连接", strlen("我是服务器,您已成功连接"), 0)) { printf("send fail!"); closesocket(SockAccept); WSACleanup(); return -1; } } else { char szRecvBuffer[1024] = { 0 };
int nReturnValue = recv(TempSockets.fd_array[i], szRecvBuffer, sizeof(szRecvBuffer) - 1, 0);
if (0 == nReturnValue) { printf("客户端已下线"); SOCKET SockTemp = TempSockets.fd_array[i]; FD_CLR(TempSockets.fd_array[i], &allSockets); closesocket(SockTemp); } else if (SOCKET_ERROR == nReturnValue) { int nRes = WSAGetLastError(); printf("响应套接字%d所对应的客户端中断连接\n", TempSockets.fd_array[i]); } else { printf("Data from Socket %d : %s \n", TempSockets.fd_array[i],szRecvBuffer); } } } } } for (u_int i = 0; i < allSockets.fd_count; i++) { closesocket(allSockets.fd_array[i]); } WSACleanup();
system("pause"); return 0; }
|
👉创建客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112
| #include <stdio.h> #include <stdlib.h> #include <WinSock2.h> #include <WS2tcpip.h>
#pragma comment(lib,"Ws2_32.lib")
int main() { WORD wdVersion = MAKEWORD(2, 2); WSADATA wsaData;
if (0 != WSAStartup(wdVersion, &wsaData)) {
printf("WSAStartup fail!"); return -1; }
if (2 != HIBYTE(wsaData.wHighVersion) || 2 != LOBYTE(wsaData.wVersion)) { printf("Version fail!"); WSACleanup(); return -1; }
SOCKET socketUser = socket(AF_INET, SOCK_STREAM, 0); if (SOCKET_ERROR == socketUser) { printf("socket fail!"); WSACleanup(); return -1; }
SOCKADDR_IN sockAddress; sockAddress.sin_family = AF_INET; inet_pton(AF_INET, "127.0.0.1", (void*)&sockAddress.sin_addr); sockAddress.sin_port = htons(51314);
if (SOCKET_ERROR == connect(socketUser, (struct sockaddr*)&sockAddress, sizeof(sockAddress))) {
printf("connect fail!\n"); closesocket(socketUser); WSACleanup(); return -1; }
char szBuffer[1024] = { 0 }; int nReturnValue = recv(socketUser, szBuffer, sizeof(szBuffer), 0);
if (0 == nReturnValue) { closesocket(socketUser); } else if (SOCKET_ERROR == nReturnValue) { printf("连接中断"); } else { printf("server data : %s\n", szBuffer);
while (1) { char szSendData[1024] = { 0 }; printf("Input Something : "); scanf_s("%s", szSendData, 1024); if (SOCKET_ERROR == send(socketUser, szSendData, strlen(szSendData), 0)) {
printf("send fail!"); closesocket(socketUser); WSACleanup(); return -1; } } }
closesocket(socketUser); WSACleanup();
system("pause"); return 0; }
|
👉最后运行客户端,生成解决方案之后,到工程文件夹里找到exe,开启多个客户端。