2024年5月31日发(作者:)
MFC的网络编程
今天来八一八,MFC的SOCKET 编程,利用CSocket实现一个基于TCP实现一个
QQ聊天程序。你会发现,MFC要比WIN32 简单的多。但是如果你不理解具体API socket
基础知识,你可能会觉得有一点费解。 所以在开始之前 我还是请大家先看看
/lh844386434/article/details/6664025
在应用程序开始的时候,我们先应该初始话winSock 库,所以便会用到下面的一个函数。
[cpp] view plain copy
1. BOOL AfxSocketInit( WSADATA* lpwsaData = NULL ); //用来初始化Socket,用
WSAStartup();来初始化,在应用程序结束时他会自动调用WSACleanup()
我们在开始编程之前,应该调用这个函数,对Socket进行初始化。如果初始化成功返回非
0 ,否则返回0.
可能人会问,这个函数加载的是那个版本的Socket库呢?通过查看底层代码,我们发现,
他加载的是1.1版本的Socket
注意:这个函数只能在你自己应用程序的 CXXWinApp::InitInstance 中初始化.在初始化前
还要记得加入头文件Afxsock.h
我服务器端程序 为 NetChatServer 所以我在的CNetChatServerApp::InitInstance()中加
入
/////////////////////////////////////////////////////////////////////////////////////////////////////CNetChatServerApp::InitI
nstance()///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
[cpp] view plain copy
1. if(!AfxSocketInit())
2. {
3. AfxMessageBox(_T("Socket 库初始化出错!"));
4. return false;
5. }
m_iSocket 是一个 CServerSocket*的 指针 ,CServerSocket类是一个我们自己的类我会
在后面给出相应代码,他继承于CSocket类。
[cpp] view plain copy
1. m_iSocket = new CServerSocket(); // 1.动态创建一个服务器Socket对象。
2. if(!m_iSocket)
3. {
4. AfxMessageBox(_T("动态创建服务器套接字出错!"));
5. return false;
6. }
接着创建套接字
[cpp] view plain copy
1. if(!m_iSocket->Create(8989))
2. {
3. AfxMessageBox(_T("创建套接字错误!"));
4. m_iSocket->Close();
5. return false;
6. }
其中8989 是指定的端口号,但是要注意在保存我们指定的8989端口前,这个端口是空闲
的没有被其他进程所占用,那怎么查看端口是否被其他进程占用呢?
首先打开cmd 键入 netstat -aon
你会看到所有的TCP/UDP 信息 ,但是由于太多了不好查看,所以。我们再在最下面
tasklist|find “8989”
现在我们看到 我们没有找到任何 和8989端口相关的东西,所以说明8989端口没有被占
用。
创建了套接字以后按照win32的步骤我们就应该 对bind端口。
但是MFC 不这样,应为MFC的Create内部已经调用了bind ,如下是MFC的底层代码
[cpp] view plain copy
1. BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,long lEvent, LPC
TSTR lpszSocketAddress)
2. {
3. if (Socket(nSocketType, lEvent))
4. {
5. if (Bind(nSocketPort,lpszSocketAddress))//调用了bind
6. return TRUE;
7. int nResult = GetLastError();
8. Close();
9. WSASetLastError(nResult);
10. }
11. return FALSE;
12. }
所以 我们不用在调用bind 了,直接对套接字进行监听
[cpp] view plain copy
1. if(!m_iSocket->Listen())
2. {
3. AfxMessageBox(_T("监听失败!"));
4. m_iSocket->Close();
5. return false;
6. }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////
然后重载ExitInstance,退出时对进行清理
[cpp] view plain copy
1. int CNetChatServerApp::ExitInstance()
2. {
3. if(m_iSocket)
4. {
5. delete m_iSocket;
6. m_iSocket = NULL;
7. }
8. return CWinApp::ExitInstance();
9. }
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////
下面 来看下CServerSocket的具体实现
[cpp] view plain copy
1. #pragma once
2.
3. #include "ClientSocket.h"
4.
5. class CServerSocket : public CSocket
6. {
7. public:
8. CServerSocket();
9. virtual ~CServerSocket();
10. public :
11. CPtrList m_listSockets;//用来保存服务器与所有客户端连接成功后的
ClientSocket
12.
13.
14. public :
15. virtual void OnAccept(int nErrorCode);
16. };
[cpp] view plain copy
1. #include "stdafx.h"
2. #include "NetChatServer.h"
3. #include "ServerSocket.h"
4.
5. CServerSocket::CServerSocket()
6. {
7.
8. }
9.
10. CServerSocket::~CServerSocket()
11. {
12.
13. }
14.
15. void CServerSocket::OnAccept(int nErrorCode)
16. {
17. //接受到一个连接请求
18. CClientSocket* theClientSock(0);
19. theClientSock = new CClientSocket(&m_listSockets);
20. if(!theClientSock)
21. {
22. AfxMessageBox(_T("内存不足,客户连接服务器失败!"));
23. return;
24. }
25. Accept(*theClientSock);
26. //加入list中便于管理
27. m_l(theClientSock);
28. CSocket::OnAccept(nErrorCode);
29. }
我们可以看到在CServerSocket中 又出现了一个CClientSocket的类,这个类和
CServerSocket一样,也是派生于CSocket类,但是专门用于客户端的Socket。
在这里必须重载OnAccept(int nErrorCode)函数,这样CServerSocket才能接收到客户
端的请求,并且必须在OnAccept中调用Accept()函数对连接请求进行响应。
在OnAccept()我们用一个List 将ClientSocket指针保存,以便以后调用访问。
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////
接着 我们再来看看CClientSocket类
[cpp] view plain copy
1. #pragma once
2.
3. #include "stdafx.h"
4. /////////////////////////////////////////////////
5. ///说明,该类用于和客户端建立通信的Socket
6. /////////////////////////////////////////////////
7.
8. class CClientSocket : public CSocket
9. {
10. public:
11. CClientSocket(CPtrList* pList);
12. virtual ~CClientSocket();
13. public:
14. CPtrList* m_pList;//保存服务器ClientSocket中List的东西,这个是中
CServerSocket中传过来的
15. CString m_strName; //连接名称
16. public:
17. virtual void OnClose(int nErrorCode);
18. virtual void OnReceive(int nErrorCode);
19. void OnLogoIN(char* buff,int nlen);//处理登录消息
20. void OnMSGTranslate(char* buff,int nlen);//转发消息给其他聊天群
21. CString UpdateServerLog();//服务器端更新、记录日志
22. void UpdateAllUser(CString strUserInfo);//更新服务器端的在线人员列表
23. private:
24. BOOL WChar2MByte(LPCWSTR srcBuff, LPSTR destBuff, int nlen);//多字节的转
换
25. };
可以看到 我们重载了OnClose()、OnReceive()函数,这样当套接字关闭、有数据到
达时,就会自动调用这两个函数,我们便可以在这两个函数中响应、处理事件。
由于本人使用的是VS2010,并且采用的Unicode编码,所以,经常要涉及Unicode转多
字节的情况,于是就写了WChar2MByte()进行转换
[cpp] view plain copy
1. #include "stdafx.h"
2. #include "NetChatServer.h"
3. #include "ClientSocket.h"
4. #include "Header.h"
5. #include "NetChatServerDlg.h"
6.
7. CClientSocket::CClientSocket(CPtrList* pList)
8. :m_pList(pList),m_strName(_T(""))
9. {
10.
11. }
12.
13. CClientSocket::~CClientSocket()
14. {
15. }
16.
17. /////////////////////////////////////////////////////////////////////
18. void CClientSocket::OnReceive(int nErrorCode)
19. {
20. //有消息接收
21. //先得到信息头
22. HEADER head;
23. int nlen = sizeof HEADER;
24. char *pHead = NULL;
25. pHead = new char[nlen];
26. if(!pHead)
27. {
28. TRACE0("CClientSocket::OnReceive 内存不足!");
29. return;
30. }
31. memset(pHead,0, sizeof(char)*nlen );
32. Receive(pHead,nlen);
33. = ((LPHEADER)pHead)->type;
34. ntLen = ((LPHEADER)pHead)->nContentLen;
35. delete pHead;
36. pHead = NULL;
37.
38. //再次接收,这次是数据类容
39. pHead = new char[ntLen];
40. if(!pHead)
41. {
42. TRACE0("CClientSocket::OnRecive 内存不足!");
43. return;
44. }
45. if( Receive(pHead, ntLen)!=ntLen)
46. {
47. AfxMessageBox(_T("接收数据有误!"));
48. delete pHead;
49. return;
50. }
51. ////////////根据消息类型,处理数据////////////////////
52. switch()
53. {
54. case MSG_LOGOIN:
55. OnLogoIN(pHead, ntLen);
56. break;
57. case MSG_SEND:
58. OnMSGTranslate(pHead, ntLen);
59. break;
60. default : break;
61. }
62.
63. delete pHead;
64. CSocket::OnReceive(nErrorCode);
65. }
66.
67. //关闭连接
68. void CClientSocket::OnClose(int nErrorCode)
69. {
70. CTime time;
71. time = CTime::GetCurrentTime();
72. CString strTime = ("%Y-%m-%d %H:%M:%S ");
73. strTime = strTime + this->m_strName + _T(" 离开...rn");
74. ((CNetChatServerDlg*)nWnd())->DisplayLog(strTime);
75. m_pList->RemoveAt(m_pList->Find(this));
76. //更改服务器在线名单
77. CString str1 = this->UpdateServerLog();
78. //通知客户端刷新在线名单
79. this->UpdateAllUser(str1);
80. this->Close();
81. //销毁该套接字
82. delete this;
83. CSocket::OnClose(nErrorCode);
84. }
85.
86. //登录
87. void CClientSocket::OnLogoIN(char* buff, int nlen)
88. {
89. //对得接收到的用户信息进行验证
90. //... (为了简化这步省略)
91. //登录成功
92. CTime time;
93. time = CTime::GetCurrentTime();
94. CString strTime = ("%Y-%m-%d %H:%M:%S ");
95.
96. CString strTemp(buff);
97. strTime = strTime + strTemp + _T(" 登录...rn");
98. //记录日志
99. ((CNetChatServerDlg*)nWnd())->DisplayLog(strTime);
100. m_strName = strTemp;
101. //更新服务列表
102. CString str1 = this->UpdateServerLog();
103. //更新在线所有客服端
104. this->UpdateAllUser(str1);
105. }
106.
107. //转发消息
108. void CClientSocket::OnMSGTranslate(char* buff, int nlen)
109. {
110. HEADER head;
111. = MSG_SEND;
112. ntLen = nlen;
113. POSITION ps = m_pList->GetHeadPosition();
114.
115. while(ps!=NULL)
116. {
117. CClientSocket* pTemp = (CClientSocket*)m_pList->GetNext(ps);
118. pTemp->Send(&head,sizeof(HEADER));
119. pTemp->Send(buff, nlen);
120. }
121. }
122.
123.
124. BOOL CClientSocket::WChar2MByte(LPCWSTR srcBuff, LPSTR destBuff, int nlen)
125. {
126. int n = 0;
127. n = WideCharToMultiByte(CP_OEMCP,0, srcBuff, -1, destBuff,0, 0, FALSE
);
128. if(n 129. return FALSE; 130. 131. WideCharToMultiByte(CP_OEMCP, 0, srcBuff, -1, destBuff, nlen, 0, FALSE ); 132. 133. return TRUE; 134. } 135. 136. //跟新所有在线用户 137. void CClientSocket::UpdateAllUser(CString strUserInfo) 138. { 139. HEADER _head; 140. _ = MSG_UPDATE; 141. _ntLen = gth()+1; 142. char *pSend = new char[_ntLen]; 143. memset(pSend, 0, _ntLen*sizeof(char)); 144. if( !WChar2MByte(fer(0), pSend, _ntLen)) 145. { 146. AfxMessageBox(_T("字符转换失败")); 147. delete pSend; 148. return; 149. } 150. POSITION ps = m_pList->GetHeadPosition(); 151. while(ps!=NULL) 152. { 153. CClientSocket* pTemp = (CClientSocket*)m_pList->GetNext(ps); 154. //发送协议头 155. pTemp->Send((char*)&_head, sizeof(_head)); 156. pTemp->Send(pSend,_ntLen ); 157. } 158. 159. delete pSend; 160. 161. } 162. 163. //跟新服务器在线名单 164. // 返回在线用户列表的String 165. CString CClientSocket::UpdateServerLog() 166. { 167. CString strUserInfo = _T(""); 168. 169. POSITION ps = m_pList->GetHeadPosition(); 170. 171. while(ps!=NULL) 172. { 173. CClientSocket* pTemp = (CClientSocket*)m_pList->GetNext(ps); 174. strUserInfo += pTemp->m_strName + _T("#"); 175. } 176. ((CNetChatServerDlg*)nWnd())->UpdateUserInfo(strUserInfo); 177. 178. return strUserInfo; 179. } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////// 在上面的代码中 还涉及到一个HEADER struct 这是一个我们自定义的一个头结构,相当 于自定义的一个协议,不过这个很简化。在这个协议里我们要指定我们本次要发送数据 的type,既是我们发送的那种消息的数据。还有数据的长度。为了不浪费空间,我们选择2 次发送。每次给服务器发数据时都 先发送一个协议头,然后再发送数据本身。 其实也可以既不浪费空间,也只发送一次。但是那就在发送之前,对数据进行序列化。在接 收端接收到数据后又反序列化。但是C++中并没有提供相应的方法,所以我们要么自己写, 要么用第三方的库类。但是这种方法代价比,我们分两次发送代价高得多,所以为了方便我 们就分2次发送。 ////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////定义协议头 因为直接要传输的类容中有不确定长的的类容 ///为了避免浪费空间选择分两部分传输,故定义一个头 //////////////////////////////////////////////////////////////////////////////////////////////////////////// #pragma once ////////////自定义协议/////////////////// const int MSG_LOGOIN = 0x01; //登录 const int MSG_SEND = 0x11; //发送消息 const int MSG_CLOSE = 0x02; //退出 const int MSG_UPDATE = 0x21; //更新信息 #pragma pack(push,1) typedef struct tagHeader{ int type ;//协议类型 int nContentLen; //将要发送内容的长度 }HEADER ,*LPHEADER; #pragma pack(pop) 这里面涉及了一个字节对齐的知识,请查 看 /lh844386434/article/details/6680549 ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////// 到这里基本服务器端基本有关发送的框架全部搭建完毕,剩下的就是一些界面编程,比如什 么显示之类的工作,在这里我就不贴这些代码,但是呢我会在最后给出整个工程的下载地址, 下面我们就简单看看客户端的代码 ////////////////////////////////////////////////////////客户端////////////////////////////////////////////////////////////// 客户端相对来说要简单的多,他只涉及一个CClientSocket,但是呢,这个类并不是和服务 器端那个一样的,只是名字相同而已。 首先还是要初始化socket库 不多说。位置和添加方法和客户端一样、接着创建客户端的套 接字、然后连接服务器。 [cpp] view plain copy 1. if(!AfxSocketInit()) 2. { 3. AfxMessageBox(_T("初始化Socket库失败!")); 4. return false; 5. } 6. 7. m_pSocket = new CClientSocket(); 8. if(!m_pSocket) 9. { 10. AfxMessageBox(_T("内存不足!")); 11. return false; 12. } 13. 14. if(!m_pSocket->Create()) 15. { 16. AfxMessageBox(_T("创建套接字失败!")); 17. return false; 18. } 19. 20. CLogoInDlg* pLogoinDlg;//登录对话框 21. pLogoinDlg = new CLogoInDlg(); 22. 23. if(pLogoinDlg->DoModal()==IDOK)//这里其实是点击了推出的按钮,只是ID我用的是 IDOK的,没有修改 24. { 25. //不登录 26. delete pLogoinDlg; 27. m_pSocket->Close(); 28. return false; 29. } 30. else 31. { 32. delete pLogoinDlg; 33. } (上面还有一个CLogoInDlg类,那是一个登录对话框的类,在后面会给出他的部分代码。) 接着和服务器端一样,重载ExitInstance(); [cpp] view plain copy 1. int CNetChatClientApp::ExitInstance() 2. { 3. if(m_pSocket) 4. { 5. delete m_pSocket; 6. m_pSocket = NULL; 7. } 8. 9. return CWinApp::ExitInstance(); 10. } 11. 12. CClientSocket* CNetChatClientApp::GetMainSocket() const 13. { 14. return m_pSocket; 15. } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////// 然后 看看客户端的CClientSocket的实现 [cpp] view plain copy 1. #pragma once 2. 3. class CClientSocket : public CSocket 4. { 5. public: 6. CClientSocket(); 7. virtual ~CClientSocket(); 8. public: 9. virtual void OnReceive(int nErrorCode);//客户端接收消息 10. BOOL SendMSG(LPSTR lpBuff, int nlen);//客户端发送消息 11. BOOL LogoIn(LPSTR lpBuff, int nlen);//客户端登录 12. CString m_strUserName;//用户姓名 13. }; 显然我们必须重载OnReceive函数,来处理接收到的数据,其他函数是一些事件处理函数, 和说明一样 [cpp] view plain copy 1. #include "stdafx.h" 2. #include "NetChatClient.h" 3. #include "ClientSocket.h" 4. #include "Header.h" 5. #include "NetChatClientDlg.h" 6. // CClientSocket 7. 8. CClientSocket::CClientSocket() 9. :m_strUserName(_T("")) 10. { 11. 12. } 13. 14. CClientSocket::~CClientSocket() 15. { 16. } 17. 18. 19. void CClientSocket::OnReceive(int nErrorCode) 20. { 21. //首先接受head头 22. HEADER head ; 23. char* pHead = NULL; 24. pHead = new char[sizeof(head)]; 25. memset(pHead, 0, sizeof(head)); 26. Receive(pHead, sizeof(head)); 27. 28. =((LPHEADER)pHead)->type; 29. ntLen = ((LPHEADER)pHead)->nContentLen; 30. delete pHead; 31. pHead = NULL; 32. 33. char* pBuff = NULL; 34. pBuff = new char[ntLen]; 35. if(!pBuff) 36. { 37. AfxMessageBox(_T("内存不足!")); 38. return; 39. } 40. memset(pBuff, 0 , sizeof(char)*ntLen); 41. if(ntLen!=Receive(pBuff, ntLen)) 42. { 43. AfxMessageBox(_T("收到数据有误!")); 44. delete pBuff; 45. return; 46. } 47. CString strText(pBuff); 48. switch() 49. { 50. case MSG_UPDATE: 51. { 52. CString strText(pBuff); 53. ((CNetChatClientDlg*)(AfxGetApp()->GetMainWnd()))->UpdateUserInf o(strText); 54. } 55. break; 56. case MSG_SEND: 57. { 58. //显示接收到的消息 59. CString str(pBuff); 60. ((CNetChatClientDlg*)(AfxGetApp()->GetMainWnd()))->UpdateText(st r); 61. break; 62. } 63. default: break; 64. } 65. 66. delete pBuff; 67. CSocket::OnReceive(nErrorCode); 68. } 69. 70. BOOL CClientSocket::SendMSG(LPSTR lpBuff, int nlen) 71. { 72. //生成协议头 73. HEADER head; 74. = MSG_SEND; 75. ntLen = nlen; 76. 77. if(Send(&head, sizeof(HEADER))==SOCKET_ERROR) 78. { 79. AfxMessageBox(_T("发送错误!")); 80. return FALSE; 81. }; 82. if(Send(lpBuff, nlen)==SOCKET_ERROR) 83. { 84. AfxMessageBox(_T("发送错误!")); 85. return FALSE; 86. }; 87. 88. return TRUE; 89. } 90. 91. BOOL CClientSocket::LogoIn(LPSTR lpBuff, int nlen) 92. { 93. HEADER _head; 94. _ = MSG_LOGOIN; 95. _ntLen = nlen; 96. int _nSnd= 0; 97. if((_nSnd = Send((char*)&_head, sizeof(_head)))==SOCKET_ERROR) 98. return false; 99. if((_nSnd = Send(lpBuff, nlen))==SOCKET_ERROR) 100. return false; 101. 102. return TRUE; 103. } ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ////// 最后来看看 登录、和发送消息的部分代码,也是和服务器端一样,分成两步分发送,先发 协议头,再发内容 [cpp] view plain copy 1. void CLogoInDlg::OnBnClickedBtnLogoin() 2. { 3. //登录 4. UpdateData(); 5. if(m_y()) 6. { 7. AfxMessageBox(_T("用户名不能为空!")); 8. return; 9. } 10. 11. if(m_dwIP==0) 12. { 13. AfxMessageBox(_T("无效IP地址")); 14. return; 15. } 16. 17. CClientSocket* pSock = nSocket(); 18. IN_ADDR addr ; 19. addr.S_un.S_addr = htonl(m_dwIP); 20. CString strIP(inet_ntoa(addr)); 21. if(!pSock->Connect(fer(0),8989)) 22. { 23. AfxMessageBox(_T("连接服务器失败!")); 24. return ; 25. } 26. //发送 27. pSock->m_strUserName = m_strUser; 28. char* pBuff = new char[m_gth()+1]; 29. memset(pBuff, 0, m_gth()); 30. if(WChar2MByte(m_fer(0), pBuff, m_gth()+1)) 31. pSock->LogoIn(pBuff, m_gth()+1); 32. delete pBuff; 33. CDialogEx::OnCancel(); 34. } 35. 36. 37. void CLogoInDlg::OnBnClickedOk() 38. { 39. //退出 40. CClientSocket* pSock = nSocket(); 41. pSock->Close(); 42. CDialogEx::OnOK(); 43. } ///消息发送 [cpp] view plain copy 1. void CNetChatClientDlg::OnBnClickedBtnSend() 2. { 3. //发送消息 4. UpdateData(); 5. if(m_y()) 6. { 7. AfxMessageBox(_T("发送类容不能为空!")); 8. return ; 9. } 10. 11. CString temp ; 12. CTime time = CTime::GetCurrentTime(); 13. temp = ("%H:%M:%S"); 14. //姓名 +_T("nt") 时间 15. m_strSend = nSocket()->m_strUserName+_T(" ") + temp +_T(" rn ") + m_strSend +_T("rn"); 16. 17. char* pBuff = new char[m_gth()*2]; 18. memset(pBuff, 0, m_gth()*2); 19. //转换为多字节 20. WChar2MByte(m_fer(0), pBuff, m_gth()*2); 21. // 22. nSocket()->SendMSG(pBuff, m_gth()*2); 23. 24. delete pBuff; 25. 26. m_(); 27. UpdateData(0); 28. 29. } [cpp] view plain copy 1. void CNetChatClientDlg::UpdateUserInfo(CString strInfo) 2. { 3. CString strTmp; 4. CListBox* pBox = (CListBox*)GetDlgItem(IDC_LB_ONLINE); 5. pBox->ResetContent(); 6. while(!y()) 7. { 8. int n = ('#'); 9. if(n==-1) 10. break; 11. strTmp = (n); 12. pBox->AddString(strTmp); 13. strInfo = (gth()-n-1); 14. } 15. } 16. 17. void CNetChatClientDlg::UpdateText(CString &strText) 18. { 19. ((CEdit*)GetDlgItem(IDC_ET_TEXT))->ReplaceSel(strText); 20. } /////////////////////////////////////////以上都是部分代码,我会在后面给出工程下载地址 ////////////////////////////////////////// 结束语: 我们简单的过了一下windows的网络编程,由于本人水平有限,又是刚开始学着 写博客,所以其中错误难免。请大家见谅。其实上面的代码只是实现了,群聊天室。 并没有实现1对1的类似于QQ那种聊天,但是做到这一步,要实现QQ那种聊天那种应 该很简单了,加一点代码就可以了。还有 由于最近时间比较紧我没有去写界面。那样的话 又会添加更多的代码,但是我会在VC++重温笔记中,重温界面编程时,实现一个QQ2011 里面那种界面效果,在这里就不花时间了。 截图 登录: 服务器记录日志: 两个用户聊天: 说明:本程序在vs2010+win7 X64中通过 工程下载地址:/source/3513082


发布评论