Windows socket之最简单的socket程序
最簡單的服務器的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));//connectint 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);
nfds
用于兼容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 64SOCKET 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 1024enum 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 portHANDLE 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 receivedpIoData->message[dwBytesTransfered] = '\0';printf("the client %d:%s \n",pIoData->sock,pIoData->message);//send back the data received add a "server:" in the frontchar 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; }posted on 2014-12-16 08:17 NET未來之路 閱讀(...) 評論(...) 編輯 收藏
轉載于:https://www.cnblogs.com/lonelyxmas/p/4166277.html
總結
以上是生活随笔為你收集整理的Windows socket之最简单的socket程序的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: cudaMalloc和cudaMallo
- 下一篇: 对KVC和KVO的理解