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类的一样。