socket編程,簡單多線程服務端測試程序
前些天重溫了MSDN關於socket編程的WSAStartup、WSACleanup、socket、closesocket、bind、listen、accept、recv、send等函數的介紹,今天寫了一個CUI界面的測試程序(依賴MFC)作為補充。程序功能簡介如下:
1:一個線程做監聽用。
2:監聽線程收到客戶端連接后,創建新線程接收客戶端數據。所有對客戶端線程將加入容器,以便管理。
3:服務端打印所有客戶端發來的信息。
4:服務端CUI界面輸入數字0,將關閉所有連接線程,釋放socket,並退出程序。
程序實現依賴類:
1:MFC
2:CSingleton模板,我關於singleton實現的文章中有源碼。http://www.cnblogs.com/hgwang/p/6085922.html
3:CThreadLockCs,CRITICAL_SECTION封裝類,我關於singleton實現的文章中有源碼。http://www.cnblogs.com/hgwang/p/6085922.html。
4:windows socket庫,我關於windows socket的文章中有介紹及調用方法,http://www.cnblogs.com/hgwang/p/6074038.html。
下面是該測試程序服務端封裝類的源碼:
Listen.h頭文件
1 #pragma once 2 3 #ifndef WHG_LISTEN 4 #define WHG_LISTEN 5 6 #include "Singleton.h" 7 8 class CListen 9 { 10 public: 11 CListen(void); 12 ~CListen(void); 13 //類入口點,會創建監聽線程 14 void SetAddress(unsigned short port,std::string ip=""); 15 //用於線程訪問關閉socket,並為CListen記錄關閉信息 16 void DeleteLink(SOCKET s); 17 private: 18 //線程和CListen類共享的任務信息 19 struct ThreadSocketInfo 20 { 21 CWinThread* pWt; 22 SOCKET* pS; 23 CListen* pListen; 24 ThreadSocketInfo() 25 { 26 pListen = NULL; 27 pWt = NULL; 28 pS = NULL; 29 } 30 ~ThreadSocketInfo() 31 { 32 pListen = NULL; 33 pWt = NULL; 34 pS = NULL; 35 } 36 }; 37 //監聽socket 38 SOCKET listensocket; 39 //監聽線程指針 40 CWinThread* m_pListenThread; 41 //任務信息列表 42 std::vector<ThreadSocketInfo> vec_WorkThreads; 43 //當前監聽ip和端口 44 unsigned short m_port; 45 std::string m_ip; 46 //線程互斥訪問鎖 47 CThreadLockCs m_tlcs; 48 49 private: 50 //監聽線程工作對象 51 static UINT AFX_CDECL ListenThread(LPVOID); 52 //對客戶端線程工作對象 53 static UINT AFX_CDECL WorkThread(LPVOID); 54 //bind、listen、accept實現 55 void Address(unsigned short port,std::string ip=""); 56 }; 57 58 //申明singleton的監聽線程訪問對象,全局唯一實例 59 typedef CSingleton<CListen> LISTEN; 60 61 #endif
Listen.cpp:
1 #include "StdAfx.h" 2 #include "Listen.h" 3 4 CListen::CListen(void) 5 :m_pListenThread(NULL) 6 { 7 listensocket = 0; 8 WSAData wsa; 9 if (WSAStartup(MAKEWORD(1,1),&wsa) != 0) 10 { 11 cout<<"WSAStartup "<<endl; 12 WSACleanup(); 13 } 14 else 15 { 16 listensocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 17 if (listensocket == INVALID_SOCKET) 18 { 19 cout<<"socket() error,error code "<<WSAGetLastError()<<endl; 20 } 21 } 22 } 23 24 CListen::~CListen(void) 25 { 26 //要先關閉對客戶端連接,再關閉監聽socket 27 if (vec_WorkThreads.size() > 0) 28 { 29 std::vector<ThreadSocketInfo>::iterator iter = vec_WorkThreads.begin(); 30 for (;iter!=vec_WorkThreads.end();iter++) 31 { 32 SOCKET* s = (*iter).pS; 33 if (s) 34 { 35 closesocket(*s); 36 delete s; 37 (*iter).pS = NULL; 38 } 39 CWinThread* pwt = (*iter).pWt; 40 if (pwt) 41 { 42 WaitForSingleObject(pwt->m_hThread,INFINITE); 43 delete pwt; 44 (*iter).pWt = NULL; 45 } 46 } 47 } 48 if (listensocket) 49 { 50 closesocket(listensocket); 51 } 52 WSACleanup(); 53 cout<<"WSACleanup "<<endl; 54 55 if (m_pListenThread) 56 { 57 WaitForSingleObject(m_pListenThread->m_hThread,INFINITE); 58 delete m_pListenThread; 59 } 60 vec_WorkThreads.clear(); 61 } 62 63 //監聽線程工作對象 64 UINT CListen::ListenThread(LPVOID param) 65 { 66 CListen* p = (CListen*)param; 67 if (p) 68 { 69 p->Address(p->m_port,p->m_ip); 70 } 71 return 0; 72 } 73 74 //對客戶端線程工作對象 75 UINT CListen::WorkThread(LPVOID param) 76 { 77 ThreadSocketInfo* tsi = (ThreadSocketInfo*)param; 78 SOCKET acp = *(tsi->pS); 79 CListen* pListen = tsi->pListen; 80 delete tsi; 81 82 do 83 { 84 char buf[1024]; 85 int len = recv(acp,buf,1024,0); 86 if (len == 0) 87 { 88 cout<<"connection has been closed "<<endl; 89 break; 90 } 91 else if (len == SOCKET_ERROR) 92 { 93 cout<<"recv error,error code "<<WSAGetLastError()<<endl; 94 break; 95 } 96 else 97 { 98 char* outbuf = new char[len+1]; 99 memcpy(outbuf,buf,len); 100 outbuf[len] = 0; 101 cout<<"recv data,"<<outbuf<<endl; 102 delete outbuf; 103 } 104 } while (1); 105 //刪除當前連接記錄 106 pListen->DeleteLink(acp); 107 return 0; 108 } 109 110 //用於線程訪問關閉socket,並為CListen記錄關閉信息 111 void CListen::DeleteLink(SOCKET s) 112 { 113 m_tlcs.lock(); 114 std::vector<ThreadSocketInfo>::iterator iter = vec_WorkThreads.begin(); 115 for (;iter!=vec_WorkThreads.end();iter++) 116 { 117 SOCKET* ss = (*iter).pS; 118 if (ss && *ss == s) 119 { 120 closesocket(s); 121 delete ss; 122 iter->pS = NULL; 123 } 124 } 125 m_tlcs.unlock(); 126 } 127 128 //類入口點,會創建監聽線程 129 void CListen::SetAddress(unsigned short port,std::string ip) 130 { 131 //監聽線程只允許啟動一次 132 if (m_pListenThread == NULL) 133 { 134 m_ip = ip; 135 m_port = port; 136 m_pListenThread = AfxBeginThread(ListenThread,this,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED,NULL); 137 if (m_pListenThread) 138 { 139 m_pListenThread->m_bAutoDelete = FALSE ; 140 m_pListenThread->ResumeThread(); 141 } 142 } 143 } 144 145 //bind、listen、accept實現 146 void CListen::Address(unsigned short port,std::string ip) 147 { 148 sockaddr_in service; 149 service.sin_family = AF_INET; 150 service.sin_port = htons(port); 151 if (ip.empty()) 152 { 153 service.sin_addr.s_addr = INADDR_ANY; 154 } 155 else 156 { 157 service.sin_addr.s_addr = inet_addr(ip.c_str()); 158 } 159 160 if (bind(listensocket,(sockaddr*)&service,sizeof(service)) == SOCKET_ERROR) 161 { 162 cout<<"bind() error,error code "<<WSAGetLastError()<<endl; 163 return; 164 } 165 cout<<"bind "<<endl; 166 167 if (listen(listensocket,SOMAXCONN) == SOCKET_ERROR) 168 { 169 cout<<"listen() error,error code "<<WSAGetLastError()<<endl; 170 return; 171 } 172 cout<<"listen "<<endl; 173 174 while (1) 175 { 176 sockaddr_in recvLinkAddr; 177 int recvAddr = sizeof(recvLinkAddr); 178 SOCKET acp = accept(listensocket,(sockaddr*)&recvLinkAddr,&recvAddr); 179 if (acp == INVALID_SOCKET) 180 { 181 cout<<"accept error,error code "<<WSAGetLastError()<<endl; 182 return; 183 } 184 cout<<"獲取新的連接請求,ip:"<<inet_ntoa(recvLinkAddr.sin_addr)<<",port:"<<recvLinkAddr.sin_port<<endl; 185 186 SOCKET* s = new SOCKET(acp); 187 ThreadSocketInfo* tsi = new ThreadSocketInfo; 188 tsi->pListen = this; 189 tsi->pS = s; 190 CWinThread* workthread = AfxBeginThread(WorkThread,tsi,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED,NULL); 191 if (workthread) 192 { 193 workthread->m_bAutoDelete = FALSE; 194 workthread->ResumeThread(); 195 tsi->pWt = workthread; 196 ThreadSocketInfo t = *tsi; 197 m_tlcs.lock(); 198 vec_WorkThreads.push_back(t); 199 m_tlcs.unlock(); 200 } 201 } 202 }
客戶端代碼:
1 // WinsockClient.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 int _tmain(int argc, _TCHAR* argv[]) 6 { 7 cout<<"input id:"; 8 std::string str; 9 cin>>str; 10 11 WSAData wsa; 12 if (WSAStartup(MAKEWORD(1,1),&wsa) != 0) 13 { 14 WSACleanup(); 15 return 0; 16 } 17 SOCKET cnetsocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); 18 do 19 { 20 if (cnetsocket == INVALID_SOCKET) 21 break; 22 sockaddr_in server; 23 server.sin_family = AF_INET; 24 server.sin_port = htons(8828); 25 server.sin_addr.s_addr = inet_addr("127.0.0.1"); 26 if (connect(cnetsocket,(sockaddr*)&server,sizeof(server)) == SOCKET_ERROR) 27 { 28 break; 29 } 30 str += " : windows socket test!"; 31 while (1) 32 { 33 int len = send(cnetsocket,str.c_str(),str.length(),0); 34 cout<<"send data:"<<str.c_str()<<" ,length = "<<str.length()<<endl; 35 if (len < str.length()) 36 { 37 cout<<"data send uncompleted"<<endl; 38 str = str.substr(len+1,str.length()); 39 len = send(cnetsocket,str.c_str(),str.length(),0); 40 cout<<"send data uncomplete,send remaining data :"<<str.c_str()<<" ,length = "<<str.length()<<endl; 41 } 42 else if (len == SOCKET_ERROR) 43 { 44 break; 45 } 46 Sleep(5000); 47 } 48 } while (0); 49 closesocket(cnetsocket); 50 WSACleanup(); 51 52 return 1; 53 }
main函數:
1 // MultithreadServer.cpp : Defines the entry point for the console application. 2 // 3 4 #include "stdafx.h" 5 #include "MultithreadServer.h" 6 7 #include "Listen.h" 8 #include "Singleton.h" 9 #ifdef _DEBUG 10 #define new DEBUG_NEW 11 #endif 12 // The one and only application object 13 CWinApp theApp; 14 using namespace std; 15 16 int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) 17 { 18 int nRetCode = 0; 19 20 // initialize MFC and print and error on failure 21 if (!AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0)) 22 { 23 // TODO: change error code to suit your needs 24 _tprintf(_T("Fatal Error: MFC initialization failed\n")); 25 nRetCode = 1; 26 } 27 LISTEN::Instance()->SetAddress(8828,"127.0.0.1"); 28 29 int outId; 30 cin>>outId; 31 if (outId == 0) 32 { 33 LISTEN::Close(); 34 } 35 return nRetCode; 36 }
測試結果:
1:4個客戶端連接
2:客戶端4關閉連接
3:輸入0,關閉整個服務端,自動斷開1.2.3的客戶端
這里面涉及到幾個錯誤代碼,中文說明如下:
1:10054,遠程主機強迫關閉了一個現有的連接。
2:10053,你的主機中的軟件中止了一個已建立的連接。
3: 10004,一個封鎖操作被對 WSACancelBlockingCall 的調用中斷。
至此,程序正常結束。