1. 程式人生 > >Windows Socket 程式設計 伺服器端 可多使用者連線

Windows Socket 程式設計 伺服器端 可多使用者連線

                                          效果圖

一、介紹

      VC6下編譯通過,監聽797埠(自己隨便設定的一個),每當有客戶端連線時就建立一個新執行緒接收這個客戶端傳送的訊息,客戶端離開時程序結束,接收到客戶端的訊息後將訊息轉發給除了傳送者外的所有其他客戶端(實現一個簡單的群聊的功能)。

二、原始碼和註釋

// Server.cpp  
  
#include <iostream>  
#include <cstdio>  
#include <string>  
#include <cstring>  
#include <vector>  
#include <iterator>  
#include <algorithm>  
#include <Winsock2.h>  
#include <Windows.h>  
  
#pragma comment(lib,"ws2_32.lib")

using namespace std;  
HANDLE bufferMutex;     // 令其能互斥成功正常通訊的訊號量控制代碼  
SOCKET sockConn;        // 客戶端的套接字  
vector <SOCKET> clientSocketGroup;  
  
int g_nThreadNum=0;//當前的接收執行緒數 也就是當前連線的客戶數

//自定義的型別 用來存放客戶端的SOCKET唯一編號和客戶端ip地址
struct RecvType{
	SOCKET recvSocket;
	struct  in_addr recvIp;
};

int main()  
{  
// 載入socket動態連結庫(dll)  
    WORD wVersionRequested;  
    WSADATA wsaData;    // 這結構是用於接收Wjndows Socket的結構資訊的  
    wVersionRequested = MAKEWORD( 2, 2 );   // 請求2.2版本的WinSock庫  
    int err = WSAStartup( wVersionRequested, &wsaData );  
    if ( err != 0 ) {  
        return -1;          // 返回值為零的時候是表示成功申請WSAStartup  
    }  
    if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 ) { // 檢測是否2.2版本的socket庫  
        WSACleanup( );  
        return -1;   
    }  
      
// 建立socket操作,建立流式套接字,返回套接字號sockSrv  
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);     
  
// 套接字sockSrv與本地地址相連  
    SOCKADDR_IN addrSrv;  
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 將INADDR_ANY轉換為網路位元組序,呼叫 htonl(long型)或htons(整型)  
    addrSrv.sin_family = AF_INET;  
    addrSrv.sin_port = htons(797);  //這個是埠號,我設定的是797,可以改成自己的(如果使用的是雲伺服器需要在安全策略中增加該埠)
  
    if(SOCKET_ERROR == bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR))){ // 第二引數要強制型別轉換  
        return -1;  
    }  
  
// 將套接字設定為監聽模式(連線請求), listen()通知TCP伺服器準備好接收連線 100表示最大連線數為100  
    listen(sockSrv, 100);  
  
    cout << "伺服器已就緒.\n";  
// accept(),接收連線,等待客戶端連線  
  
    bufferMutex = CreateSemaphore(NULL, 1, 1, NULL);   
  
    DWORD WINAPI SendMessageThread(LPVOID IpParameter);  
    DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter);  
  
	//只建立一次傳送執行緒
    HANDLE sendThread = CreateThread(NULL, 0, SendMessageThread, NULL, 0, NULL);    

	RecvType *recvType;

    while(true){    // 不斷等待客戶端請求的到來  因為是伺服器所以程序會一直執行 所以這裡用while(true)
		recvType=(RecvType*)(malloc(sizeof(RecvType)));//自定義的型別
		SOCKADDR_IN clientsocket;
		int len=sizeof(SOCKADDR);
        sockConn = accept(sockSrv, (SOCKADDR*)&clientsocket,&len);//接收到連線的請求才會繼續之後的程式碼

		//將客戶端的唯一標識和IP地址記錄下來,作為引數傳給接收執行緒,接收執行緒顯示“100.100.0.1 says:hello”
		recvType->recvSocket=sockConn;
		recvType->recvIp =clientsocket.sin_addr;

        if (SOCKET_ERROR != sockConn){  
            clientSocketGroup.push_back(sockConn);  
        }  //如果連線沒有錯誤,將新連線的SOCKET加入到客戶端組的向量中

        //建立一個接收執行緒,將自定義的型別的地址作為引數傳給執行緒
		HANDLE receiveThread = CreateThread(NULL, 0, ReceiveMessageThread, (LPVOID)recvType, 0, NULL);    
        
		WaitForSingleObject(bufferMutex, INFINITE);     //等待互斥體未被佔用後執行之後的程式碼,然後佔用互斥體,防止多個執行緒同時對螢幕緩衝進行讀寫   
        if(NULL == receiveThread) {   
            printf("\nCreatThread AnswerThread() failed.\n");   
        }   
        else{   
			g_nThreadNum++;
            printf("\nCreate Receive Client Thread OK.Current Thread Count:%d\n",g_nThreadNum);   
					
        }   
        ReleaseSemaphore(bufferMutex, 1, NULL);     // 解除資源佔用  
    }  
   
    WaitForSingleObject(sendThread, INFINITE);  // 等待執行緒結束  
    CloseHandle(sendThread);  
    CloseHandle(bufferMutex);  
    WSACleanup();   // 終止對套接字型檔的使用  
    printf("\n");  
    system("pause");  
    return 0;  
}  
  
  
DWORD WINAPI SendMessageThread(LPVOID IpParameter)  //傳送執行緒 只會有一個傳送執行緒存在
{  
    while(1){  
        string talk;  
        getline(cin, talk);  //在伺服器程序直接輸入內容可以發給所有的客戶端(不過不推薦這樣做)
     
		WaitForSingleObject(bufferMutex, INFINITE);     // 螢幕資源未被佔用則執行之後程式碼並佔用螢幕資源    

		//顯示的時候增加一個回車
		if (!talk.empty() && talk!="\n"){
			talk=talk+'\n';
			
			cout <<"Send:"<< talk;  
			for(int i = 0; i < clientSocketGroup.size(); ++i){  //用迴圈遍歷所有線上的客戶端,給他們傳送訊息
		 
				send(clientSocketGroup[i], talk.c_str(), talk.size(), 0);   // 傳送資訊  
			}
		}
        ReleaseSemaphore(bufferMutex, 1, NULL);     // 解除螢幕資源的佔用 
    }  
    return 0;  
}  
  
  
DWORD WINAPI ReceiveMessageThread(LPVOID IpParameter)  //接收執行緒 每當有客戶端連線時都會新建一個接收執行緒
{  
    //SOCKET ClientSocket=(SOCKET)(LPVOID)IpParameter;   

	RecvType *recvType=(RecvType *)(LPVOID)IpParameter;
	SOCKET ClientSocket=recvType->recvSocket;//獲取連線的SOCKET編號
		

    while(1){     //與客戶端的TCP連線正常時一直接收客戶端發來的訊息
        char recvBuf[300];  
        int t=recv(ClientSocket, recvBuf, 200, 0); //接收客戶端發來的訊息 t為接收的長度 
        WaitForSingleObject(bufferMutex, INFINITE);     //螢幕資源未被佔用則執行之後程式碼並佔用螢幕資源
  
        if ( (t==0) || (t==-1) ){  //接收不到客戶端的連線狀態時斷開TCP連線,並且結束執行緒,釋放資源
            vector<SOCKET>::iterator result = find(clientSocketGroup.begin(), clientSocketGroup.end(), ClientSocket);  
            clientSocketGroup.erase(result);  
            closesocket(ClientSocket);  
            ReleaseSemaphore(bufferMutex, 1, NULL);     // 釋放螢幕資源    
            printf("\nAttention:%s has leave...\n",inet_ntoa(recvType->recvIp));
			g_nThreadNum--;//接收執行緒數減1,當前連線數減1
            break;  
        }  
		recvBuf[t]='\0';
  
        printf("%s Says: %s status:%d\n", inet_ntoa(recvType->recvIp), recvBuf,t);     // 輸出接收資訊如“100.100.0.1 say:hello~server”  

		//接收到任何一個客戶端發來的訊息以後 轉發給除了傳送者的其他所有客戶端
		///////////////////////////////////////////
			for(int i = 0; i < clientSocketGroup.size(); ++i){  
		//      send(clientSocketGroup[i], talk.c_str(), talk.size(), 0);   // 傳送資訊  
				if(clientSocketGroup[i]!=ClientSocket)
					send(clientSocketGroup[i], recvBuf, t, 0);   // 傳送資訊  
			}

		/////////////////////////////////////////////
		

        ReleaseSemaphore(bufferMutex, 1, NULL);     //釋放螢幕資源 
    }  
    return 0;  
}

三、測試

      1.使用VC6編譯以上原始碼,並且上傳到伺服器,執行。(伺服器的安全策略需要開797埠)

      2.開啟兩個能建立TCP連線的客戶端(我這裡使用的一個是netcat(nc),另外一個是手機的APP),使這兩個客戶端與伺服器處於能連通的網路內。(可以是區域網,兩個客戶端連在和伺服器同一個交換機或路由器,如果伺服器是在公網下兩個客戶端能上網就行了)

      3.客戶端連線伺服器的IP地址,埠填寫797。

      4.一方傳送資訊,另一方能接收到。


 安卓的具有TCP連線功能的APP客戶端傳送了一個你好tcp(APP原始碼後續會放出)


已經與伺服器建立好TCP連線的NetCat(NC)收到了APP發來的訊息