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

使用Select异步模式来实现返送示例。服务器启动并监听9999端口,并将收到的客户端信息打印并返送给客户端。

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

 

Server

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

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 }

 

结果截图

posted @ 2012-04-01 00:29  hanyuanbo  阅读(8423)  评论(2编辑  收藏  举报