项目的实现:
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();
这样就形成了一个,客户端给服务端发送消息,服务端接收到消息后,给所有连接的客户端都发送一条消息,而客户端在收到消息后,马上将其显示在窗口中。