1. 程式人生 > >C/S模型之TCP群聊

C/S模型之TCP群聊

cpp etl word client inet_addr accep 應用程序 with value

說明:
利用TCP協議和多線程實現群聊功能。一個服務器,多個客戶端(同一個程序多次啟動)。客戶端向服務端發送數據,由服務端進行轉發到其他
客戶端。

/服務端
// WSASever.cpp : 定義控制臺應用程序的入口點。
//
#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#include <vector>
#pragma comment (lib,"wSock32.lib")

SOCKET sockLink;
SOCKET g_psockSockLink[1024] = {0};    //存放客戶端的sock
int g_nSocketNum=0; //記錄客戶端sock的數目 //多線程進行接受和轉發 DWORD WINAPI SeverThread(LPVOID lParam) { int nErr = 0; char pSeverBuff[MAXBYTE] = { 0 }; //接受客戶端的數據 char pSendBuff[MAXBYTE] = { 0 }; //顯示在窗口,包括來自哪個IP地址,端口號,數據 SOCKET sockLink = (SOCKET)lParam; //當前的客戶端sock SOCKADDR_IN sockAddr;
int len = sizeof(SOCKADDR_IN); while (TRUE) { //接受客戶端 nErr = recv(sockLink,pSeverBuff, MAXBYTE, 0); if (nErr == SOCKET_ERROR) { break; return -1; } //根據sock獲取sock地址 getpeername(sockLink, (sockaddr*)&sockAddr, &len);
//將Ip、端口號、數據存入pSendBuff sprintf_s(pSendBuff,"%s(%d):%s\n", inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port), pSeverBuff); //顯示在窗口 printf("%s\n", pSendBuff); //轉發 for (int i = 0;i<g_nSocketNum;++i) { //不為當前發送方的sock if (g_psockSockLink[i] != sockLink) { send(g_psockSockLink[i], pSendBuff, MAXBYTE, 0); } } } //當客戶端關閉時,服務端也隨之關閉 //if (nErr == INVALID_SOCKET) //return-1; return 0; } int _tmain(int argc, _TCHAR* argv[]) { //版本檢測 WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { printf("WSAStartup failed with error: %d\n", err); return 1; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { printf("Could not find a usable version of Winsock.dll\n"); WSACleanup(); return 1; } else printf("The Winsock 2.2 dll was found okay\n"); //程序開始 //創建socket->bind-》listen->accept->recv->send->closesocket SOCKET severSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (severSocket == INVALID_SOCKET) { printf("new socket error!"); } //設置端口號和IP地址、協議。 SOCKADDR_IN sockAddr; sockAddr.sin_port = htons(10086); sockAddr.sin_family = AF_INET; sockAddr.sin_addr.s_addr = htonl (INADDR_ANY); //IP地址表示方法 /*方法1:m_addr.sin_addr.S_un.S_un_b.s_b1 = 192; m_addr.sin_addr.S_un.S_un_b.s_b2 = 168; m_addr.sin_addr.S_un.S_un_b.s_b3 = 0; m_addr.sin_addr.S_un.S_un_b.s_b4 = 1; 方法2: m_addr.sin_addr.S_un.S_un_w.s_w1 = (168 << 8) | 192; m_addr.sin_addr.S_un.S_un_w.s_w2 = (1 << 8) | 0; 方法3: m_addr.sin_addr.S_un.S_addr = (1 << 24) | (0 << 16) | (168 << 8) | 192 方法4; service.sin_addr.s_addr = inet_addr("127.0.0.1"); */ /*sockAddr.sin_addr.S_un.S_un_b.s_b1 = 127; sockAddr.sin_addr.S_un.S_un_b.s_b2 = 0; sockAddr.sin_addr.S_un.S_un_b.s_b3 = 0; sockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;*/ //綁定 if (bind(severSocket, (sockaddr*)&sockAddr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR) { printf("bind error! %d\n", WSAGetLastError()); } //監聽5個 if (listen(severSocket, 5) == SOCKET_ERROR) { printf("listen error!%d\n", WSAGetLastError()); } //創建一個一客戶端連接的socket while (true) { //接受來自客戶端的sock,並存入客戶端的數組中 SOCKET sockLink = accept(severSocket, NULL, NULL); if (sockLink != INVALID_SOCKET) { printf("communication sucess!\n"); } g_psockSockLink[g_nSocketNum++] = sockLink; //每啟動一個客戶端,啟動一條線程 HANDLE hThread = CreateThread(NULL, 0, SeverThread, (LPVOID)sockLink, 0, NULL); //CloseHandle(hThread); if (hThread == NULL) continue; } closesocket(severSocket); closesocket(sockLink); WSACleanup(); return 0; }

//客戶端
// WASClient.cpp : 定義控制臺應用程序的入口點。
//

//#include <WinSock2.h>一定要在#include <Windows.h>前面

#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#pragma comment (lib,"wSock32.lib")


//用於來自接受服務器的數據,避免的send中造成阻塞。
DWORD WINAPI RectThread(LPVOID lParam)
{
    SOCKET sockLink = (SOCKET)lParam;
    char pReturnValue[MAXBYTE] = { 0 };
    while (true)
    {
        recv(sockLink, pReturnValue, MAXBYTE, 0);
        printf("%s\n", pReturnValue);
    }
    return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
    WORD wVersionRequested;
    WSADATA wsaData;
    int err;
    
    //版本檢測
    wVersionRequested = MAKEWORD(2, 2);

    err = WSAStartup(wVersionRequested, &wsaData);
    if (err != 0) {

        printf("WSAStartup failed with error: %d\n", err);
        return 1;
    }

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
    {
        printf("Could not find a usable version of Winsock.dll\n");
        WSACleanup();
        return 1;
    }
    else
        printf("The Winsock 2.2 dll was found okay\n");


    //程序開始
    //創建socket-》連接connect-》發送send-》接受recv-》釋放closesocke

    SOCKET clientSocket=socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (clientSocket == INVALID_SOCKET)
    {
        printf("new socket error!");
    }

    SOCKADDR_IN sockAddr;
    //一定要把主機字節序換成網絡字節序 並是short類型   htons()
    sockAddr.sin_port = htons(10086);
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    

    //IP地址表示方法
    /*方法1:  m_addr.sin_addr.S_un.S_un_b.s_b1 = 192; m_addr.sin_addr.S_un.S_un_b.s_b2 = 168; m_addr.sin_addr.S_un.S_un_b.s_b3 = 0; m_addr.sin_addr.S_un.S_un_b.s_b4 = 1; 
    方法2:  m_addr.sin_addr.S_un.S_un_w.s_w1 = (168 << 8) | 192; m_addr.sin_addr.S_un.S_un_w.s_w2 = (1 << 8) | 0; 
    方法3:  m_addr.sin_addr.S_un.S_addr = (1 << 24) | (0 << 16) | (168 << 8) | 192*/

    /*sockAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
    sockAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
    sockAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
    sockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;
    */
    
    //連接
    if (connect(clientSocket, (sockaddr*)&sockAddr, sizeof(SOCKADDR_IN)) != SOCKET_ERROR)
    {
        printf("communication sucess!\n");
    }

    char pClientBuf[MAXBYTE] = { 0 };        //存放輸入數據

    //啟動線程
    HANDLE hThread = CreateThread(NULL, 0, RectThread, (LPVOID)clientSocket, 0, NULL);
    if (hThread == NULL)
    {
        printf("CreateThread Error num:%d", GetLastError());
        CloseHandle(hThread);
    }
    CloseHandle(hThread);


    //請求連接,發送數據
    while (TRUE)
    {
        gets_s(pClientBuf);
        int  nSendErr=send(clientSocket, pClientBuf, MAXBYTE, 0);
        if (nSendErr== SOCKET_ERROR)
        {
            break;
        }
    }

    WSACleanup();
    closesocket(clientSocket);

    return 0;
}

註意點:
1.#include <WinSock2.h>一定要在#include <Windows.h>前面
如:
#include <WinSock2.h>
#include <Windows.h>

2.設定端口號時,一定要把主機字節序換成網絡字節序 並是short類型 htons()
sockAddr.sin_port = htons(10086);

3.網絡連接的流程:
服務端:創建socket->綁定bind->監聽listen->接受客戶端的套接字accept->接收recv->發送send->釋放closesocket
客戶端://創建socket-》連接connect-》發送send-》接受recv-》釋放closesocke

4.getpeername(sockLink, (sockaddr*)&sockAddr, &len);
該函數可以根據當前的sock獲取對象的sock地址,從而獲取對應的IP地址、端口號,協議。

5.IP地址表示方法
方法1: m_addr.sin_addr.S_un.S_un_b.s_b1 = 192;
m_addr.sin_addr.S_un.S_un_b.s_b2 = 168;
m_addr.sin_addr.S_un.S_un_b.s_b3 = 0;
m_addr.sin_addr.S_un.S_un_b.s_b4 = 1;
方法2; service.sin_addr.s_addr = inet_addr("192.168.0.1");
方法3: m_addr.sin_addr.S_un.S_un_w.s_w1 = (168 << 8) | 192; m_addr.sin_addr.S_un.S_un_w.s_w2 = (1 << 8) | 0;
方法4: m_addr.sin_addr.S_un.S_addr = (1 << 24) | (0 << 16) | (168 << 8) | 192

6.SOCKET sockLink = accept(severSocket, NULL, NULL);
accept返回的是一個新的sock,該sock可以與客戶端進行連接。就好比服務端與客戶端建立一條管道,兩者間隨時可以進行通信。sockLink與clientSocket
是一對組合。因此不同的客戶端啟動,將會有不同的sock接入服務端。

7.
問題:客戶端為什麽專門啟動一條線程來接受消息?
解析:首先,該程序是群聊功能,無法確定別人的客戶端什麽時候回發送消息過來。
其次,如何將send和recv寫在同一個while中,當send發送消息後,如果別人客戶端沒有消息進來,此時就在recv阻塞,直到其他客戶端發來消息才會解除,
該客戶端才可以繼續發送消息,無法實現一個客戶端發送多次消息。

C/S模型之TCP群聊