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