最简单的服务器的socket程序流程如下(面向连接的TCP连接 ):
1. WSAStartup(); 初始化网络库的使用。
2. socket(); 获得一个socket。
3. bind(); 把获得的socket绑定到一个ip 和端口。既然作为服务器, ip通常为本地IP127.0.0.1。
4. listen(); 监听已经绑定了指定端口的socket。
5. accept(); 接受一个来自客户端的连接。
accept()返回一个新的socket,该socket代表着本地服务器与某一个连接过来的客户端的链接。以该socket为参数,可以调用send函数往客户端发送数据,也可以调用recv函数接受客户端发送过来的函数。
最后服务器程序结束的时候调用closesocket()关闭socket, WSACleanup()终止网络库的使用,清理资源。
最简单的客户端的socket程序流程如下(同样是面向连接的TCP连接):
1. WSAStartup();初始化网络库的使用。
2. socket(); 获得一个socket。
3. connect(); 连接到一个 服务器。
连接成功后就可以收发数据了。收发完毕后调用closesocket()关闭socket,最后程序结束前调用 WSACleanup()清理资源。
下面直接上代码
需包含以下头文件和定义
#include <stdlib.h>
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")
#define SERVE_ADDRESS "127.0.0.1"
#define SERVE_PORT 7001
// ---------------------------- WSAStartup() ----------------------------// WSADATA wsd; int resStartup = WSAStartup(MAKEWORD(2,2),&wsd); if(0 != resStartup) { printf("failed to WSAStartup!\n"); goto Main_End; } //------------------------------------------------------------------------------// // ---------------------------- socket() ----------------------------// SOCKET serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(INVALID_SOCKET == serverSocket) { printf("failed to invoke socket, the socket returned is invalid!\n"); goto Main_End; } // ------------------------------------------------------------------------------------// //---------------------------- bind() ----------------------------// // 初始化 struct sockaddr 结构体, SOCKADDR_IN就是 struct sockaddr的宏定义 SOCKADDR_IN localAddr; localAddr.sin_family = AF_INET; localAddr.sin_addr.S_un.S_addr = inet_addr(SERVE_ADDRESS); localAddr.sin_port = htons(SERVE_PORT); memset(localAddr.sin_zero,0x0,sizeof(localAddr.sin_zero)); // int resBind = bind(serverSocket,(sockaddr*)&localAddr,sizeof(SOCKADDR_IN)); if(0 != resBind) { printf("failed to bind ! \n"); goto Main_End; } //------------------------------------------------------------------------------------// //---------------------------- listen() ----------------------------// int resListen = listen(serverSocket,5); if(0 != resListen) { printf("failed to listen! \n"); goto Main_End; } printf("the server is listening now!\n"); //------------------------------------------------------------------------------------// //---------------------------- accept() ----------------------------// SOCKADDR_IN clientAddr; int addrLen = sizeof(clientAddr); SOCKET acceptedSocket = accept(serverSocket,(sockaddr*)&clientAddr,&addrLen); if(INVALID_SOCKET == acceptedSocket) { printf("accept error!\n"); goto Main_End; } printf("a client has connected to the server!\n"); //------------------------------------------------------------------------------------// char recvBuffer[256]; char sendBuffer[256]; strcpy(sendBuffer,"server:Welcome to connect !"); int sendBufLen = strlen(sendBuffer); int resSend = send(acceptedSocket,sendBuffer,sendBufLen,0); while(true) { if(resSend != sendBufLen) //发送的长度与需要发送的长度不等 { printf("send data error!!\n"); break; } int recvLen = recv(acceptedSocket,recvBuffer,sizeof(recvBuffer),0); if(0 == recvLen) { printf("a client close the socket!\n"); break; } else if(recvLen < 0) { printf("an error has happen when receiving\n"); break; } recvBuffer[recvLen] = '\0'; printf("client:%s\n",recvBuffer); //在客户发过来的数据前面加上server:再发回给客户端 strcpy(sendBuffer,"server:"); strcat(sendBuffer,recvBuffer); sendBufLen = strlen(sendBuffer); resSend = send(acceptedSocket,sendBuffer,sendBufLen,0); } closesocket(acceptedSocket); closesocket(serverSocket); Main_End: WSACleanup(); system("pause"); return 0;
客户端代码:
//---------------------------- WSAStartup() ----------------------------// WSADATA wsd; int resStartup = WSAStartup(MAKEWORD(2,2),&wsd); if(0 != resStartup) { printf("failed to WSAStartup!\n"); goto Main_End; } //------------------------------------------------------------------------------------// //---------------------------- socket() ----------------------------// SOCKET connSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(INVALID_SOCKET == connSocket) { printf("the socket returned is invalid!\n"); goto Main_End; } //------------------------------------------------------------------------------------// //---------------------------- connect() ----------------------------// //初始化struct sockaddr 结构体 SOCKADDR_IN serverAddr; serverAddr.sin_family = AF_INET; serverAddr.sin_addr.S_un.S_addr = inet_addr(SERVE_ADDRESS); serverAddr.sin_port = htons(SERVE_PORT); memset(serverAddr.sin_zero,0x0,sizeof(serverAddr.sin_zero)); //connect int resConn = connect(connSocket,(sockaddr*)&serverAddr,sizeof(serverAddr)); if(0 != resConn) { printf("failed to connect to server!!\n"); goto Main_End; } //------------------------------------------------------------------------------------// char sendBuffer[256]; char recvBuffer[256]; while(true) { int recvLen = recv(connSocket,recvBuffer,256,0); if(recvLen < 0) { printf("receive error!!\n"); break; } else if(0 == recvLen) { printf("the server close the socket!\n"); } recvBuffer[recvLen] = '\0'; printf("the data recv:%s\n\n\n",recvBuffer); printf("please input what you want to send:\n"); gets(sendBuffer); if(0 == strcmp(sendBuffer,"exit")) { break; } int sendDataLen = strlen(sendBuffer); int nDataSent = send(connSocket,sendBuffer,sendDataLen,0); if(nDataSent != sendDataLen) { printf("failed to send data!!\n"); break; } } closesocket(connSocket); printf("the connection is closed!\n"); Main_End: WSACleanup(); system("pause"); return 0;
客户端连接到服务端后,每次给服务端发送一段内容,服务器在内容前面加上server:再发送给客户端。
当客户端发送的内容是exit时,客户端程序跳出循环,关闭socket断开连接。服务端发现客户端断开连接后也关闭套接字结束程序。
当然上面程序只为了演示最简单的网络编程。有若干漏洞。
1. 服务器只能接受一个客户端连接。当然加一个循环语句进去可以重复地接受客户端的连接,但是仍然是每次只处理一个客户端连接。
2.accept, connect,send,recv函数默认均是阻塞函数。当没有客户连接到服务端时,服务端阻塞在accept函数,无法退出程序。当服务器在接受客户端的数据时,如果客户端不发送数据,也不断开连接,那么服务端阻塞在recv函数,无法退出程序。
改进该程序,使得服务端随时都可以停止服务退出程序,无论有多少个用户已经在连接。
为了多个客户端可以同时连接,最容易理解的便是利用多线程。每一个连接的客户端都用一个线程去处理它的通信。
至于为了随时可以退出服务端,不能再调用永久阻塞的函数了。利用select函数,可以阻塞指定的时间,阻塞期间不占CPU。
int select( __in int nfds, __in_out fd_set*readfds, __in_out fd_set*writefds, __in_out fd_set*exceptfds, __in const struct timeval*timeout);
用于兼容Berkeley sockets.不用理会,随便给个0值就OK。
用于检查是否存在可读socket的的一个socket集合。可为空。
用于检查是否存在可写socket的一个socket集合。可为空。
用于检查是否存在有错误的socket的一个 socket集合,可为空。
TIMEVAL结构体,用于指定该函数阻塞多长时间。
在 调用select时,当readfds不为空时,当readfds中任何一个socket就绪可读时,或者当writefds不为空且writefds中任何一个socket准备就绪可写,或者当exceptfds不为空且任何一个socket发生socket错误时,select就立即返回。否则,直到timeout指定的时间过去以后才返回。
返回值,返回准备就绪的socket的个数。如果为0,说明该函数超时了,如果大于0,说明至少有一个socket就绪。如果小于0,说明发生错误了。
fd_set 是一种集合类型。
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
记录着一个socket数组,以及里面的socket个数。
struct timeval是一个表示等待时间的结构体。
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* and microseconds */
};
tv_sec表示有多少秒,tv_usec表示有多少毫秒。
对于fd_set类型,用到几个宏定义函数。
FD_ZERO(fd_set*), 清空fd_set集合
FD_SET(SOCKET,fd_set*),把socket加入fd_set集合。
FD_ISSET(SOCKET,fd_set*),判断socket是否在集合fd_set中,并且socket准备就绪。
FD_CLR(SOCKET,fd_set*),如果fd_set存在该SOCKET,则移除它。
下面是改进后的服务端代码
typedef struct _ThreadInfo { HANDLE hThread; bool bRunning; SOCKET sock; }ThreadInfo; typedef struct _AcceptThreadParam { bool bRunning; SOCKET listeningSocket; }AcceptThreadParam; std::list<ThreadInfo*> g_threadInfoList; CRITICAL_SECTION g_csForList; DWORD WINAPI ListeningThread(LPVOID lpParameter); DWORD WINAPI CommunicationThread(LPVOID lpParameter); int _tmain(int argc, _TCHAR* argv[]) { _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); // ---------------------------- WSAStartup() ----------------------------// WSADATA wsd; int resStartup = WSAStartup(MAKEWORD(2,2),&wsd); if(0 != resStartup) { printf("failed to WSAStartup!\n"); return -1; } //------------------------------------------------------------------------------// InitializeCriticalSection(&g_csForList); // ---------------------------- socket() ----------------------------// SOCKET serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(INVALID_SOCKET == serverSocket) { printf("failed to invoke socket, the socket returned is invalid!\n"); goto Main_End; } // ------------------------------------------------------------------------------------// //---------------------------- bind() ----------------------------// // 初始化 struct sockaddr 结构体, SOCKADDR_IN就是 struct sockaddr的宏定义 SOCKADDR_IN localAddr; localAddr.sin_family = AF_INET; localAddr.sin_addr.S_un.S_addr = inet_addr(SERVE_ADDRESS); localAddr.sin_port = htons(SERVE_PORT); memset(localAddr.sin_zero,0x0,sizeof(localAddr.sin_zero)); // int resBind = bind(serverSocket,(sockaddr*)&localAddr,sizeof(SOCKADDR_IN)); if(0 != resBind) { printf("failed to bind ! \n"); goto Main_End; } //------------------------------------------------------------------------------------// //---------------------------- listen() ----------------------------// int resListen = listen(serverSocket,5); if(0 != resListen) { printf("failed to listen! \n"); goto Main_End; } //------------------------------------------------------------------------------------// AcceptThreadParam threadParam; threadParam.bRunning = true; threadParam.listeningSocket = serverSocket; HANDLE hListeningThread = CreateThread(0,0,ListeningThread,&threadParam,0,0); if(0 == hListeningThread) { printf("failed to create the listening thread!\n"); goto Main_End; } else { printf("the server is listening now!pass any key to close the server!\n"); } while(true) { char ch = getchar(); threadParam.bRunning = false; DWORD resWait = WaitForSingleObject(hListeningThread,3000); if(WAIT_TIMEOUT == resWait) { printf("failed to wait for the listening thread exiting!\n"); } else { printf("the listening thread has exited!\n"); } break; } Main_End: if(INVALID_SOCKET != serverSocket) { closesocket(serverSocket); serverSocket = INVALID_SOCKET; } WSACleanup(); DeleteCriticalSection(&g_csForList); system("pause"); return 0; } DWORD WINAPI ListeningThread(LPVOID lpParameter) { AcceptThreadParam* pAcceptThreadParam = (AcceptThreadParam*)lpParameter; SOCKET serverSocket = pAcceptThreadParam->listeningSocket; while(pAcceptThreadParam->bRunning) { //---------------------------- accept() ----------------------------// fd_set fdAccept; FD_ZERO(&fdAccept); FD_SET(serverSocket,&fdAccept); TIMEVAL acceptTimeVal; acceptTimeVal.tv_sec = 1; acceptTimeVal.tv_usec = 0; int selRes = select(0,&fdAccept,0,0,&acceptTimeVal); if(selRes > 0) { SOCKADDR_IN clientAddr; int addrLen = sizeof(clientAddr); SOCKET acceptedSocket = accept(serverSocket,(sockaddr*)&clientAddr,&addrLen); if(INVALID_SOCKET == acceptedSocket) { printf("accept error!\n"); break; } printf("a client has connected to the server!\n"); ThreadInfo* pTI = new ThreadInfo; pTI->bRunning = true; pTI->sock = acceptedSocket; pTI->hThread = CreateThread(0,0,CommunicationThread,(LPVOID)pTI,0,0); if(0 == pTI->hThread) { printf("failed to create a thread!\n"); delete pTI; pTI = 0; } else { EnterCriticalSection(&g_csForList); g_threadInfoList.push_back(pTI); LeaveCriticalSection(&g_csForList); } } else if(selRes < 0) { printf("an error has occured when listening !\n"); break; } } std::list<ThreadInfo*> tempList; EnterCriticalSection(&g_csForList); std::list<ThreadInfo*>::iterator listIter; for(listIter = g_threadInfoList.begin(); listIter != g_threadInfoList.end(); listIter++) { (*listIter)->bRunning = false; tempList.push_back(*listIter); } g_threadInfoList.clear(); LeaveCriticalSection(&g_csForList); int nSuccessfullyExit = 0; for(listIter = tempList.begin(); listIter != tempList.end(); listIter++) { DWORD resWait = WaitForSingleObject((*listIter)->hThread,2000); if(WAIT_TIMEOUT == resWait) { printf("failed to wait for a communication thread exiting!\n"); } else { nSuccessfullyExit++; } delete (*listIter); } printf("succeed waiting for %d thread exiting!\n",nSuccessfullyExit); tempList.clear(); printf("listening thread is exiting!\n"); return 0; } DWORD WINAPI CommunicationThread(LPVOID lpParameter) { ThreadInfo* pThreadInfo = (ThreadInfo*)lpParameter; SOCKET clientSocket = pThreadInfo->sock; fd_set fdRead,fdWrite; FD_ZERO(&fdRead); FD_ZERO(&fdWrite); FD_SET(clientSocket,&fdRead); FD_SET(clientSocket,&fdWrite); TIMEVAL sendTimeVal; sendTimeVal.tv_sec = 0; sendTimeVal.tv_usec = 500; int selRes = select(0,0,&fdWrite,0,&sendTimeVal); if(selRes <= 0) { goto ThreadOver; } char recvBuffer[256]; char sendBuffer[256]; strcpy(sendBuffer,"server:Welcome to connect !"); int sendBufLen = strlen(sendBuffer); int resSend = send(clientSocket,sendBuffer,sendBufLen,0); if(resSend != sendBufLen) { printf("there are %d bytes to send, but it just succeeded sending %d bytes!\n",sendBufLen,resSend); goto ThreadOver; } while(pThreadInfo->bRunning) { FD_ZERO(&fdRead); FD_SET(pThreadInfo->sock,&fdRead); TIMEVAL recvTimeVal; recvTimeVal.tv_sec = 0; recvTimeVal.tv_usec = 500; int recvSelRes = select(0,&fdRead,0,0,&recvTimeVal); if(recvSelRes < 0) { printf("socket error when receiving!\n"); break; } else if(recvSelRes > 0) { int recvLen = recv(clientSocket,recvBuffer,sizeof(recvBuffer),0); if(0 == recvLen) { printf("a client close the socket!\n"); break; } else if(recvLen < 0) { printf("an error has happen when recving\n"); break; } else { recvBuffer[recvLen] = '\0'; printf("a client:%s\n",recvBuffer); strcpy(sendBuffer,"server:"); strcat(sendBuffer,recvBuffer); sendBufLen = strlen(sendBuffer); FD_ZERO(&fdWrite); FD_SET(pThreadInfo->sock,&fdWrite); sendTimeVal.tv_sec = 0; sendTimeVal.tv_usec = 500; int sendSelRes = select(0,0,&fdWrite,0,&sendTimeVal); if(sendSelRes > 0) { int bytesSent = send(clientSocket,sendBuffer,sendBufLen,0); if(bytesSent != sendBufLen) { printf("there are %d bytes to be sent,but only %d bytes are sent!\n",sendBufLen,bytesSent); break; } } else { printf("failed to send in 500 ms!\n"); break; } } } } ThreadOver: closesocket(pThreadInfo->sock); bool bMainThreadWaiting = true; EnterCriticalSection(&g_csForList); std::list<ThreadInfo*>::iterator listIter; for(listIter = g_threadInfoList.begin(); listIter != g_threadInfoList.end(); listIter++) { if(pThreadInfo == (*listIter)) { bMainThreadWaiting = false; g_threadInfoList.erase(listIter); break; } } LeaveCriticalSection(&g_csForList); if(false == bMainThreadWaiting) { CloseHandle(pThreadInfo->hThread); delete pThreadInfo; pThreadInfo = 0; } return 0; }
前面的代码与之前的一样,改变的地方在于accept的地方。对于一个监听的socket,如果该socket可读,说明有用户连接过来了。
全局维护了一个纪录创建的线程的信息的链表,每创建一个线程都有一个标识该线程是否应该继续循环执行的bool变量。当bRunning变为false的时候,线程函数跳出循环,返回。
当需要停止服务端运行时,服务端只需要按任何一个键和回车,就会通知线程退出,并且调用WaitForSingleObject(),来确认线程已退出。还有利用了 EnterCriticalSection()和LeaveCriticalSection()临界区函数来保证只有一个线程在操作全局的链表。
使用多线程要消耗一定的资源。对于fd_set,默认最多可以容纳64个socket.所以可以用1个线程去处理64个客户端的连接。而不必每个客户端都创建一个线程。
代码如下:
typedef struct _AcceptThreadParam { bool bRunning; SOCKET listeningSocket; }AcceptThreadParam; #define SOCKET_ARRAY_SIZE 64 SOCKET g_socketArray[SOCKET_ARRAY_SIZE]; int g_socketCount = 0; CRITICAL_SECTION g_csForSocketArray; DWORD WINAPI ListeningThread(LPVOID lpParameter); DWORD WINAPI CommunicationThread(LPVOID lpParameter); int _tmain(int argc, _TCHAR* argv[]) { _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); // ---------------------------- WSAStartup() ----------------------------// WSADATA wsd; int resStartup = WSAStartup(MAKEWORD(2,2),&wsd); if(0 != resStartup) { printf("failed to WSAStartup!\n"); return -1; } //------------------------------------------------------------------------------// InitializeCriticalSection(&g_csForSocketArray); g_socketCount = 0; // ---------------------------- socket() ----------------------------// SOCKET serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(INVALID_SOCKET == serverSocket) { printf("failed to invoke socket, the socket returned is invalid!\n"); goto Main_End; } // ------------------------------------------------------------------------------------// //---------------------------- bind() ----------------------------// // 初始化 struct sockaddr 结构体, SOCKADDR_IN就是 struct sockaddr的宏定义 SOCKADDR_IN localAddr; localAddr.sin_family = AF_INET; localAddr.sin_addr.S_un.S_addr = inet_addr(SERVE_ADDRESS); localAddr.sin_port = htons(SERVE_PORT); memset(localAddr.sin_zero,0x0,sizeof(localAddr.sin_zero)); // int resBind = bind(serverSocket,(sockaddr*)&localAddr,sizeof(SOCKADDR_IN)); if(0 != resBind) { printf("failed to bind ! \n"); goto Main_End; } //------------------------------------------------------------------------------------// //---------------------------- listen() ----------------------------// int resListen = listen(serverSocket,5); if(0 != resListen) { printf("failed to listen! \n"); goto Main_End; } //------------------------------------------------------------------------------------// AcceptThreadParam threadParam; threadParam.bRunning = true; threadParam.listeningSocket = serverSocket; bool bCommunicationThreadRunning = true; HANDLE hListeningThread = CreateThread(0,0,ListeningThread,&threadParam,0,0); HANDLE hCommunicationThread = CreateThread(0,0,CommunicationThread,&bCommunicationThreadRunning,0,0); if(0 == hListeningThread || 0 == hCommunicationThread) { printf("failed to create a thread!\n"); if(0 != hListeningThread) { threadParam.bRunning = false; WaitForSingleObject(hListeningThread,2000); CloseHandle(hListeningThread); } if(0 != hCommunicationThread) { bCommunicationThreadRunning = false; WaitForSingleObject(hCommunicationThread,2000); CloseHandle(hCommunicationThread); } goto Main_End; } else { printf("the server is listening now!pass any key to close the server!\n"); } while(true) { char ch = getchar(); threadParam.bRunning = false; bCommunicationThreadRunning = false; DWORD resWait = WaitForSingleObject(hListeningThread,3000); if(WAIT_TIMEOUT == resWait) { printf("failed to wait for the listening thread exiting!\n"); } else { printf("the listening thread has exited!\n"); } CloseHandle(hListeningThread); resWait = WaitForSingleObject(hCommunicationThread,3000); if(WAIT_TIMEOUT == resWait) { printf("failed to wait for the communication thread exiting!\n"); } else { printf("the communication thread has exited!\n"); } CloseHandle(hCommunicationThread); break; } Main_End: if(INVALID_SOCKET != serverSocket) { closesocket(serverSocket); serverSocket = INVALID_SOCKET; } WSACleanup(); DeleteCriticalSection(&g_csForSocketArray); system("pause"); return 0; } DWORD WINAPI ListeningThread(LPVOID lpParameter) { AcceptThreadParam* pAcceptThreadParam = (AcceptThreadParam*)lpParameter; SOCKET serverSocket = pAcceptThreadParam->listeningSocket; while(pAcceptThreadParam->bRunning) { //---------------------------- accept() ----------------------------// fd_set fdAccept; FD_ZERO(&fdAccept); FD_SET(serverSocket,&fdAccept); TIMEVAL acceptTimeVal; acceptTimeVal.tv_sec = 1; acceptTimeVal.tv_usec = 0; int selRes = select(0,&fdAccept,0,0,&acceptTimeVal); if(selRes > 0) { SOCKADDR_IN clientAddr; int addrLen = sizeof(clientAddr); SOCKET acceptedSocket = accept(serverSocket,(sockaddr*)&clientAddr,&addrLen); if(INVALID_SOCKET == acceptedSocket) { printf("accept error!\n"); break; } printf("a client has connected to the server!\n"); fd_set fdWrite; FD_ZERO(&fdWrite); FD_SET(acceptedSocket,&fdWrite); TIMEVAL writeTimeVal; writeTimeVal.tv_sec = 0; writeTimeVal.tv_usec = 500; int writeSelRes = select(0,0,&fdWrite,0,&writeTimeVal); if(writeSelRes > 0) { int sendBufferLen = strlen("server:Welcome to connect!"); int bytesSent = send(acceptedSocket,"server:Welcome to connect!",sendBufferLen,0); if(bytesSent == sendBufferLen) { EnterCriticalSection(&g_csForSocketArray); if(g_socketCount < 64) { g_socketArray[g_socketCount] = acceptedSocket; g_socketCount++; } else { printf("the server has accepted more than 64 clients!\n"); closesocket(acceptedSocket); } LeaveCriticalSection(&g_csForSocketArray); } else { printf("send error, there are %d bytes to be sent, but only %d bytes are sent!\n",sendBufferLen,bytesSent); closesocket(acceptedSocket); } } else { printf("select error of can not wait for sending data when select!\n"); closesocket(acceptedSocket); } } else if(selRes < 0) { printf("an error has occured when listening !\n"); break; } } printf("listening thread is exiting!\n"); return 0; } DWORD WINAPI CommunicationThread(LPVOID lpParameter) { bool* pBRunning = (bool*)lpParameter; char recvBuffer[256]; char tempBuffer[256]; while(true == *pBRunning) { int currentSocketCount = 0; EnterCriticalSection(&g_csForSocketArray); if(0 == g_socketCount) { LeaveCriticalSection(&g_csForSocketArray); Sleep(200); continue; } currentSocketCount = g_socketCount; LeaveCriticalSection(&g_csForSocketArray); fd_set fdRead; FD_ZERO(&fdRead); for(int i = 0; i < currentSocketCount; i++) { FD_SET(g_socketArray[i],&fdRead); } TIMEVAL readTimeVal; readTimeVal.tv_sec = 1; readTimeVal.tv_usec = 0; int selRes = select(0,&fdRead,0,0,&readTimeVal); if(selRes > 0) { for(int i = 0; i < currentSocketCount; i++) { if(FD_ISSET(g_socketArray[i],&fdRead) != 0) { int bytesRecv = recv(g_socketArray[i],recvBuffer,sizeof(recvBuffer),0); if(bytesRecv > 0) { recvBuffer[bytesRecv] = '\0'; printf("the %d client: %s\n",i + 1,recvBuffer); sprintf(tempBuffer,"the server:%s",recvBuffer); fd_set fdWrite; FD_ZERO(&fdWrite); FD_SET(g_socketArray[i],&fdWrite); TIMEVAL writeTimeVal; writeTimeVal.tv_sec = 0; writeTimeVal.tv_usec = 500; int writeSelRes = select(g_socketArray[i],0,&fdWrite,0,&writeTimeVal); if(writeSelRes > 0) { int sendBufLen = strlen(tempBuffer); int bytesSent = send(g_socketArray[i],tempBuffer,sendBufLen,0); if(bytesSent == sendBufLen) { break; } else { printf("there are %d bytes to be sent,but only %d bytes are sent!\n",sendBufLen,bytesSent); } } else { printf("select error!\n"); } } else if(0 == bytesRecv) { printf("the %d client has closed the socket!\n",i + 1); } else { printf("recv error!\n"); } closesocket(g_socketArray[i]); EnterCriticalSection(&g_csForSocketArray); g_socketArray[i] = g_socketArray[g_socketCount - 1]; g_socketCount--; LeaveCriticalSection(&g_csForSocketArray); } } } else if(selRes < 0) { printf("select error in communication thread!\n"); } } EnterCriticalSection(&g_csForSocketArray); for(int i = 0; i < g_socketCount; i++) { closesocket(g_socketArray[i]); } LeaveCriticalSection(&g_csForSocketArray); printf("the communication thread is exiting!\n"); return 0; }
完成的功能一样。只需要一个线程就可以处理多个客户端了。
还可以用异步IO来实现该服务器,以下是用完成端口来实现同样功能的服务器。
typedef struct _RepeatAcceptingThreadParam { SOCKET listeningSocket; bool* pBRunning; }RepeatAcceptingThreadParam; typedef struct _CompletionPortWorkerThreadParam { HANDLE hCompletionPort; bool* pBRunning; }CompletionPortWorkerThreadParam; #define MESSAGE_BUF_SIZE 1024 enum OPERATION_TYPE { OPERATION_SEND, OPERATION_RECV }; typedef struct { SOCKET sock; WSAOVERLAPPED overlap; WSABUF wsaBuf; char message[1024]; DWORD bytesRecv; DWORD flags; OPERATION_TYPE operationType; }PER_IO_OPERATION_DATA; //global vector, which saves the information of the client sockets connected to the server std::vector<PER_IO_OPERATION_DATA*> g_perIoDataPointerVec; //accept sockets connected to the server's listening socket in a recycle - while DWORD WINAPI RepeatAcceptingThread(LPVOID lpParameter); //the worker thread that deal with the communications between the server and the clients. DWORD WINAPI CompletionPortWorkerThread(LPVOID lpParameter); int _tmain(int argc,_TCHAR* argv[]) { _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); // ---------------------------- WSAStartup() ----------------------------// WSADATA wsd; int resStartup = WSAStartup(MAKEWORD(2,2),&wsd); if(0 != resStartup) { printf("failed to WSAStartup!\n"); return -1; } //------------------------------------------------------------------------------// // ---------------------------- socket() ----------------------------// SOCKET serverSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(INVALID_SOCKET == serverSocket) { printf("failed to invoke socket, the socket returned is invalid!\n"); return -1; } // ------------------------------------------------------------------------------------// //---------------------------- bind() ----------------------------// // 初始化 struct sockaddr 结构体, SOCKADDR_IN就是 struct sockaddr的宏定义 SOCKADDR_IN localAddr; localAddr.sin_family = AF_INET; localAddr.sin_addr.S_un.S_addr = inet_addr(SERVE_ADDRESS); localAddr.sin_port = htons(SERVE_PORT); memset(localAddr.sin_zero,0x0,sizeof(localAddr.sin_zero)); // int resBind = bind(serverSocket,(sockaddr*)&localAddr,sizeof(SOCKADDR_IN)); if(0 != resBind) { printf("failed to bind ! \n"); closesocket(serverSocket); return -1; } //------------------------------------------------------------------------------------// //---------------------------- listen() ----------------------------// int resListen = listen(serverSocket,5); if(0 != resListen) { printf("failed to listen! \n"); closesocket(serverSocket); return -1; } //------------------------------------------------------------------------------------// bool bRepeatAcceptingThreadRunning = true; // a bool variable that take control of terminating the RepeatAcceptingThread. //init the parameter for the RepeatAcceptingThread. RepeatAcceptingThreadParam rtp; rtp.listeningSocket = serverSocket; rtp.pBRunning = &bRepeatAcceptingThreadRunning; HANDLE hRepeatAcceptingThread = CreateThread(0,0,RepeatAcceptingThread,&rtp,0,0); if(0 == hRepeatAcceptingThread) { printf("failed to create the repeat-accepting thread!\n"); closesocket(serverSocket); return -1; } printf("the repeat-accepting thread has run!\n"); while(true) { // pass any key char ch = getchar(); bRepeatAcceptingThreadRunning = false;//to notify the RepeatAcceptingThread to exit safely DWORD waitRes = WaitForSingleObject(hRepeatAcceptingThread,3000); if(WAIT_TIMEOUT == waitRes) { printf("failed to wait for the repeatAcceptingThread exiting!\n"); } else { printf("the repeat accepting thread has exited!\n"); } CloseHandle(hRepeatAcceptingThread); break; } system("pause"); return 0; } DWORD WINAPI RepeatAcceptingThread(LPVOID lpParameter) { //get the parameters passed by the creator of the thread. RepeatAcceptingThreadParam* pParam = (RepeatAcceptingThreadParam*)lpParameter; SOCKET listeningSocket = pParam->listeningSocket; bool* pStillRun = pParam->pBRunning; // create a completion port HANDLE hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE,0,0,0); if(0 == hCompletionPort) { printf("failed to CreateIoCompletionPort!\n"); return -1; } // a bool variable for notifying the worker threads of exiting. bool bWorkThreadRunning = true; // a vector of HANDLEs,which will be used for synchronization of waiting the worker threads to exit. std::vector<HANDLE> threadHandlesVec; SYSTEM_INFO systemInfo; GetSystemInfo(&systemInfo); //the parameter to be passed to the worker thread. CompletionPortWorkerThreadParam cpwtp; cpwtp.pBRunning = &bWorkThreadRunning; cpwtp.hCompletionPort = hCompletionPort; for(int i = 0; i < systemInfo.dwNumberOfProcessors; i++) { HANDLE hThread = CreateThread(0,0,CompletionPortWorkerThread,&cpwtp,0,0); if(0 == hThread) { printf("failed to create a completion port worker thread!\n"); bWorkThreadRunning = false; // terminate all threads created safely. std::vector<HANDLE>::iterator vecIter; for(vecIter = threadHandlesVec.begin(); vecIter != threadHandlesVec.end(); vecIter++) { DWORD waitRes = WaitForSingleObject(*vecIter,2000); if(WAIT_TIMEOUT == waitRes) { printf("failed the wait for the completion port worker thread!\n"); } CloseHandle(*vecIter); } threadHandlesVec.clear(); CloseHandle(hCompletionPort); return -1; } else { threadHandlesVec.push_back(hThread); //add the handle to the vector } } printf("succeed creating completion port worker threads!\n"); while(true == *pStillRun) { fd_set fdAccept; FD_ZERO(&fdAccept); FD_SET(listeningSocket,&fdAccept); TIMEVAL acceptTimeVal; acceptTimeVal.tv_sec = 1; acceptTimeVal.tv_usec = 0; int selRes = select(0,&fdAccept,0,0,&acceptTimeVal); if(selRes > 0) // a client connected { SOCKADDR_IN clientAddr; int addrLen = sizeof(clientAddr); SOCKET acceptedSocket = WSAAccept(listeningSocket,(struct sockaddr*)&clientAddr,&addrLen,0,0); if(0 == acceptedSocket) { printf("failed to accept a connection!\n"); } else { printf("a clent %s:%d has connected!\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port)); PER_IO_OPERATION_DATA* perIoData = new PER_IO_OPERATION_DATA; if(0 == perIoData) { closesocket(acceptedSocket); printf("failed to new a struct! there is not enough memory!\n\n"); } else { //associate the newly connected client socket with the completion port. if(0 == CreateIoCompletionPort((HANDLE)acceptedSocket,hCompletionPort,(ULONG_PTR)perIoData,0)) { printf("failed to associate the newly connected client socket with the completion port!\n"); closesocket(acceptedSocket); delete perIoData; perIoData = 0; } else { //associated successfully, Set the information of the client socket in A PER_IO_OPERATION_DATA struct. //when a IO operation is completed, we can get notified with the struct to be one of the parameters. perIoData->sock = acceptedSocket; perIoData->operationType = OPERATION_SEND; perIoData->wsaBuf.buf = perIoData->message; perIoData->overlap.hEvent = INVALID_HANDLE_VALUE; strcpy(perIoData->message,"Welcome to connect to the server!"); perIoData->wsaBuf.len = strlen(perIoData->message); int sendRes = WSASend(acceptedSocket,&(perIoData->wsaBuf),1,&(perIoData->bytesRecv),0,0,0); if(0 == sendRes) //finished immediately { // asynchronously invoke a receive operation. When the reception finished,we can get its information by // invoking GetQueuedCompletionStatus() perIoData->wsaBuf.buf = perIoData->message; perIoData->wsaBuf.len = MESSAGE_BUF_SIZE; perIoData->flags = 0; perIoData->operationType = OPERATION_RECV; ZeroMemory(&perIoData->overlap,sizeof(perIoData->overlap)); int recvRes = WSARecv(acceptedSocket,&perIoData->wsaBuf,1,&perIoData->bytesRecv,&perIoData->flags,&perIoData->overlap,0); if(0 == recvRes) //the receiving operation finished immediately , the information of the operation has been queued. { g_perIoDataPointerVec.push_back(perIoData); } else if(SOCKET_ERROR == recvRes && WSA_IO_PENDING == WSAGetLastError()) //the receiving operation will finish later { g_perIoDataPointerVec.push_back(perIoData); } else { printf("failed to WSARecv!\n"); closesocket(acceptedSocket); delete perIoData; perIoData = 0; } } else if(SOCKET_ERROR == sendRes && WSA_IO_PENDING == WSAGetLastError()) //the sending operation will finish later { g_perIoDataPointerVec.push_back(perIoData); } else { //int lastErr = WSAGetLastError(); printf("send data error!\n"); closesocket(acceptedSocket); delete perIoData; perIoData = 0; } } } } } else if(selRes < 0) { printf("select error!\n"); } } bWorkThreadRunning = false; //notifies the worker threads of exiting // terminate all threads created safely. std::vector<HANDLE>::iterator vecIter; for(vecIter = threadHandlesVec.begin(); vecIter != threadHandlesVec.end(); vecIter++) { DWORD waitRes = WaitForSingleObject(*vecIter,2000); if(WAIT_TIMEOUT == waitRes) { printf("failed the wait for the completion port worker thread!\n"); } CloseHandle(*vecIter); } threadHandlesVec.clear(); CloseHandle(hCompletionPort); //delete the structs of PER_IO_OPERATION_DATA newed for clients connected. std::vector<PER_IO_OPERATION_DATA*>::iterator pIoDataPointerIter; for(pIoDataPointerIter = g_perIoDataPointerVec.begin(); pIoDataPointerIter != g_perIoDataPointerVec.end(); pIoDataPointerIter++) { closesocket((*pIoDataPointerIter)->sock); delete (*pIoDataPointerIter); *pIoDataPointerIter = 0; } g_perIoDataPointerVec.clear(); printf(" the repeat accepting thread is exiting!\n"); return 0; } bool ReleaseIOOperationData(PER_IO_OPERATION_DATA* & pDataToBeDeleted) { bool retVal = false; std::vector<PER_IO_OPERATION_DATA*>::iterator vecIter; for(vecIter = g_perIoDataPointerVec.begin(); vecIter != g_perIoDataPointerVec.end(); vecIter++) { if(pDataToBeDeleted == (*vecIter)) { g_perIoDataPointerVec.erase(vecIter); closesocket(pDataToBeDeleted->sock); delete pDataToBeDeleted; pDataToBeDeleted = 0; retVal = true; break; } } return retVal; } DWORD WINAPI CompletionPortWorkerThread(LPVOID lpParameter) { CompletionPortWorkerThreadParam* pParam = (CompletionPortWorkerThreadParam*)lpParameter; bool* pStillRun = pParam->pBRunning; HANDLE hCompletionPort = pParam->hCompletionPort; DWORD dwBytesTransfered; PER_IO_OPERATION_DATA* pIoData; WSAOVERLAPPED* pOverlap; while(true == *pStillRun) { dwBytesTransfered = 0; pIoData = 0; pOverlap = 0; BOOL bGetStatus = GetQueuedCompletionStatus(hCompletionPort,&dwBytesTransfered,(PULONG_PTR)&pIoData,&pOverlap,500); if(FALSE == bGetStatus) { if(0 == pOverlap) //did not get a packet from the queue. { continue; } else { //get a packet for a failed I/O operation. } } if(OPERATION_SEND == pIoData->operationType) { if(0 == dwBytesTransfered) //a packet for a failed I/O operation. { printf("the client %d has close the socket!\n",pIoData->sock); ReleaseIOOperationData(pIoData); } else { // receive operation. pIoData->operationType = OPERATION_RECV; pIoData->wsaBuf.buf = pIoData->message; pIoData->wsaBuf.len = MESSAGE_BUF_SIZE; pIoData->flags = 0; ZeroMemory(&pIoData->overlap,sizeof(pIoData->overlap)); int recvRes = WSARecv(pIoData->sock,&pIoData->wsaBuf,1,&pIoData->bytesRecv,&pIoData->flags,&pIoData->overlap,0); if(0 != recvRes && WSA_IO_PENDING != WSAGetLastError()) { printf("recv error, may be the client %d has close the socket!\n",pIoData->sock); ReleaseIOOperationData(pIoData); } } } else if(OPERATION_RECV == pIoData->operationType) { if(0 == dwBytesTransfered) //a packet for a failed I/O operation. { printf("the client %d has close the socket!\n",pIoData->sock); ReleaseIOOperationData(pIoData); } else { // show the data received pIoData->message[dwBytesTransfered] = '\0'; printf("the client %d:%s \n",pIoData->sock,pIoData->message); //send back the data received add a "server:" in the front char tempBuf[MESSAGE_BUF_SIZE]; sprintf(tempBuf,"server:%s",pIoData->message); strcpy(pIoData->message,tempBuf); pIoData->operationType = OPERATION_SEND; pIoData->wsaBuf.buf = pIoData->message; pIoData->wsaBuf.len = strlen(pIoData->message); int sendRes = WSASend(pIoData->sock,&pIoData->wsaBuf,1,&pIoData->bytesRecv,0,0,0); if(0 == sendRes) { pIoData->operationType = OPERATION_RECV; pIoData->wsaBuf.buf = pIoData->message; pIoData->wsaBuf.len = MESSAGE_BUF_SIZE; pIoData->flags = 0; ZeroMemory(&pIoData->overlap,sizeof(pIoData->overlap)); int recvRes = WSARecv(pIoData->sock,&pIoData->wsaBuf,1,&pIoData->bytesRecv,&pIoData->flags,&pIoData->overlap,0); if(0 != recvRes && WSA_IO_PENDING != WSAGetLastError()) { printf("recv error, may be the client %d has close the socket!\n",pIoData->sock); ReleaseIOOperationData(pIoData); } } else if(SOCKET_ERROR == sendRes && WSA_IO_PENDING == WSAGetLastError()) { } else { printf("send error, maybe the client %d has close the socket!\n",pIoData->sock); ReleaseIOOperationData(pIoData); } } } } printf("a completion port thread is exiting!\n"); return 0; }