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\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阻塞,直到其他客户端发来消息才会解除,
该客户端才可以继续发送消息,无法实现一个客户端发送多次消息。

 

posted @ 2017-10-19 18:46  gd_沐辰  阅读(647)  评论(0编辑  收藏  举报