Windows Socket 异步编程(非阻塞模式) -- Select回送示例
2015-09-19 14:31
344 查看
使用Select异步模式来实现返送示例。服务器启动并监听9999端口,并将收到的客户端信息打印并返送给客户端。
重点理解的是:一个套接字是否是可读、可写状态。当服务器端socket在Accept成功之后,便是可读状态,接收客户端发送数据。当客户端发送recv函数时,这个socket便成为可写状态,服务器端便知道这个客户端可写,然后根据自己的定义发送给客户端内容。如果客户端不发送recv函数,即下面Client中的recv函数的话,服务器端保存的客户端这个socket便没有进入可写状态的时候,也就不会有回送的情况发生。
Server
Client
结果截图
重点理解的是:一个套接字是否是可读、可写状态。当服务器端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 }
结果截图
相关文章推荐
- C++语句:vector<string>v_string;是什么意思?v_string代表什么?
- JAVA类的方法调用和变量
- EasyMock异常“java.lang.IllegalStateException: 1 matchers expected, 3 recorded”的奇葩原因
- 红黑树C++实现
- Spring transaction manager example
- PYTHON正成为中国互联网+的核心技术驱动力
- Ubuntu下配置Python进行数据处理的环境
- 关于C++的接口类
- 错误代码: 1146 Table 'test.triggers' doesn't exist
- java__输入输出流复习
- VC++ unicode下读取unicode CFile::typeUnicodetxt的数据
- 树莓派--Raspbian安装与设置
- win8下sublime text2的安装和对python的环境配置
- 黑马程序员独特的放松方式 "小巧玲珑的C语言游戏--井字游戏"
- 如何使用Git上传项目代码到github
- EditPlus常用快捷键
- Andorid--java0
- Git工具连接GitHub(Windows配置篇)
- java__递归
- socket编程中select的使用