首页 > 技术文章 > 基于CSocket的多人通信聊天

Super-biscuits 2021-06-04 08:50 原文

项目的实现:

1.实现多人聊天。

2.服务器程序自动独立运行,每个客户端发送的消息,只要其他连接到服务器的其他客户端均可以收到消息

3.基于TCP网络

首先,服务器的设计:

通过客户端连接服务器,客户端发送消息,由服务端自动转发给所有在线的客户端

代码实现:

创建一个socket对象

 1 #pragma once
 2 
 3 // CClientSocket 命令目标
 4 class CClientSocket : public CSocket
 5 {
 6 public:
 7     CClientSocket();
 8     virtual ~CClientSocket();
 9     virtual void OnReceive(int nErrorCode);
10 };
11 
12 /*
13 * 本类的主要功能就是接收数据,然后将接收到的数据传递给ServerSocket的SendAll函数。
14 */

该对象专门负责接收用户消息

 1 // ClientSocket.cpp : 实现文件
 2 //
 3 
 4 #include "stdafx.h"
 5 #include "Test.h"
 6 #include "ClientSocket.h"
 7 
 8 
 9 // CClientSocket
10 
11 CClientSocket::CClientSocket()
12 {
13 }
14 
15 CClientSocket::~CClientSocket()
16 {
17 }
18 
19 
20 // CClientSocket 成员函数
21 
22 // 当有数据可以接收时,自动触发该函数
23 void CClientSocket::OnReceive(int nErrorCode)
24 {
25     // TODO:  在此添加专用代码和/或调用基类
26     char bufferdata[2048];
27     int len = Receive(bufferdata, 2048);
28     bufferdata[len] = '\0';
29     theApp.m_ServerSock.SendAll(bufferdata, len);
30 
31     CSocket::OnReceive(nErrorCode);
32 }

并且该对象再收到数据之后调用转发

创建第二个socket对象

 1 #pragma once
 2 
 3 // CServerSocket 命令目标
 4 
 5 
 6 class CServerSocket : public CSocket
 7 {
 8 public:
 9     void CServerSocket::DelAll();
10     void SendAll(char *bufferdata, int len);
11     CPtrList  m_socketlist;                                    // 保存socket
12     CServerSocket();
13     virtual ~CServerSocket();
14     virtual void OnAccept(int nErrorCode);
15 };

该对象用于自动接收连接的客户端,并将所有连接到服务端的socket加入List链表中,

并且实现一个函数,给所有加入服务端的客户端发送消息

并且加入清理函数DelAll

 1 // ServerSocket.cpp : 实现文件
 2 //
 3 
 4 #include "stdafx.h"
 5 #include "Test.h"
 6 #include "ServerSocket.h"
 7 #include "ClientSocket.h"
 8 
 9 // CServerSocket
10 
11 CServerSocket::CServerSocket()
12 {
13 }
14 
15 CServerSocket::~CServerSocket()
16 {
17 }
18 
19 
20 // CServerSocket 成员函数
21 
22 
23 // 监听之后,如果有客户端连接,将触发此函数
24 void CServerSocket::OnAccept(int nErrorCode)
25 {
26     // TODO:  在此添加专用代码和/或调用基类
27     CClientSocket* psocket = new CClientSocket();
28     if (Accept(*psocket))
29         m_socketlist.AddTail(psocket);                    // 将连接过来的客户端添加到链表中
30     else
31         delete psocket;
32 
33     CSocket::OnAccept(nErrorCode);
34 
35 }
36 
37 
38 // 通过ClientSocket的OnReceive函数调用该函数,遍历链表,给所有客户端发消息
39 void CServerSocket::SendAll(char *bufferdata, int len)
40 {
41     if (len != -1)
42     {
43         bufferdata[len] = 0;            // 由OnReceive函数传递来的(接收到客户端)消息            
44         POSITION pos = m_socketlist.GetHeadPosition();           // 获取链表的头结构
45         while (pos != NULL)
46         {
47             CClientSocket* socket = (CClientSocket*)m_socketlist.GetNext(pos);                            // 遍历链表
48             if (socket != NULL)
49                 socket->Send(bufferdata, len);                                                    // 将消息向所有客户端发送
50         }
51     }
52 }
53 
54 
55 // 删除所有的socket信息
56 void CServerSocket::DelAll()
57 {
58     POSITION pos = m_socketlist.GetHeadPosition();
59     while (pos != NULL)
60     {
61         CClientSocket* socket = (CClientSocket*)m_socketlist.GetNext(pos);
62         if (socket != NULL)
63             delete socket;
64     }
65     m_socketlist.RemoveAll();
66 }

当有客户端接连的时候,加入链表,当有数据可以接收时,接收数据,并转发给所有的客户端

然后我们在窗口类创建监听

 1 // CTestDlg 对话框
 2 class CTestDlg : public CDialogEx
 3 {
 4 // 构造
 5 public:
 6     
 7 
 8     CTestDlg(CWnd* pParent = NULL);    // 标准构造函数
 9 
10 // 对话框数据
11     enum { IDD = IDD_TEST_DIALOG };
12 
13     protected:
14     virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV 支持
15 
16 
17 // 实现
18 protected:
19     HICON m_hIcon;
20 
21     // 生成的消息映射函数
22     virtual BOOL OnInitDialog();
23     afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
24     afx_msg void OnPaint();
25     afx_msg HCURSOR OnQueryDragIcon();
26     DECLARE_MESSAGE_MAP()
27 public:
28     CIPAddressCtrl m_ip;
29     int m_nServPort;
30     afx_msg void OnBnClickedButton1();
31     afx_msg void OnDestroy();
32 };

我们在窗口中创建了一个IP输入控件,一个端口输入控件,一个按钮

 1 void CTestDlg::OnBnClickedButton1()
 2 {
 3     // TODO:  在此添加控件通知处理程序代码
 4     UpdateData();                            // 更新窗口
 5     
 6     CString strIP;
 7 
 8 
 9     BYTE nf1, nf2, nf3, nf4;
10     m_ip.GetAddress(nf1, nf2, nf3, nf4);                            // 将控件中的ip地址提取出来
11     strIP.Format(_T("%d.%d.%d.%d"), nf1, nf2, nf3, nf4);
12 
13     if (m_nServPort>1024 && !strIP.IsEmpty())
14     {
15         theApp.m_ServerSock.Create(m_nServPort, SOCK_STREAM, strIP);            // 创建socket,该socket在App类中定义
16         BOOL ret = theApp.m_ServerSock.Listen();                            // 监听
17         if (ret)
18             AfxMessageBox(_T("启动成功"));
19     }
20     else AfxMessageBox(_T("信息设置错误"));
21 }
22 
23 void CTestDlg::OnDestroy()
24 {
25     CDialogEx::OnDestroy();
26 
27     // TODO:  在此处添加消息处理程序代码
28     theApp.m_ServerSock.DelAll();                            // 窗口销毁之前,删除所有的socket信息
29 }

通过按钮来创建socket,并进入监听

我们的socket对象的作用域在App类中,这样保证了全局有效性

最后,我们在App类中加入socket库的初始化。

    WSADATA wsd;
    AfxSocketInit(&wsd);

 客户端:

我们建立两个窗口

 

当连接上服务器后我们启动聊天界面

首先创建通信socket

 1 #pragma once
 2 
 3 // CClientSocket 命令目标
 4 #include "DlgChat.h"
 5 class CClientSocket : public CSocket
 6 {
 7 public:
 8     CDlgChat *m_pDlg;
 9     void SetWnd(CDlgChat *pDlg);
10     CClientSocket();
11     virtual ~CClientSocket();
12     virtual void OnReceive(int nErrorCode);
13 };

创建的socket类用于接收服务器发来的消息,并显示在聊天界面中

 1 // ClientSocket.cpp : 实现文件
 2 //
 3 
 4 #include "stdafx.h"
 5 #include "client.h"
 6 #include "ClientSocket.h"
 7 
 8 
 9 // CClientSocket
10 
11 CClientSocket::CClientSocket()
12 {
13     m_pDlg = NULL;
14 }
15 
16 CClientSocket::~CClientSocket()
17 {
18 }
19 
20 
21 // CClientSocket 成员函数
22 void CClientSocket::SetWnd(CDlgChat *pDlg)
23 {
24     m_pDlg = pDlg;
25 }
26 
27 void CClientSocket::OnReceive(int nErrorCode)
28 {
29     // TODO:  在此添加专用代码和/或调用基类
30     if (m_pDlg)
31     {
32         char buffer[2048];
33         CString str;
34         int len = Receive(buffer, 2048);                    // 接收数据保存在buffer中
35         if (len != -1)
36         {
37             buffer[len] = '\0';
38             buffer[len+1] = '\0';
39             str.Format(_T("%s"), buffer);
40             m_pDlg->m_lst.InsertString(0,str);                        // 在当前窗口显示接收到的数据
41         }
42     }
43     CSocket::OnReceive(nErrorCode);
44 }

该类的用途就是等待接收,并将数据显示在窗口中

然后我们创建发送按钮的事件处理函数

 1 void CDlgChat::OnBnClickedButton1()
 2 {
 3     // TODO:  在此添加控件通知处理程序代码
 4     CString  strInfo;
 5     int len;
 6     UpdateData();
 7 
 8     if (m_strSendContent.IsEmpty())
 9         AfxMessageBox(_T("发送内容不能为空"));
10     else 
11     {
12         strInfo.Format(_T("%s说:%s"), theApp.m_strName, m_strSendContent);
13         len = theApp.m_clinetsock.Send(strInfo.GetBuffer(strInfo.GetLength()), 2 * strInfo.GetLength());
14         if (SOCKET_ERROR == len)
15             AfxMessageBox(_T("发送错误"));
16     }
17 }

发送按钮通过获取文本框中的数据,向服务端发送消息

那么我们再来看一下,如何连接客户端:

 1 void CclientDlg::OnBnClickedButton1()
 2 {
 3     // TODO:  在此添加控件通知处理程序代码
 4     CString strIP, strPort;
 5     UINT port;
 6 
 7     UpdateData();
 8     if (m_ip.IsBlank() || m_nServPort < 1024 || m_strNickname.IsEmpty())
 9     {
10         AfxMessageBox(_T("请设置服务器信息"));
11         return;
12     }
13     BYTE nf1, nf2, nf3, nf4;
14     m_ip.GetAddress(nf1, nf2, nf3, nf4);
15     strIP.Format(_T("%d.%d.%d.%d"), nf1, nf2, nf3, nf4);
16     
17     theApp.m_strName = m_strNickname;
18     
19     if (theApp.m_clinetsock.Connect(strIP, m_nServPort))
20     {
21         AfxMessageBox(_T("连接服务器成功!"));
22         CDlgChat dlg;
23         theApp.m_clinetsock.SetWnd(&dlg);
24         dlg.DoModal();
25     }
26     else
27     {
28         AfxMessageBox(_T("连接服务器失败!"));
29     }
30 }

我们在窗口类中连接服务器,并且将窗口句柄传递给socket类,因为socket类在接收到数据后要显示到窗口中,连接成功后,启动消息聊天窗口

socket的创建和socket库的初始化是在App类中完成的

    WSADATA wsd;
    AfxSocketInit(&wsd);
    m_clinetsock.Create();

这样就形成了一个,客户端给服务端发送消息,服务端接收到消息后,给所有连接的客户端都发送一条消息,而客户端在收到消息后,马上将其显示在窗口中。

 

推荐阅读