在 Winsocket 一:单线程阻塞server&client程序(tcp) 和 Winsocket 二:多线程阻塞服务器程序(tcp)介绍了阻塞tcp程序,阻塞式tcp程序服务器程序会因为建立连接和关闭连接而频繁的创建和关闭线程会产生大量的内存碎片,从而导致服务端程序不能保证长时间的稳定运行,本文简单介绍非阻塞式tcp程序的编写。
阻塞是指在进行一个操作的时候,如服务器接收客户端的连接(accept),客户端程序执行connect操作,服务器或者客户端读写数据(read、write),如果该操作没有执行完成(成功或者失败都算是执行完成),则程序会一直阻塞在操作执行的地方,直到该操作返回一个明确的结果。非阻塞式程序会在产生阻塞操作的地方阻塞一定的时间(该时间可以由程序员自己设置)。如果操作没有完成,在到达所设置的时间之后,无论该操作成功与否,都结束该操作而执行程序后面的操作。
// server_2.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <cassert> #include <list> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") #define ASSERT assert using namespace std; static const int c_iPort = 10001; bool GraceClose(SOCKET *ps); int _tmain(int argc, _TCHAR* argv[]) { int iRet = SOCKET_ERROR; // 初始化Winsocket WSADATA data; ZeroMemory(&data, sizeof(WSADATA)); iRet = WSAStartup(MAKEWORD(2, 0), &data); ASSERT(SOCKET_ERROR != iRet); // 建立服务端程序的监听套接字 SOCKET skListen = INVALID_SOCKET; skListen = socket(AF_INET, SOCK_STREAM, 0); ASSERT(INVALID_SOCKET != skListen); // 初始化监听套接字地址信息 sockaddr_in adrServ; ZeroMemory(&adrServ, sizeof(sockaddr_in)); adrServ.sin_family = AF_INET; adrServ.sin_port = htons(c_iPort); adrServ.sin_addr.s_addr = INADDR_ANY; // 绑定监听套接字到本地 iRet = bind(skListen, (sockaddr*)&adrServ, sizeof(sockaddr_in)); ASSERT(SOCKET_ERROR != iRet); // 使用监听套接字进行监听 iRet = listen(skListen, FD_SETSIZE); ASSERT(SOCKET_ERROR != iRet); // 将套接口从阻塞状态设置到费阻塞状态 unsigned long ulMode = 1; iRet = ioctlsocket(skListen, FIONBIO, &ulMode); ASSERT(SOCKET_ERROR != iRet); fd_set fsListen; //fd_set为套字集合 FD_ZERO(&fsListen); //FD_ZERO(*set)将集合set清空 fd_set fsRead; FD_ZERO(&fsRead); timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; list<SOCKET> sl; int i = 2; for(;;) { // 接收来自客户端的连接, 并将新建的套接字加入套接字列表中 FD_SET(skListen, &fsListen);//FD_SET(s, *set)将套接字s加入到集合set中 iRet = select(1, &fsListen, NULL, NULL, &tv); if(iRet > 0) { sockaddr_in adrClit; int iLen = sizeof(sockaddr_in); ZeroMemory(&adrClit, iLen); SOCKET skAccept = accept(skListen, (sockaddr*)&adrClit,&iLen); ASSERT(INVALID_SOCKET != skAccept); sl.push_back(skAccept); cout << "New connection " << skAccept << ", c_ip: " << inet_ntoa(adrClit.sin_addr) << ", c_port: " << ntohs(adrClit.sin_port) << endl; } // 将套接字列表中的套接字加入到可读套接字集合中,以便在可以检测集合中的套接字是否有数据可读 FD_ZERO(&fsRead); for(list<SOCKET>::iterator iter = sl.begin(); iter != sl.end(); ++iter) { FD_SET(*iter, &fsRead); } // 检测集合中的套接字是否有数据可读 iRet = select(sl.size(), &fsRead, NULL, NULL, &tv); if(iRet > 0) { for(list<SOCKET>::iterator iter = sl.begin(); iter != sl.end(); ++iter) { // 如果有数据可读, 则遍历套接字列表中的所有套接字,检测出有数据可读的套接字 iRet = FD_ISSET(*iter, &fsRead);// FD_ISSET(s, *set)判断套接字s是否在集合中有信号 if(iRet > 0) { // 读取套接字上的数据 const int c_iBufLen = 512; char recvBuf[c_iBufLen + 1] = {'\0'}; int iRecv = SOCKET_ERROR; iRecv = recv(*iter, recvBuf, c_iBufLen, 0); if (iRecv <= 0 )// 读取出现错误或者对方关闭连接 { iRecv == 0 ? cout << "Connection shutdown at socket " << *iter << endl ://优雅关闭 cout << "Connection recv error at socket " << *iter << endl;// 粗暴关闭 iRet = GraceClose(&(*iter)); ASSERT(iRet); } else { recvBuf[iRecv] = '\0'; cout << "Server recved message from socket " << *iter << ": " << recvBuf << endl; // 创建可写集合 FD_SET fsWrite; FD_ZERO(&fsWrite); FD_SET(*iter, &fsWrite); // 如果可可写套接字, 则向客户端发送数据 iRet = select(1, NULL, &fsWrite, NULL, &tv); if (iRet <= 0) { int iWrite = SOCKET_ERROR; iWrite = send(*iter, recvBuf, iRecv, 0); if (SOCKET_ERROR == iWrite) { cout << "Send message error at socket " << *iter << endl; iRet = GraceClose(&(*iter)); ASSERT(iRet); } } } } } //for sl.remove(INVALID_SOCKET); // 删除无效的套接字, 套接字在关闭后被设置为无效 } //if } //for (;;) // 将套接字设置回阻塞状态 ulMode = 0; iRet = ioctlsocket(skListen, FIONBIO, &ulMode); ASSERT(SOCKET_ERROR != iRet); // 关闭监听套接字 iRet = GraceClose(&skListen); ASSERT(iRet); // 清理Winsocket资源 iRet = WSACleanup(); ASSERT(SOCKET_ERROR != iRet); system("pause"); return 0; } bool GraceClose(SOCKET *ps) { const int c_iBufLen = 512; char szBuf[c_iBufLen + 1] = {'\0'}; // 关闭该套接字的连接 int iRet = shutdown(*ps, SD_SEND); while(recv(*ps, szBuf, c_iBufLen, 0) > 0); if (SOCKET_ERROR == iRet) { return false; } // 清理该套接字的资源 iRet = closesocket(*ps); if (SOCKET_ERROR == iRet) { return false; } *ps = INVALID_SOCKET; return true; }
if(iRet > 0) { sockaddr_in adrClit; int iLen = sizeof(sockaddr_in); ZeroMemory(&adrClit, iLen); SOCKET skAccept = accept(skListen, (sockaddr*)&adrClit,&iLen); ASSERT(INVALID_SOCKET != skAccept); sl.push_back(skAccept); cout << "New connection " << skAccept << ", c_ip: " << inet_ntoa(adrClit.sin_addr) << ", c_port: " << ntohs(adrClit.sin_port) << endl; }将接受自客户端程序的套接字加入到list中,以方便管理。
if (iRecv <= 0 )// 读取出现错误或者对方关闭连接 { iRecv == 0 ? cout << "Connection shutdown at socket " << *iter << endl ://优雅关闭 cout << "Connection recv error at socket " << *iter << endl;// 粗暴关闭 iRet = GraceClose(&(*iter)); ASSERT(iRet); }在套接字被客户端关闭或者异常中断后,需要将list中的套接字设置为无效套接字,并在操作完所有的套接字一次后,将无效的套接字从集合中移除。
// client.cpp : 定义控制台应用程序的入口点。 // #include "stdafx.h" #include <iostream> #include <cassert> #include <WinSock2.h> #pragma comment(lib, "ws2_32.lib") using namespace std; #define ASSERT assert static const char c_szIP[] = "127.0.0.1"; static const int c_iPort = 10001; bool GraceClose(SOCKET *ps); int main() { int iRet = SOCKET_ERROR; // 初始化Winsocket WSADATA data; ZeroMemory(&data, sizeof(WSADATA)); iRet = WSAStartup(MAKEWORD(2, 0), &data); ASSERT(SOCKET_ERROR != iRet); // 建立连接套接字 SOCKET skClient = INVALID_SOCKET; skClient = socket(AF_INET, SOCK_STREAM, 0); ASSERT(INVALID_SOCKET != skClient); // 初始化连接套接字地址信息 sockaddr_in adrServ; ZeroMemory(&adrServ, sizeof(sockaddr_in)); adrServ.sin_family = AF_INET; adrServ.sin_port = htons(c_iPort); adrServ.sin_addr.s_addr = inet_addr(c_szIP); // 将套接口从阻塞状态设置到非阻塞状态 unsigned long ulEnable = 1; iRet = ioctlsocket(skClient, FIONBIO, &ulEnable); ASSERT(SOCKET_ERROR != iRet); fd_set fsWrite; TIMEVAL tv; tv.tv_sec = 1; tv.tv_usec = 0; cout << "Client began to connect to the server..." << endl; for (;;) { // 使用非阻塞方式连接服务器,请注意connect操作的返回值总是为SOCKET_ERROR iRet = connect(skClient, (sockaddr*)&adrServ, sizeof(sockaddr_in)); int iErrorNo = SOCKET_ERROR; int iLen = sizeof(int); // 如果getsockopt返回值不为0,则说明有错误出现 if (SOCKET_ERROR == iRet && 0 != getsockopt(skClient, SOL_SOCKET, SO_ERROR, (char*)&iErrorNo, &iLen)) { cout << "An error happened on connecting to server. The error no is " << iErrorNo << ". The program will exit now." << endl; exit(-1); } FD_ZERO(&fsWrite); FD_SET(skClient, &fsWrite); // 如果集合fsWrite中的套接字有信号,则说明连接成功,此时iRet的返回值大于0 iRet = select(1, NULL, &fsWrite, NULL, &tv); if (iRet > 0) { cout << "Successed connect to the server..." << endl; break; } cout << "retrying" << endl; } //for for(;;) { const int c_iBufLen =512; char szBuf[c_iBufLen + 1] = {'\0'}; cout << "What you will say:"; cin >> szBuf; if(0 == strcmp("exit", szBuf)) { break; } FD_ZERO(&fsWrite); FD_SET(skClient, &fsWrite); // 如果集合fsWrite中的套接字有信号, 则可以用send操作发数据 iRet = select(1, NULL, &fsWrite, NULL, &tv); if (0 < iRet) { iRet = send(skClient, szBuf, strlen(szBuf), 0); if(SOCKET_ERROR == iRet) { cout << "send error." << endl; break; } fd_set fsRead; FD_ZERO(&fsRead); FD_SET(skClient, &fsRead); // 如果集合fsRead中的套接字有信号, 则可以用recv操作读数据 iRet = select(1, &fsRead, NULL, NULL, &tv); if (0 < iRet) { iRet = recv(skClient, szBuf, c_iBufLen, 0); if(0 == iRet) { cout << "connection shutdown." << endl; break; } else if(SOCKET_ERROR == iRet) { cout << "recv error." << endl; break; } szBuf[iRet] = '\0'; cout << szBuf << endl; } } //if } //for // 将套接字设置回阻塞状态 ulEnable = 0; iRet = ioctlsocket(skClient, FIONBIO, &ulEnable); ASSERT(SOCKET_ERROR != iRet); // 关闭监听套接字 iRet = GraceClose(&skClient); ASSERT(iRet); // 清理Winsocket资源 iRet = WSACleanup(); ASSERT(SOCKET_ERROR != iRet); system("pause"); return 0; } bool GraceClose(SOCKET *ps) { const int c_iBufLen = 512; char szBuf[c_iBufLen + 1] = {'\0'}; // 关闭该套接字的连接 int iRet = shutdown(*ps, SD_SEND); while(recv(*ps, szBuf, c_iBufLen, 0) > 0); if (SOCKET_ERROR == iRet) { return false; } // 清理该套接字的资源 iRet = closesocket(*ps); if (SOCKET_ERROR == iRet) { return false; } *ps = INVALID_SOCKET; return true; }