一、CAsyncSocket类
CAsyncSocket属于异步非阻塞类。
CAsyncSocket类采用了windows socket中的WSAAsyncSelect模型。CAsyncSocket 类是在很低的层次上对windows socket API进行了封装,它的成员函数和winsock API的函数调用直接对应,一个CAsyncSocket对象代表了一个windows套接字,它是网络通信的端点。该类将根据不同的windows套接字消息调用CAsyncSocket类的回调函数。
通知侦听套接字,它可以通过调用Accept,接受挂起连接请求 |
||
通知套接字,关闭对它的套接字连接 |
||
通知连接套接字,连接尝试已经完成,无论成功或失败 |
||
通知接收套接字,在套接字上有带外数据读入,通常是忙消息 |
||
通知侦听套接字,通过调用Receive恢复数据 |
||
通知套接字,通过调用Send,它可以发送数据 |
网络应用程序一般采用客户端/服务器模式,他们使用的CAsyncSocket编程有所不同,下面以表格的形式方式看一下服务器和客户端之间的不同
序号 | 服务端 | 客户端 |
1 |
构造一个套接字 CAsyncSocket sockServer |
构造一个套接字 CAsyncSocket sockClient |
2 |
创建SOCKET句柄,绑定到指定的端口 sockServer.Create(nPort); |
创建SOCKET句柄,使用默认参数 sockClient.Create(); |
3 |
启动监听,时刻准备接收连接请求 sockServer.Listen(); |
|
4 |
请求链接服务器 sockClient.Connect(strAddress,nPort) |
|
5 |
构造一个新的空套接字 CAsyncSocket sockRecv; 接收连接 sockServer.Accept(sockRecv); |
|
6 |
接收数据 sockRecv.Receive(pBuffer,nLen); |
发送连接 sockClient.Send(pBuffer,nLen); |
7 |
发送数据 sockRecv.Send(pBuffer,nLen); |
接收数据 sockClient.Receive(pBuffer,nLen); |
8 |
关闭套接字对象 sockRecv.Close(); |
关闭套接字对象 sockClient.Close(); |
ps:客户端与服务端都要首先构造一个CAsyncSocket对象,然后使用该对象的Create成员函数来创建底层的SOCKET句柄。服务器端要绑定到特定的端口
对于服务器端的套接字对象,应使用CAsyncSocket::Listen函数进行监听状态,一旦收到来自客户端的链接请求,就调用CAsyncSocket::Accept来接收。对于客户端的套接字对象,应当使用CAsyncSocket::Connect来连接到一个服务器端的套接字对象。建立链接之后,双方就可以按照应用层协议交换数据了。
这里需要注意,Accept是将一个新的空CAsyncSocket对象作为它的参数,在调用Accept之前必须构造这个对象。与客户端套接字的连接是通过它建立的,如果这个套接字对象退出,连接也就关闭。对于这个新的套接字对象,不需要调用Create来创建它的底层套接字
调用CAsyncSocket对象的其他成员函数,如Send和Receive执行与其他套接字对象的通信,这些成员函数与Windows Sockets API函数在形式和用法上基本是一致的。
关闭并销毁CAsyncSocket对象。如果在堆栈上创建了套接字对象,当包含此对象的函数退出时,会调用该类的析构函数,销毁该对象。在销毁该对象之前,析构函数会调用该对象的Close成员函数。如果在堆上使用new创建了套接字对象,可先调用Close成员函数关闭它,在使用delete来删除释放该对象。
CAsyncSocket编程注意问题:
a) 阻塞处理。CAsyncSocket对象专用于异步操作,不支持阻塞工作模式,如果应用程序需要支持阻塞操作,必须自己解决。
b) 字节顺序的转换。在不同的结构类型的计算机之间进行数据传输时,可能会有计算机之间字节存储顺序不一致的情况,需要自己对不用的字节顺序进行转换。
c) 字符串转换。同样不同结构类型的计算机的字符串顺序也可能不同,需要自行转换。
d)在使用CAsyncSocket之前,必须调用AfxSocketInit初始化WinSock环境,而AfxSocketInit会创建一个隐藏的CSocketWnd对象,由于这个对象由Cwnd派生,因此它能够接收Windows消息。一方面它会接受各个CAsyncSocket的状态报告,另一方面它能捕捉系统发出的各种SOCKET事件,其通信流程如下。
二、CSocket类
CSocket是MFC在CAsyncSocket基础上派生的一个同步阻塞Socket的封装类。
CSocket类是从CAsyncsocket派生而来的,它继承了CAsyncsocket对WindowsSockets API的封装。与CAsyncsocket对象相比,CSocket对象代表了WindowsSockets API的更高一级的抽象化。
a)在使用MFC编写socket程序时,必须要包含<afxsock.h>都文件。
b) AfxSocketInit() 这个函数,在使用CSocket前一定要先调用该函数,否则使用CSocket会出错;并且该函数还有一个重要的使用方式,就是在某个线程下使用 CSocket 前一定要调用,就算主线程调用了该函数,在子线程下使用 CSocket 也要先调用该函数,要不会出错。
c) 还要注意的是, Create 方法已经包含了 Bind 方法,如果是以 Create 方法初始化的前提下不能再调用 Bind ,要不一定出错。
三、代码实现
设计两个对话框应用程序,通过TCP/IP进行通信,使用MFC的CSocket类实现服务器端和客户端之间的相互通信。
3.1服务器端
服务器端的socket通信需要用到两个socket:
- 用来监听连接的socket
- 用来和客户端通信的socket,此socket中保存了客户端信息
因此服务器端需要添加两个继承自CSocket的类,分别起名为CServerSocket、CConnectSocket。
3.1.1 CServerSocket
此socket主要用来监听客户端请求,当有请求到来时,MFC框架将调用OnAccept函数,所以我们需要重写CSocket类的OnAccept函数。
1 ///////////////////////////////////////////////////////// 2 # ServerSocket.h 文件 3 ///////////////////////////////////////////////////////// 4 class CServerSocket : public CSocket 5 { 6 public: 7 CServerSocket(); 8 virtual ~CServerSocket(); 9 10 void OnAccept(int nErrorCode); 11 //开启socket服务 12 void StartServer(UINT nPort); 13 //发送消息函数 14 void MessageSend(const char* pMesg); 15 16 private: 17 //保存的是客户端的socket信息 18 CConnectSocket m_clientSock; 19 };
在OnAccept函数中调用Accept函数,接受客户端请求,另外将客户信息保存到m_clientSock中,使用此套接字对象与客户端进行通信,发送信息可以直接调用API函数Send。
在StartServer函数中做了两件事儿,创建套接字,监听套接字。需要主要的是Create函数内部已经对套接字进行了绑定,所以不需要再次绑定。
1 ///////////////////////////////////////////////////////// 2 # ServerSocket.cpp 文件 3 ///////////////////////////////////////////////////////// 4 void CServerSocket::OnAccept(int nErrorCode) 5 { 6 Accept(m_clientSock); 7 CSocket::OnAccept(nErrorCode); 8 } 9 10 void CServerSocket::StartServer(UINT nPort) 11 { 12 if (!Create(nPort)) 13 { 14 AfxMessageBox(_T("Socket 创建失败!")); 15 return; 16 } 17 if (!Listen(5)) 18 { 19 AfxMessageBox(_T("Socket 监听失败!")); 20 return; 21 } 22 } 23 24 void CServerSocket::MessageSend(const char* pMesg) 25 { 26 m_clientSock.Send(pMesg, strlen(pMesg) + 1); 27 }
3.1.2CConnectSocket类
此类中保存了客户端信息,所以用来与客户端进行通信,重写了OnSend和OnReceive函数,这两个函数由MFC框架调用。
1 ///////////////////////////////////////////////////////// 2 # ConnectSocket.h 文件 3 ///////////////////////////////////////////////////////// 4 class CConnectSocket : public CSocket 5 { 6 public: 7 CConnectSocket(); 8 virtual ~CConnectSocket(); 9 10 //函数重写 11 void OnSend(int nErrorCode); 12 void OnReceive(int nErrorCode); 13 };
当客户端与客户端连接成功之后服务器端框架会调用OnSend函数给客户端发送通知。另外,当服务器端接收到客户端发来的消息之后,MFC框架会调用OnReceive函数,我们可以在此函数中对接收到的消息进行处理。
1 ///////////////////////////////////////////////////////// 2 # ConnectSocket.cpp 文件 3 ///////////////////////////////////////////////////////// 4 void CConnectSocket::OnSend(int nErrorCode) 5 { 6 char* pSend = "你好, 我是服务器,我们已经成功建立了连接!"; 7 Send(pSend, strlen(pSend) + 1); 8 CSocket::OnSend(nErrorCode); 9 } 10 11 void CConnectSocket::OnReceive(int nErrorCode) 12 { 13 char bufRecv[1024]; 14 int nCount = Receive(bufRecv, 1024); 15 bufRecv[nCount] = 0; 16 CUnicodeAndChar uc; 17 CString str = uc.MultiToWide(string(bufRecv)); 18 AfxMessageBox(str); 19 CSocket::OnReceive(nErrorCode); 20 }