您的位置:首页 > 编程语言

Windows Socket 异步编程(非阻塞模式) -- Select回送示例

2015-09-19 14:31 344 查看
使用Select异步模式来实现返送示例。服务器启动并监听9999端口,并将收到的客户端信息打印并返送给客户端。

重点理解的是:一个套接字是否是可读、可写状态。当服务器端socket在Accept成功之后,便是可读状态,接收客户端发送数据。当客户端发送recv函数时,这个socket便成为可写状态,服务器端便知道这个客户端可写,然后根据自己的定义发送给客户端内容。如果客户端不发送recv函数,即下面Client中的recv函数的话,服务器端保存的客户端这个socket便没有进入可写状态的时候,也就不会有回送的情况发生。



Server





1 #include <WINSOCK2.H>   
  2 #include <iostream>
  3 
  4 #pragma comment(lib,"WS2_32.lib")   
  5 
  6 using namespace std;
  7 
  8 #define   PORT   9999  
  9 #define   DATA_BUFSIZE   8192 
 10 
 11 
 12 // 定义套接字信息
 13 typedef   struct   _SOCKET_INFORMATION   {   
 14     CHAR   Buffer[DATA_BUFSIZE];        // 发送和接收数据的缓冲区
 15     WSABUF   DataBuf;                        // 定义发送和接收数据缓冲区的结构体,包括缓冲区的长度和内容
 16     SOCKET   Socket;                            // 与客户端进行通信的套接字
 17     DWORD   BytesSEND;                    // 保存套接字发送的字节数
 18     DWORD   BytesRECV;                    // 保存套接字接收的字节数
 19 } SOCKET_INFORMATION,   *   LPSOCKET_INFORMATION;   
 20 
 21 DWORD   TotalSockets = 0;                // 记录正在使用的套接字总数量
 22 LPSOCKET_INFORMATION   SocketArray[FD_SETSIZE];            // 保存Socket信息对象的数组,FD_SETSIZE表示SELECT模型中允许的最大套接字数量
 23 
 24 // 创建SOCKET信息
 25 BOOL   CreateSocketInformation(SOCKET   s)   
 26 {   
 27     LPSOCKET_INFORMATION   SI;                                        // 用于保存套接字的信息       
 28 //   printf("Accepted   socket   number   %d\n",   s);            // 打开已接受的套接字编号
 29 // 为SI分配内存空间
 30     if ((SI = (LPSOCKET_INFORMATION) GlobalAlloc(GPTR, sizeof(SOCKET_INFORMATION)))   ==   NULL)   
 31     {   
 32         printf("GlobalAlloc()   failed   with   error   %d\n",   GetLastError());   
 33         return   FALSE;   
 34     }   
 35     // 初始化SI的值    
 36     SI->Socket = s;   
 37     SI->BytesSEND = 0;   
 38     SI->BytesRECV = 0;   
 39     
 40     // 在SocketArray数组中增加一个新元素,用于保存SI对象 
 41     SocketArray[TotalSockets]   =   SI;       
 42     TotalSockets++;                        // 增加套接字数量
 43 
 44     return(TRUE);   
 45 }     
 46 
 47 // 从数组SocketArray中删除指定的LPSOCKET_INFORMATION对象
 48 void   FreeSocketInformation(DWORD   Index)   
 49 {   
 50     LPSOCKET_INFORMATION SI = SocketArray[Index];    // 获取指定索引对应的LPSOCKET_INFORMATION对象
 51     DWORD   i;   
 52     
 53     closesocket(SI->Socket);       // 关闭套接字
 54     GlobalFree(SI);   // 释放指定LPSOCKET_INFORMATION对象资源
 55 // 将数组中index索引后面的元素前移
 56     if (Index != (TotalSockets-1))
 57     {
 58         for (i = Index; i < TotalSockets; i++)   
 59         {   
 60             SocketArray[i] = SocketArray[i+1];   
 61         } 
 62     }
 63   
 64     TotalSockets--;        // 套接字总数减1
 65 }   
 66 
 67 
 68 int main()
 69 {
 70     SOCKET   ListenSocket;                    // 监听套接字
 71     SOCKET   AcceptSocket;                    // 与客户端进行通信的套接字
 72     SOCKADDR_IN   InternetAddr;            // 服务器的地址
 73     WSADATA   wsaData;                        // 用于初始化套接字环境
 74     INT   Ret;                                            // WinSock API的返回值
 75     FD_SET   WriteSet;                            // 获取可写性的套接字集合
 76     FD_SET   ReadSet;                            // 获取可读性的套接字集合
 77     DWORD   Total = 0;                                // 处于就绪状态的套接字数量
 78     DWORD   SendBytes;                        // 发送的字节数
 79     DWORD   RecvBytes;                        // 接收的字节数
 80 
 81 
 82 // 初始化WinSock环境
 83     if ((Ret = WSAStartup(MAKEWORD(2,2),&wsaData)) != 0)   
 84     {   
 85         printf("WSAStartup()   failed   with   error   %d\n",   Ret);   
 86         WSACleanup();   
 87         return -1;   
 88     }   
 89     // 创建用于监听的套接字 
 90     if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED))   ==   INVALID_SOCKET)     
 91     {   
 92         printf("WSASocket()   failed   with   error   %d\n",   WSAGetLastError());   
 93         return -1;   
 94     }   
 95     // 设置监听地址和端口号
 96     InternetAddr.sin_family = AF_INET;   
 97     InternetAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);   
 98     InternetAddr.sin_port = htons(PORT);   
 99     // 绑定监听套接字到本地地址和端口
100     if(bind(ListenSocket, (PSOCKADDR)&InternetAddr, sizeof(InternetAddr)) == SOCKET_ERROR)   
101     {   
102         printf("bind()   failed   with   error   %d\n",   WSAGetLastError());   
103         return -1;   
104     }   
105     // 开始监听
106     if   (listen(ListenSocket,   5))   
107     {   
108         printf("listen()   failed   with   error   %d\n",   WSAGetLastError());   
109         return -1;   
110     }   
111     // 设置为非阻塞模式
112     ULONG NonBlock = 1;   
113     if(ioctlsocket(ListenSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)   
114     {   
115         printf("ioctlsocket() failed with error %d\n", WSAGetLastError());   
116         return -1;   
117     }   
118     
119     CreateSocketInformation(ListenSocket);// 为ListenSocket套接字创建对应的SOCKET_INFORMATION,把ListenSocket添加到SocketArray数组中
120     
121     while(TRUE)   
122     {   
123         FD_ZERO(&ReadSet);// 准备用于网络I/O通知的读/写套接字集合
124         FD_ZERO(&WriteSet);   
125         
126         FD_SET(ListenSocket,   &ReadSet);// 向ReadSet集合中添加监听套接字ListenSocket
127 // 将SocketArray数组中的所有套接字添加到WriteSet和ReadSet集合中,SocketArray数组中保存着监听套接字和所有与客户端进行通信的套接字
128 // 这样就可以使用select()判断哪个套接字有接入数据或者读取/写入数据
129         for   (DWORD i=0; i<TotalSockets; i++)   
130         {
131             LPSOCKET_INFORMATION SocketInfo = SocketArray[i];
132             FD_SET(SocketInfo->Socket,   &ReadSet);//这说明该socket有读操作。而读操作是客户端发起的
133             FD_SET(SocketInfo->Socket,   &WriteSet);//这说明该socket有写操作。
134             
135         }
136         // 判断读/写套接字集合中就绪的套接字    
137         if((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR)//将NULL以形参传入Timeout,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止.服务器会停到这里等待客户端相应
138         {   
139             printf("select()   returned   with   error   %d\n",   WSAGetLastError());   
140             return -1;   
141         }      
142         // 依次处理所有套接字。本服务器是一个回应服务器,即将从客户端收到的字符串再发回到客户端。
143         for   (DWORD i=0; i<TotalSockets; i++)   
144         {
145             LPSOCKET_INFORMATION SocketInfo = SocketArray[i];            // SocketInfo为当前要处理的套接字信息
146 // 判断当前套接字的可读性,即是否有接入的连接请求或者可以接收数据
147             if (FD_ISSET(SocketInfo->Socket,   &ReadSet))   
148             {
149                 if(SocketInfo->Socket == ListenSocket)        // 对于监听套接字来说,可读表示有新的连接请求
150                 {
151                     Total--;    // 就绪的套接字减1
152 // 接受连接请求,得到与客户端进行通信的套接字AcceptSocket
153                     if((AcceptSocket = accept(ListenSocket, NULL, NULL))   !=   INVALID_SOCKET)   
154                     {   
155                         // 设置套接字AcceptSocket为非阻塞模式
156 // 这样服务器在调用WSASend()函数发送数据时就不会被阻塞
157                         NonBlock   =   1;   
158                         if(ioctlsocket(AcceptSocket, FIONBIO, &NonBlock)   ==   SOCKET_ERROR)   
159                         {   
160                             printf("ioctlsocket()   failed   with   error   %d\n",   WSAGetLastError());   
161                             return -1;                             
162                         }   
163                         // 创建套接字信息,初始化LPSOCKET_INFORMATION结构体数据,将AcceptSocket添加到SocketArray数组中
164                         if(CreateSocketInformation(AcceptSocket) == FALSE)   
165                             return -1;   
166                     }   
167                     else   
168                     {   
169                         if(WSAGetLastError() != WSAEWOULDBLOCK)   
170                         {   
171                             printf("accept()   failed   with   error   %d\n",   WSAGetLastError());   
172                             return -1;   
173                         }   
174                     }                   
175                 }
176                 else   // 接收数据
177                 {
178                     Total--;                // 减少一个处于就绪状态的套接字
179                     memset(SocketInfo->Buffer, ' ', DATA_BUFSIZE);            // 初始化缓冲区
180                     SocketInfo->DataBuf.buf = SocketInfo->Buffer;            // 初始化缓冲区位置
181                     SocketInfo->DataBuf.len   =   DATA_BUFSIZE;                // 初始化缓冲区长度
182 // 接收数据
183                     DWORD  Flags = 0;   
184                     if(WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags,NULL, NULL) == SOCKET_ERROR)   
185                     {   
186                         // 错误编码等于WSAEWOULDBLOCK表示暂没有数据,否则表示出现异常
187                         if(WSAGetLastError() != WSAEWOULDBLOCK)        
188                         {   
189                             printf("WSARecv()   failed   with   error   %d\n",   WSAGetLastError());   
190                             FreeSocketInformation(i);        // 释放套接字信息
191                         }   
192                         continue;   
193                     }     
194                     else   // 接收数据
195                     {   
196                         SocketInfo->BytesRECV = RecvBytes;        // 记录接收数据的字节数
197                         SocketInfo->DataBuf.buf[RecvBytes] = '\0';
198                         if(RecvBytes == 0)                                    // 如果接收到0个字节,则表示对方关闭连接
199                         {   
200                             FreeSocketInformation(i);   
201                             continue;   
202                         }   
203                         else
204                         {
205                             cout << SocketInfo->DataBuf.buf << endl;// 如果成功接收数据,则打印收到的数据
206                         }
207                     }
208                 }   
209             }
210             else
211             {
212                 // 如果当前套接字在WriteSet集合中,则表明该套接字的内部数据缓冲区中有数据可以发送
213                 if(FD_ISSET(SocketInfo->Socket,  &WriteSet))   
214                 {   
215                     Total--;            // 减少一个处于就绪状态的套接字
216                     SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;            // 初始化缓冲区位置
217                     SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;    // 初始化缓冲区长度
218                     if(SocketInfo->DataBuf.len > 0)        // 如果有需要发送的数据,则发送数据
219                     {
220                         if(WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0, NULL, NULL) == SOCKET_ERROR)   
221                         {   
222                             // 错误编码等于WSAEWOULDBLOCK表示暂没有数据,否则表示出现异常
223                             if(WSAGetLastError() != WSAEWOULDBLOCK)   
224                             {   
225                                 printf("WSASend()   failed   with   error   %d\n", WSAGetLastError());   
226                                 FreeSocketInformation(i);        // 释放套接字信息
227                             }   
228                             continue;   
229                         }   
230                         else   
231                         {   
232                             SocketInfo->BytesSEND += SendBytes;            // 记录发送数据的字节数
233 // 如果从客户端接收到的数据都已经发回到客户端,则将发送和接收的字节数量设置为0
234                             if (SocketInfo->BytesSEND   ==   SocketInfo->BytesRECV)   
235                             {   
236                                 SocketInfo->BytesSEND   =   0;   
237                                 SocketInfo->BytesRECV   =   0;   
238                             }   
239                         }   
240                     }
241                 }               
242 
243             }    // 如果ListenSocket未就绪,并且返回的错误不是WSAEWOULDBLOCK(该错误表示没有接收的连接请求),则出现异常
244 
245         }            
246     }   
247     system("pause");
248     return 0;
249 }






Client





1 #include <Winsock.h>
 2 #include <string>
 3 #include <iostream>
 4 
 5 #pragma comment(lib, "ws2_32.lib")
 6 using namespace std;
 7 
 8 
 9 #define BUFSIZE 64
10 #define PORT 9999
11 
12 int main()
13 {
14     WSAData wsaData;
15     SOCKET sHost;
16     sockaddr_in addrServ;
17     char buf[BUFSIZE];
18     int retVal;
19 
20     if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0)
21     {
22         cout << "WSAStartup失败!" << endl;
23         return -1;
24     }
25 
26     sHost = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
27     if (INVALID_SOCKET == sHost)
28     {
29         cout << "socket() 错误!" << endl;
30         WSACleanup();
31         return -1;
32     }
33 
34     addrServ.sin_family = AF_INET;
35     addrServ.sin_port = htons(PORT);
36     addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
37 
38     retVal = connect(sHost, (LPSOCKADDR)&addrServ, sizeof(addrServ));
39     if (SOCKET_ERROR == retVal)
40     {
41         cout << "connect 错误!" << endl;
42         closesocket(sHost);
43         WSACleanup();
44         return -1;
45     }
46 
47     while (true)
48     {
49         cout << "输入要发给服务器的内容" << endl;
50         //         string msg;
51 //         getline(cin, msg);
52         char msg[BUFSIZE];
53         cin.getline(msg, BUFSIZE);
54         ZeroMemory(buf, BUFSIZE);
55         strcpy(buf, msg);
56         retVal = send(sHost, buf, strlen(buf), 0);
57         if (SOCKET_ERROR == retVal)
58         {
59             cout << "发送失败" << endl;
60             closesocket(sHost);
61             WSACleanup();
62             return -1;
63         }
64 
65         retVal = recv(sHost, buf, sizeof(buf)+1, 0);
66         cout << "从服务器端接收:" << buf << endl;
67         if (strcmp(buf, "quit") == 0)
68         {
69             cout << "quit" << endl;
70             break;
71         }
72     }
73 
74     closesocket(sHost);
75     WSACleanup();
76 
77 
78     return 0;
79 }






结果截图

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: