非阻塞socket学习,select基本用法
server
#include <stdio.h> #include <winsock2.h> #include <iostream> #pragma comment(lib, "WS2_32.lib") #define PORT 9999 #define DATA_BUFSIZE 8192 typedef struct _SOCKET_INFORMATION{ CHAR Buffer[DATA_BUFSIZE]; //发送和接收数据的缓冲区 WSABUF DataBuf; //定义发送和接收数据缓冲区的结构体,包括缓冲区长度和内容 SOCKET Socket; //与客户端进行通信的套接字 DWORD BytesSEND; //保存套接字发送的字节数 DWORD BytesRECV; //保存套接字接收的字节数 } SOCKET_INFOMATION, * LPSOCKET_INFORMATION; DWORD TotalSockets = 0; LPSOCKET_INFORMATION SocketArray[FD_SETSIZE]; bool CreateSocketInformation(SOCKET s) { LPSOCKET_INFORMATION SI; //用于保存套接字的信息 //为SI分配内存空间 if ((SI = (LPSOCKET_INFORMATION)GlobalAlloc(GPTR, sizeof SOCKET_INFOMATION)) == NULL) { printf("GlobalAlloc() failed with error %d\n", GetLastError()); return FALSE; } //初始化SI的值 SI->Socket = s; SI->BytesSEND = 0; SI->BytesRECV = 0; SocketArray[TotalSockets] = SI; TotalSockets++; return true; } void FreeSocketInformation(DWORD Index) { LPSOCKET_INFORMATION SI = SocketArray[Index]; DWORD i; closesocket(SI->Socket); GlobalFree(SI); if (Index != (TotalSockets - 1)) { for (i = Index; i < TotalSockets; i++) { SocketArray[i] = SocketArray[i + 1]; } } TotalSockets--; } int main() { SOCKET ListenSocket; //监听套接字 SOCKET AcceptSocket; //与客户端通信的套接字 SOCKADDR_IN InternetAddr; //服务器地址 WSADATA wsaData; int Ret; FD_SET WriteSet; //获取可写套接字集合 FD_SET ReadSet; //获取可读性套接字集合 DWORD Total = 0; //处于就绪状态的套接字 DWORD SendBytes; //发送套接字 DWORD RecvBytes; //接收的套接字 //初始化WinSock环境 if ((Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)) { printf("WSAStartup() failed with error %d\n", Ret); WSACleanup(); return -1; } //创建用于监听的套接字 if ((ListenSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED)) == INVALID_SOCKET) { printf("WSASocket() failed with error %d\n", WSAGetLastError()); return -1; } //设置监听地址和端口 InternetAddr.sin_family = AF_INET; InternetAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); InternetAddr.sin_port = htons(PORT); //绑定监听套接字到本地地址和端口 if (bind(ListenSocket, (PSOCKADDR)&InternetAddr, sizeof InternetAddr) == INVALID_SOCKET) { printf("bind() failed with error %d\n", WSAGetLastError()); return -1; } //开始监听 if (listen(ListenSocket, 5)) { printf("listen() failed with error %d\n", WSAGetLastError()); return -1; } //设置成非阻塞方式 ULONG NonBlock = 1; if (ioctlsocket(ListenSocket, FIONBIO, &NonBlock)) { printf("ioctlsocket() failed with error %d\n", WSAGetLastError()); return -1; } CreateSocketInformation(ListenSocket);//ListenSocket套接字创建对应的SOCKET_INFORMATION,把ListenSocket添加到SocketArray数组中 while (true) { FD_ZERO(&ReadSet);//读套接字清零 FD_ZERO(&WriteSet);//写套接字清零 FD_SET(ListenSocket, &ReadSet);//向ReadSet中添加监听套接字ListenSocket for (DWORD i = 0; i < TotalSockets; ++i) { LPSOCKET_INFORMATION SocketInfo = SocketArray[i]; FD_SET(SocketInfo->Socket, &ReadSet); FD_SET(SocketInfo->Socket, &WriteSet); } if((Total = select(0, &ReadSet, &WriteSet, NULL, NULL)) == SOCKET_ERROR){//刚开始为什么监听套接字没有可写返回,而与客户端连接的套接字总是可写??? printf("select() returned with error %d\n", WSAGetLastError()); return -1; } for (DWORD i = 0; i < TotalSockets; i++) { LPSOCKET_INFORMATION SocketInfo = SocketArray[i];//SocketArray保存了所有监听的可读和可写的套接字 if (FD_ISSET(SocketInfo->Socket, &ReadSet))//判断套接字是否可读 { if (SocketInfo->Socket == ListenSocket)//对于监听套接字表示有新的客户端连接 { Total--; if ((AcceptSocket = accept(ListenSocket, NULL, NULL)) != INVALID_SOCKET) { NonBlock = 1; if (ioctlsocket(AcceptSocket, FIONBIO, &NonBlock) == SOCKET_ERROR)//设置AcceptSocket套接字为非阻塞套接字,这样与客户端通信就不会阻塞了 { printf("ioctlsocket() failed with error %d\n", WSAGetLastError()); return -1; } if (CreateSocketInformation(AcceptSocket) == false)//将AcceptSocket添加到SocketArray数组中 { return -1; } } else { if (WSAGetLastError() != WSAEWOULDBLOCK) { printf("accept() failed with error %d\n", WSAGetLastError()); return -1; } } } else//接收数据 { Total--; memset(SocketInfo->Buffer, 0, sizeof SocketInfo->Buffer); SocketInfo->DataBuf.buf = SocketInfo->Buffer; SocketInfo->DataBuf.len = DATA_BUFSIZE; DWORD Flags = 0; if (WSARecv(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &RecvBytes, &Flags, NULL, NULL) == SOCKET_ERROR) { if (WSAGetLastError() != WSAEWOULDBLOCK) { printf("WSARecv() failed with error %d\n", WSAGetLastError()); FreeSocketInformation(i); } continue; } else { SocketInfo->BytesRECV = RecvBytes; SocketInfo->DataBuf.buf[RecvBytes] = '\0'; if (RecvBytes == 0) { FreeSocketInformation(i); continue; } else { std::cout << SocketInfo->DataBuf.buf << std::endl;// 如果成功接收数据,则打印收到的数据 } } } } else { if (FD_ISSET(SocketInfo->Socket, &WriteSet))//可写会一直被调用,判断大于其写缓冲的最低水位 { Total--; SocketInfo->DataBuf.buf = SocketInfo->Buffer + SocketInfo->BytesSEND;// 初始化缓冲区位置 SocketInfo->DataBuf.len = SocketInfo->BytesRECV - SocketInfo->BytesSEND;// 初始化缓冲区长度 if (SocketInfo->DataBuf.len > 0)// 如果有需要发送的数据,则发送数据 { if (WSASend(SocketInfo->Socket, &(SocketInfo->DataBuf), 1, &SendBytes, 0, NULL, NULL) == SOCKET_ERROR) { if (WSAEWOULDBLOCK != WSAGetLastError()) { printf("WSASend() failed with error %d\n", WSAGetLastError()); FreeSocketInformation(i); } continue; } else { SocketInfo->BytesSEND += SendBytes; //记录发送数据的字节数目 if (SocketInfo->BytesSEND == SocketInfo->BytesRECV) { SocketInfo->BytesSEND = 0; SocketInfo->BytesRECV = 0; } } } } } } } system("pause"); return 0; }
client
#include <winsock.h> #include <iostream> #define BUFSIZE 64 #define PORT 9999 #pragma comment(lib, "WS2_32.lib") int main() { WSADATA wsaData; SOCKET sClient; sockaddr_in addrServ; char buf[BUFSIZE]; int retVal; if(WSAStartup(MAKEWORD(2,2), &wsaData) != 0) { std::cout << "WSAStartup失败!" << std::endl; return -1; } sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == sClient) { std::cout << "socket() 错误!" << std::endl; WSACleanup(); return -1; } addrServ.sin_family = AF_INET; addrServ.sin_port = htons(PORT); addrServ.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); retVal = connect(sClient, (LPSOCKADDR)&addrServ, sizeof(addrServ)); if (SOCKET_ERROR == retVal) { std::cout << "connect 错误!" << std::endl; closesocket(sClient); WSACleanup(); return -1; } while(true) { std::cout << "输入要发给服务器的内容:" << std::endl; char msg[BUFSIZE]; std::cin.getline(msg, BUFSIZE); ZeroMemory(buf, BUFSIZE); strcpy(buf, msg); retVal = send(sClient, buf, strlen(buf), 0); if (SOCKET_ERROR == retVal) { std::cout << "发送失败" << std::endl; closesocket(sClient); WSACleanup(); return -1; } retVal = recv(sClient, buf, sizeof buf, 0); std::cout << "从服务器端接收:" << buf << std::endl; if (strcmp(buf, "quit") == 0) { std::cout << "quit" << std::endl; break; } } closesocket(sClient); WSACleanup(); return 0; }