SOCKET编程

SOCKET 基础 异步套接字

前面介绍的socket操作中,有一个非常大的缺陷,当一个socket运行accept,recv,recvfrom等网络操作的时候,程序就要等待函数成功返回一个值后才能继续往下执行。譬如说,一个tcp socket用recv函数接收数据,那么程序的执行就停留在recv语句上一直等待数据的到来,这就是所谓的同步套接字。有米办法让程序不会卡在accept,recv,recvfrom这样的网络操作上呢?简单的方法是把这样的操作放到一个新开的线程上,但这不是一个好方法,会令代码变得复杂和线程增多难以管理。为了解决这个问题异步套接字就产生了。异步套接字的使用流程如下:



1.为工程链接导入库文件 ws2_32.lib ,然后添加头文件 #include <Winsock2.h> ,然后在App类的InitInstance()函数里面加载套接字库,注意必须加载2.0或以上版本的套接字库。

2.下面以对话框工程TCP socket为例:


  为对话框类添加一个socket类型的成员变量: SOCKET socketrecv;


  然后在OnInitDialog()函数中添加以下代码:


   socketrecv=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,0);  


//WSASocket函数是用于创建套接字的扩展函数,与socket函数相比多了后面三个参数,从而使他比socket函数拥有更强大功能,如果不需要用到后三个参数新增的功能,可以直接使用socket函数创建套接字,作用一样。
   if(INVALID_SOCKET==socketrecv)
    {
        closesocket(socketrecv);
        MessageBox("套接字创建失败!");
        return FALSE;
    }


    SOCKADDR_IN addrSrv;
    addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);  //本机任意IP地址
    addrSrv.sin_family=AF_INET;
    addrSrv.sin_port=htons(6500);  //6500端口
    
    int ret1;
    ret1=bind(socketrecv,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); //绑定套接字
    if(SOCKET_ERROR==ret1)
    {closesocket(socketrecv);
     MessageBox("套接字绑定失败!");
     return FALSE;
    }


    int ret2;
    ret2=listen(socketrecv,5);                    //开始监听
    if(SOCKET_ERROR==ret2)
    {
        closesocket(socketrecv);
        MessageBox("套接字监听失败!");
        return FALSE;
    }


    if(SOCKET_ERROR==WSAAsyncSelect(socketrecv,m_hWnd,UM_SOCK,FD_ACCEPT))
   {MessageBox("注册网络ACCEPT事件失败!");
    return FALSE;
   }


//WSAAsyncSelect函数用于请求一个网络事件通知,最后的参数是事件类型,本例注册了一个FD_ACCEPT事件,这样,当有socket 请求连接(connect)socketrecv的时候,就会触发FD_ACCEPT事件,系统就会发送UM_SOCK消息(UM_SOCK是用户自定义消息),后面必须在UM_SOCK消息的消息响应函数里进行socketrecv的accept操作。查看MSDN可查询到其他可以注册的网络事件类型。


 


3.UM_SOCK消息处理:


假定UM_SOCK消息的消息响应函数是void OnSock(WPARAM,LPARAM),注意这个消息响应函数的参数必须一定是(WPARAM,LPARAM)。然后void OnSock(WPARAM,LPARAM)的具体实现如下:


void CXXXDlg::OnSock(WPARAM wParam,LPARAM lParam)  


{
    switch(LOWORD(lParam))        //参数lParam包含了网络事件的类型

    {

 case FD_ACCEPT:

        if((SOCKET)wParam==socketrecv)    //参数wParam包含了消息是针对哪个socket发出的
        {
          SOCKADDR_IN addrconn;
          int len=sizeof(SOCKADDR);   
          socknec=accept(socketrecv,(SOCKADDR*)&addrconn,&len);  


          //开始接收连接,socknec是对话框类的一个socket类型成员变量,连接成功后负责与客户端socket收送数据
          if(INVALID_SOCKET ==socknec)        
           { closesocket(socknec);
             MessageBox("接收连接失败!");
             break;   //连接失败,跳出switch
           }

           break;     //连接成功,跳出switch

        }
    }
 }

socket使用完毕后调用closesocket()函数关闭一个socket以回收资源。程序关闭前也必须使用WSACleanup函数终止对套接字库使用,注意必须在App类(应用程序类)的析构函数中调用WSACleanup函数。

以上的例子只是显示了对TCP socket的accept操作的异步处理,可以通过MSDN查询WSAAsyncSelect函数查看其他能进行异步处理的网络事件。



CAsyncSocket 与 CSocket

要使用MFC的socket类,准备工作和使用API的socket类不同,在加载套接字库时,必须在app类的InitInstance()函数里调用AfxSocketInit函数,AfxSocketInit函数内部将调用WSAStartup函数加载套接字库,并且加载的是1.1版本的套接字库,使用AfxSocketInit函数不需要为工程链接ws2_32.lib库文件。如果AfxSocketInit函数调用成功,函数返回非0值,失败则返回0值。使用AfxSocketInit函数加载套接字库的另一个好处是在程序终止前会自动调用WSACleanup函数终止套接字库使用。因为程序调用了AfxSocketInit函数,所以还必须包含相应的头文件,在stdafx.h中添加#include "afxsock.h"。



MFC提供了两个封装socket的类,分别是CAsyncSocket 和 CSocket ,当中CSocket 是由CAsyncSocket 继承的子类,这两个socket类有一个很大的区别,就是CAsyncSocket 是异步套接字而 CSocket是同步套接字,使用MFC的socket类有两个很明显的好处是:


1. 可以从这两个socket类中继承出自己的socket类,从而根据需要增加自己socket类的功能,而且要使用这两


    个socket类,就一定需要从这两个类中继承出自己的类来使用,因为需要修改虚函数才能实现某些基本功能。


2. 可以方便的处理同步与异步问题。


 


对于第一个好处是显然易懂的,而对于第二个好处,就必须了解这些类的阻塞与非阻塞情况了。这里可以分为两大类:


第一类:从CAsyncSocket类继承下来的子类(不包括CSocket类与从CSocket类继承下来的子类),他们的阻


              塞/非阻塞情况一样。


第二类:从CSocket类继承下来的子类,他们的阻塞/非阻塞情况一样。


 


具体情况:


继承了CAsyncSocket类的派生类:


由于CSocket类也是由CAsyncSocket类派生而来的,所以这里说的继承了CAsyncSocket类的派生类是自己建立的类,而不包括CSocket类和CSocket的派生类。例如自己建立一个CMySocket类,如下:


class CMySocket : public CAsyncSocket


那么这个CMySocket类的阻塞与非阻塞情况如下:


1.


Connect的时候,Connect()函数会马上返回WSAEWOULDBLOCK的错误码,然后程序继续往下执行,connect的具体操作就放在以后执行,等到connect的具体操作完了以后,无论有没有connect成功,最后都会调用OnConnect()虚函数。 无论连接成功与否还会调用OnSend()虚函数。


2.


当有一个socket要连接CMySocket类的时候,CMySocket类的OnAccept()虚函数会被自动执行,因此要接受连接请求的话,必须在OnAccept()虚函数里进行Accpet()函数操作。连接成功还会调用OnSend()虚函数,连接不成功不会调用OnSend()虚函数。


3. 


当CMySocket类使用Send()函数发送数据的时候,OnSend()虚函数不会被调用。


4.


当有一个socket向CMySocket类发送数据的时候,CMySocket类的OnReceive()虚函数会被自动执行,因此要接收数据的话,必须在OnReceive()虚函数里进行Receive()函数操作。


 


继承了CSocket类的派生类:


例如自己建立一个CYourSocket类,如下:


class CYourSocket : public CSocket


1.


Connect()的时候连接操作会马上执行,直到连接操作执行完毕后,Connect()函数才会返回,连接成功的话函数返回非0值,连接不成功就返回0值,函数返回后程序才继续往下执行。而OnConnect()虚函数不会被调用。无论连接成功与否还会调用OnSend()虚函数。


2. 


当有一个socket要连接CYourSocket类的时候,CYourSocket类的OnAccept()虚函数会被自动执行,因此要接受连接请求的话,必须在OnAccept()虚函数里进行Accpet()函数操作。连接成功还会调用OnSend()虚函数,连接不成功不会调用OnSend()虚函数。 情况就和上面的CMySocket类的一样。


3.


当CYourSocket类使用Send()函数发送数据的时候,OnSend()虚函数不会被调用。情况就和上面的CMySocket类的一样。


4. 


当有一个socket向CYourSocket类发送数据的时候,CYourSocket类的OnReceive()虚函数会被自动执行,因此要接收数据的话,必须在OnReceive()虚函数里进行Receive()函数操作。情况就和上面的CMySocket类的一样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值