• C/S模型之TCP群聊


    说明:
    利用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
    ", inet_ntoa(sockAddr.sin_addr), ntohs(sockAddr.sin_port), pSeverBuff);
            
            //显示在窗口
            printf("%s
    ", 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
    ", err);
            return 1;
        }
    
        if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
        {
            printf("Could not find a usable version of Winsock.dll
    ");
            WSACleanup();
            return 1;
        }
        else
            printf("The Winsock 2.2 dll was found okay
    ");
    
        //程序开始
        
        //创建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
    ", WSAGetLastError());
        }
    
        //监听5个
        if (listen(severSocket, 5) == SOCKET_ERROR)
        {
            printf("listen error!%d
    ", WSAGetLastError());
        }
    
        //创建一个一客户端连接的socket
        while (true)
        {    
            //接受来自客户端的sock,并存入客户端的数组中
            SOCKET sockLink = accept(severSocket, NULL, NULL);
            if (sockLink != INVALID_SOCKET)
            {
                printf("communication sucess!
    ");
            }
            
            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
    ", 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
    ", err);
            return 1;
        }
    
        if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) 
        {
            printf("Could not find a usable version of Winsock.dll
    ");
            WSACleanup();
            return 1;
        }
        else
            printf("The Winsock 2.2 dll was found okay
    ");
    
    
        //程序开始
        //创建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!
    ");
        }
    
        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阻塞,直到其他客户端发来消息才会解除,
    该客户端才可以继续发送消息,无法实现一个客户端发送多次消息。

  • 相关阅读:
    对 Excel 工作簿中的数字签名和代码签名的说明
    单例模式
    面向对象
    Des对称加密
    Java获取电脑硬件信息
    鼠标双击事件不可描述的问题
    RSA不对称加密
    JTable表格案例
    控件刷新的奥秘
    反编译插件安装
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/7694172.html
Copyright © 2020-2023  润新知