MFC--网络编程之CAsyncSocket详解

原文链接地址:https://blog.csdn.net/u012372584/article/details/76146844

CAsyncSocket类是从Object类派生而来。CAsyncSocket对象称为异步套接字对象

使用CAsyncSocket进行网络编程,可以充分利用Windows操作系统提供的消息驱动机制,通过应用程序框架来传递消息,方便地处理各种网络事件。另一方面,作为MFC微软基础类库中的一员,CAsyncSocket可以和MFC的其他类融为一体,大大扩展了网络编程的空间,方便了编程。

1. 使用CAsyncSocket的一般步骤

网络应用程序一般采用客户端/服务器模式,他们使用的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来删除释放该对象

2.在使用CAsyncSocket进行网络通信时,我们还需要处理以下几个问题:

① 堵塞处理,CAsyncSocket对象专用于异步操作,不支持堵塞工作模式,如果应用程序需要支持堵塞操作,必须自己解决

② 字节顺序的转换。在不同的结构类型的计算机之间进行数据传输时,可能会有计算机之间字节存储顺序不一致的情况。用户程序需要自己对不用的字节顺序进行转换

③ 字符串转换。同样,不同结构类型的计算机的字符串存储顺序也可能不同,需要自行转换,如Unicode和ANSI字符串之间的转换

3. 分步骤详解

创建CAsyncSocket对象

创建异步套接字对象一般是分为两个步骤,首先要构造CAsyncSocket对象,其次创建该对象底层的SOCKET句柄

(1)创建空的CAsyncSocket对象

通过调用CAsyncSocket构造函数,创建一个新的空CAsyncSocket套接字对象,构造函数还带参数。套接字对象创建之后必须调用他的成员函数来创建底层的套接字数据结构,并绑定他的地址

方法一:

CAsyncSocket Sock
Sock.Create(...)


方法二:

CAsyncSocket *pSock = new CAsyncSocket;
pSock->Create(...);
 
delete pSock;
pSock =NULL;


ps:之前见过很多朋友释放指针的时候总是delete就完事了,往往不知正在给自己的程序带来前所未有的灾难,而此时的指针我们称之为“野指针”,注意:野指针不是NULL指针,而是不可用内存的指针,即垃圾指针;野指针是很危险的,因为我们无法通过if去判断指针是正常指针还是野指针,所以,我们在书写代码时一定要养成良好的编程习惯!!!避免野指针的方法我们可以通过以下几点:

① 声明指针一定要初始化,如果不初始化为NULL,那么此时一定要指向一块合法的内存

② 当调用delete或者free去释放指针后,一定要将指针重新指向NULL,可以参照SkinUI的SafeDelete

③ 指针操作超出了变量的作用范围,比如如下代码,

class A
{
   void Fun(){}
};
class B
{
   A *m_A;
   B(){m_A=NULL;}
    
   void Fun()
   {
      A a
      m_A =&a;
      //注意变量A的生命周期,当该方法执行完毕后,A会被释放,此时m_A就变成了无效的野指针
   }
   void Fun1()
   {
      m_A->Fun(); //m_A为野指针,到这里也就出现了错误
   }
};

通过上述方法可以大大的降低代码出现野指针的风险。

(2)创建CAsyncSocket套接字的底层套接字句柄

通过CAsyncSocket::Create创建该对象的底层套接字句柄,决定套接字对象的具体特性。

函数原型如下:

BOOL Create(
     UINT nSocketPort = 0,
     int nSocketType = SOCK_STREAM,
     long lEvent = FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE,
     LPCTSTR lpszSocketAddress = NULL
    );

参数:

nSocketPort,指定了一个分配给套接字的传输层端口号,默认值为0,表示让系统为这个套接字分配一个自由的端口号。但是对于服务器应用程序而言,一般都需要事先分配一个公认的端口号,所以切记,服务器应用程序调用此函数时,必须分配一个端口号

nSocketType,套接字的类型,当指定为SOCK_STREAM时表示生成流式套接字,若使用SOCK_DGRAM表示生成数据报套接字

lEvent,指定为CAsyncSocket对象生成通知消息的套接字事件,默认对所有的套接字事件都生成通知消息

lpszSocketAddress,指定套接字的网络地址,对Internet通信域来说,就是主机的域名或者ip地址,比如www.gymsaga.com或123.123.123.123。如果使用默认值,表示使用默认的本机ip地址

4 关于CAsyncSocket可以接受并处理的消息事件

在CAsyncSocket::Create中,参数lEvent指定了为CAsyncSocket对象生成通知消息的套接字事件,最能体现CAsyncSocket对Windows消息驱动机制的支持

先认识一下这六种相关事件和通知消息

关于lEvent参数的符号常量,我们可以在WinSock中找到

/*
 * Define flags to be used with the WSAAsyncSelect() call.
 */
#define FD_READ         0x01
#define FD_WRITE        0x02
#define FD_OOB          0x04
#define FD_ACCEPT       0x08
#define FD_CONNECT      0x10
#define FD_CLOSE        0x20

它们代表了MFC套接字对象可以接收并处理的6种网络事件,当事件发生时,套接字对象会收到相应的通知消息,并自动执行套接字对象响应的事件处理函数

1:FD_READ :   通知有数据可读。当一个套接字对象的数据输入缓冲区收到其他套接字对象发送来的数据时,发生此事件,并通过该套接字对象 ,告诉它可以调用Receive成员来接收数据

2:FD_WRITE:   通知可以写数据,当一个套接字对象的数据输出缓冲区中的数据已经发送出去,输出缓冲区已腾空时,发生此事件,并通过该套接字对象,告诉它可以调用Send函数向外发送数据

3:FD_ACCEPT:  通知监听套接字有连接请求可以接收。当客户端的链接请求到达服务器时,进一步说,是当客户端的连接请求已经进入服务器监听套接字的接收缓冲区队列时,发生此事件,并通过监听套接字对象,告诉它可以调用Accept成员来接收待决的链接请求。这个事件仅对流式套接字有效,并且发生在服务器端

4:FD_CONNECT: 通知请求链接的套接字,链接的要求已经被处理。当客户端的连接请求已被处理时,发生此事件。存在两种情况:一种是服务器端已接收了链接请求,双方的连接已经建立,通知客户端套接字,可以使用链接来传输数据了;另一种情况是链接请求被拒绝,通知客户机套接字,它所请求的连接失败。这个事件仅对流式套接字有效,并且发生在客户端

5:FD_CLOSE:   通知套接字已关闭。当链接的套接字关闭时发生

6:FD_OOB:     通知将带外数据到达。当对方的流失套接字发送带外数据时,发生此事件,并通知接收套接字,正在发送的套接字有带外数据要求发送,带外数据是有没对链接的流失套接字相关的在逻辑上独立的通道,带外数据通道典型的是用来发送紧急数据。MFC支持带外有数据,使用CAsyncSocket类的高级用户可能需要使用带外数据通道,但不鼓励使用CSocket类的用户使用它,更容易的方法是创建第二个套接字来传送这样的数据

MFC框架对这六种事件的处理

当上述的网络事件发生时,MFC框架做何处理呢?MFC框架按照Windows系统的消息驱动把消息发送给相应的套接字对象,并调用作为该对象函数的事件处理函数,事件与处理函数一一映射。

在afxSock.h中我们可以找到CAsyncSocket类对这六种对应事件的处理函数

// Overridable callbacks
protected:
    virtual void OnReceive(int nErrorCode);
    virtual void OnSend(int nErrorCode);
    virtual void OnOutOfBandData(int nErrorCode);
    virtual void OnAccept(int nErrorCode);
    virtual void OnConnect(int nErrorCode);
    virtual void OnClose(int nErrorCode);

其中参数nErrorCode的值,是在函数被调用时,由MFC框架提供的,表明套接字最新的状况,如果是0,说明成功,如果为非零值,说明套接字对象有某种错误

当某个网络事件发生时,MFC框架会自动调用套接字对象对应的事件处理函数。这就相当于给套接字对象一个通知,告诉它某个重要的事件已经发生,所以也称为套接字类的通知函数或者回调函数

5.重载套接字对象的回调函数

在编程中,一般我们不会直接去使用CAsyncSocket或者CSocket,而是从他们派生出自己的套接字类来。然后在派生类中对这些虚函数进行重载处理,加入应用程序对于网络事件处理的特定代码

如果是从CAsyncSocket类派生了自己的套接字类,就必须重载该应用程序所感兴趣的那些网络事件所对应的通知函数。如果从CSocket类派生一个类,是否重载所感兴趣的通知函数则由自己决定。也可以使用CSocket类本身的回调函数,但默认情况下,CSocket本身的回调函数什么也不做,只是一个空函数。

MFC框架自动调用通知函数,使得用户可以在套接字被通知的时候来优化套接字的行为。例如,用户可以从自己的OnReceive通知函数中调用套接字对象的成员函数Receive,就是说,在被通知的时候,已经有数据可读了,才调用Receive来读取它。这个方法不是必须的,但它是一个有效的方案。此外,也可以使用自己的通知函数跟踪进程,打印TRACE消息等

对于CSocket对象,还有如下一些不同之处

在一个诸如接收或者发送数据的操作期间,一个CSocket对象成为同步的,在同步状态期间,在当前套接字等待它想要的通知时,任何的为其他套接字的通知被排成队列,一旦该套接字完成了它的同步操作,并再次成为异步的,其他的套接字才可以开始接收排列的通知

重要的一点是:在CSocket中,从来不调用OnConncet通知函数,对于连接,简单的调用Conncet函数,仅当连接完成时,无论成功还是失败,该函数都返回,连接通知如何被处理是一个MFC内部的实现细节。

猜你喜欢

转载自blog.csdn.net/XingyuZhengtu/article/details/81127103