关于同步、异步,阻塞、非阻塞的解释

在windows socket api 下:

异步方式指的是发送方不等接收方响应,便接着发下个数据包的通信方式;而同步指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式。

阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用recv()函数读取网络缓冲区中的数据,如果没有数据到达,将一直挂在recv()这个函数调用上,直到读到一些数据,此函数调用才返回;而非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。

(同步阻塞、异步非阻塞)

1、默认用作同步阻塞方式,那就是当你从不调用WSAIoctl()和ioctlsocket()来改变Socket IO模式,也从不调用WSAAsyncSelect()和WSAEventSelect()来选择需要处理的Socket事件。正是由于函数accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函数被用作阻塞方式,所以可能你需要放在专门的线程里,这样以不影响主程序的运行和主窗口的刷新。
2、如果作为异步非阻塞方式用,那么程序主要就是要处理事件。它有两种处理事件的办法:
    第一种,它常关联一个窗口,也就是异步Socket的事件将作为消息发往该窗口,这是由WinSock扩展规范里的一个函数WSAAsyncSelect()来实现和窗口关联。最终你只需要处理窗口消息,来收发数据。
  第二种,用到了扩展规范里另一个关于事件的函数WSAEventSelect(),它是用事件对象的方式来处理Socket事件,也就是,你必须首先用WSACreateEvent()来创建一个事件对象,然后调用WSAEventSelect()来使得Socket的事件和这个事件对象关联。最终你将要在一个线程里用WSAWaitForMultipleEvents()来等待这个事件对象被触发。这个过程也稍显复杂。

要点一、UNIIX BSD下SOCKET

UNIIX BSDSOCKET主要是同步的,但有阻塞和非阻塞两种方式。阻塞方式定义与前面定义相同,要解决阻塞有两种方法

一种是设置SOCKET属性,设置为非阻塞(fcntl()函数),

sockfd = socket(AF_INET, SOCK_STREAM, 0);

fcntl(sockfd, F_SETFL, O_NONBLOCK); 

通过设置套接字为非阻塞,你能够有效地"询问"套接字以获得信息。如果尝试着从一个非阻塞的套接字读信息并且没有任何数据,它不允许阻 塞,它将返回 -1 并将 errno 设置为 EWOULDBLOCK  但是一般说来,这种询问不是个好主意。如果让程序在忙等状 态查询套接字的数据,将浪费大量的 CPU 时间。更好的解决之道是用 下一章讲的 select() 去查询是否有数据要读进来。

另一种是使用select()函数

同步方式中解决recvsend阻塞问题

采用select函数解决,在收发前先检查读写可用状态。

A、读

  例子:

TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为0-10毫秒

int nSelectRet;

int nErrorCode;

FD_SET fdr = {1, sConnect};

nSelectRet=::select(0, &fdr, NULL, NULL, &tv01);//检查可读状态

if(SOCKET_ERROR==nSelectRet)

{

nErrorCode=WSAGetLastError();

TRACE("select read status errorcode=%d",nErrorCode);

::closesocket(sConnect);

goto 重新连接(客户方),或服务线程退出(服务方);

}

if(nSelectRet==0)//超时发生,无可读数据

{

继续查读状态或向对方主动发送

}

else

{

读数据

}

B、写

TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为9-10毫秒

int nSelectRet;

int nErrorCode;

FD_SET fdw = {1, sConnect};

nSelectRet=::select(0, NULL, NULL,&fdw, &tv01);//检查可写状态

if(SOCKET_ERROR==nSelectRet)

{

nErrorCode=WSAGetLastError();

TRACE("select write status errorcode=%d",nErrorCode);

::closesocket(sConnect);

//goto 重新连接(客户方),或服务线程退出(服务方);

}

if(nSelectRet==0)//超时发生,缓冲满或网络忙

{

//继续查写状态或查读状态

}

else

{

//发送

}

对于Windows这种非抢先多任务操作系统来说,这两种工作方式都是很难以接受的,为此,WINSOCK在尽量与BSD Socket保持一致外,又对它作了必要的扩充

附:

改变TCP收发缓冲区大小

  系统默认为8192,利用如下方式可改变。

SOCKET sConnect;

sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

int nrcvbuf=1024*20;

int err=setsockopt(

sConnect,

SOL_SOCKET,

SO_SNDBUF,//写缓冲,读缓冲为SO_RCVBUF

(char *)&nrcvbuf,

sizeof(nrcvbuf));

if (err != NO_ERROR)

{

TRACE("setsockopt Error!/n");

}

在设置缓冲时,检查是否真正设置成功用

int getsockopt(

SOCKET s,

int level,

int optname,

char FAR *optval,

int FAR *optlen

);

服务方同一端口多IP地址的bind和listen

 在可靠性要求高的应用中,要求使用双网和多网络通道,再服务方很容易实现,用如下方式可建立客户对本机所有IP地址在端口3024下的请求服务。

SOCKET hServerSocket_DS=INVALID_SOCKET;

struct sockaddr_in HostAddr_DS;//服务器主机地址

LONG lPort=3024;

HostAddr_DS.sin_family=AF_INET;

HostAddr_DS.sin_port=::htons(u_short(lPort));

HostAddr_DS.sin_addr.s_addr=htonl(INADDR_ANY);

hServerSocket_DS=::socket( AF_INET, SOCK_STREAM,IPPROTO_TCP);

if(hServerSocket_DS==INVALID_SOCKET)

{

AfxMessageBox("建立数据服务器SOCKET 失败!");

return FALSE;

}

if(SOCKET_ERROR==::bind(hServerSocket_DS,(struct

sockaddr *)(&(HostAddr_DS)),sizeof(SOCKADDR)))

{

int nErrorCode=WSAGetLastError ();

TRACE("bind error=%d/n",nErrorCode);

AfxMessageBox("Socket Bind 错误!");

return FALSE;

}

if(SOCKET_ERROR==::listen(hServerSocket_DS,10))//10个客户

{

AfxMessageBox("Socket listen 错误!");

return FALSE;

}

AfxBeginThread(ServerThreadProc,NULL,THREAD_PRIORITY_NORMAL);

要点二、windows socket api

WINSOCK对BSD Socket的扩充主要是在基于消息、对网络事件的异步存取接口上。下表列出了WINSOCK扩充的函数功能。

       函   数   名

         功            能

WSAAsyncGetHostByAddr()

标准Berkeley函数getXbyY的异步版本,例

WSAAsyncGetHostByName()

如:函数WSAAsyncGetHostByName()就是提

WSAAsyncGetProtoByName()

供了标准Berkeley函数gethostbyname的一

WSAAsyncGetProtoByNumber()

种基于消息的异步实现。

WSAAsyncGetServByName()

WSAAsyncGetServByPort()

WSAAsyncSelect()

函数select()的异步版本

WSACancelAsyncRequest()

取消函数WSAAsyncGetXByY执行中的实例

WSACancelBlockingCall()

取消一个执行中的“阻塞”API调用

WSACleanup()

终止使用隐含的Windows Sockets DLL

WSAGetLastError()

获取Windows Sockets API的最近错误号

WSAIsBlocking()

检测隐含的Windows Sockets DLL是否阻塞了一个当前线索的调用

WSASetBlockingHook()

设置应用程序自己的“阻塞”处理函数

WSASetLastError()

设置Windows Sockets API的最近错误号

WSAStartup()

初始化隐含的Windows Sockets DLL

WSAUnhookBlockingHook()

恢复原来的“阻塞”处理函数

从表1可以看出,WINSOCK的扩充功能可以分为如下几类:

    (1)异步选择机制:

    异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有阻塞的网络I/O例程(如send()和resv()),不管它是已经使用还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。

    (2)异步请求例程:

    异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。函数WSACancelAsyncRequest()允许用户终止一个正在执行的异步请求。

    (3)阻塞处理方法:

    WINSOCK在调用处于阻塞时进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WINSOCK还提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。

    (4)出错处理:

    为了和以后的多线索环境(如Windows/NT)兼容,WINSOCK提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置本线索的最近错误号。

    (5)启动与终止:

    WINSOCK的应用程序在使用上述WINSOCK函数前,必须先调用WSAStartup()函数对Windows Sockets DLL进行初始化,以协商WINSOCK的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSAClearnup()终止对Windows Sockets DLL的使用,并释放资源,以利下一次使用。

    在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用,其原型如下:

int PASCAL FAR WSAAsyncSelect(SOCTET s,HWND hWnd,unsigned int wMsg,long lEvent);

它请求Windows Sockets DLL在检测到在套接字s上发生的lEvent事件时,向窗口hWnd发送一个消息wMsg。它自动地设置套接字s处于非阻塞工作方式。参数lEvent由下列事件的一个或多个组成:

       值              含        义

        FD_READ       希望在套接字s收到数据(即读准备好)时接到通知

        FD_WRITE      希望在套接字s可发送数据(即写准备好)时接到通知

        FD_OOB        希望在套接字s上有带外数据到达时接到通知

        FD_ACCEPT     希望在套接字s上有外部连接到来时接到通知

        FD_CONNECT 希望在套接字s连接建立完成时接到通知

        FD_CLOSE      希望在套接字s关闭时接到通知

       表2.   异步选择网络事件表

    例如,我们要在套接字s读准备好或写准备好时接到通知,可以使用下面的语句:

    rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);

当套接字s上被提名的一个网络事件发生时,窗口hWnd将收到消息wMsg,变量lParam的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。

TOP

异步方式指的是发送方不等接收方响应,便接着发下个数据包的通信方式;而同步指发送方发出数据后,等收到接收方发回的响应,才发下一个数据包的通信方式。

阻塞套接字是指执行此套接字的网络调用时,直到成功才返回,否则一直阻塞在此网络调用上,比如调用recv()函数读取网络缓冲区中的数据,如果没有数据到达,将一直挂在recv()这个函数调用上,直到读到一些数据,此函数调用才返回;而非阻塞套接字是指执行此套接字的网络调用时,不管是否执行成功,都立即返回。比如调用recv()函数读取网络缓冲区中数据,不管是否读到数据都立即返回,而不会一直挂在此函数调用上。在实际Windows网络通信软件开发中,异步非阻塞套接字是用的最多的。

(同步阻塞、异步非阻塞)

1、默认用作同步阻塞方式,那就是当你从不调用WSAIoctl()和ioctlsocket()来改变Socket IO模式,也从不调用WSAAsyncSelect()和WSAEventSelect()来选择需要处理的Socket事件。正是由于函数accept(),WSAAccept(),connect(),WSAConnect(),send(),WSASend(),recv(),WSARecv()等函数被用作阻塞方式,所以可能你需要放在专门的线程里,这样以不影响主程序的运行和主窗口的刷新。
2、如果作为异步非阻塞方式用,那么程序主要就是要处理事件。它有两种处理事件的办法:
    第一种,它常关联一个窗口,也就是异步Socket的事件将作为消息发往该窗口,这是由WinSock扩展规范里的一个函数WSAAsyncSelect()来实现和窗口关联。最终你只需要处理窗口消息,来收发数据。
  第二种,用到了扩展规范里另一个关于事件的函数WSAEventSelect(),它是用事件对象的方式来处理Socket事件,也就是,你必须首先用WSACreateEvent()来创建一个事件对象,然后调用WSAEventSelect()来使得Socket的事件和这个事件对象关联。最终你将要在一个线程里用WSAWaitForMultipleEvents()来等待这个事件对象被触发。这个过程也稍显复杂。

要点一、UNIIX BSD下SOCKET

UNIIX BSDSOCKET主要是同步的,但有阻塞和非阻塞两种方式。阻塞方式定义与前面定义相同,要解决阻塞有两种方法

一种是设置SOCKET属性,设置为非阻塞(fcntl()函数),

sockfd = socket(AF_INET, SOCK_STREAM, 0);

fcntl(sockfd, F_SETFL, O_NONBLOCK); 

通过设置套接字为非阻塞,你能够有效地"询问"套接字以获得信息。如果尝试着从一个非阻塞的套接字读信息并且没有任何数据,它不允许阻 塞,它将返回 -1 并将 errno 设置为 EWOULDBLOCK  但是一般说来,这种询问不是个好主意。如果让程序在忙等状 态查询套接字的数据,将浪费大量的 CPU 时间。更好的解决之道是用 下一章讲的 select() 去查询是否有数据要读进来。

另一种是使用select()函数

同步方式中解决recvsend阻塞问题

采用select函数解决,在收发前先检查读写可用状态。

A、读

  例子:

TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为0-10毫秒

int nSelectRet;

int nErrorCode;

FD_SET fdr = {1, sConnect};

nSelectRet=::select(0, &fdr, NULL, NULL, &tv01);//检查可读状态

if(SOCKET_ERROR==nSelectRet)

{

nErrorCode=WSAGetLastError();

TRACE("select read status errorcode=%d",nErrorCode);

::closesocket(sConnect);

goto 重新连接(客户方),或服务线程退出(服务方);

}

if(nSelectRet==0)//超时发生,无可读数据

{

继续查读状态或向对方主动发送

}

else

{

读数据

}

B、写

TIMEVAL tv01 = {0, 1};//1ms钟延迟,实际为9-10毫秒

int nSelectRet;

int nErrorCode;

FD_SET fdw = {1, sConnect};

nSelectRet=::select(0, NULL, NULL,&fdw, &tv01);//检查可写状态

if(SOCKET_ERROR==nSelectRet)

{

nErrorCode=WSAGetLastError();

TRACE("select write status errorcode=%d",nErrorCode);

::closesocket(sConnect);

//goto 重新连接(客户方),或服务线程退出(服务方);

}

if(nSelectRet==0)//超时发生,缓冲满或网络忙

{

//继续查写状态或查读状态

}

else

{

//发送

}

对于Windows这种非抢先多任务操作系统来说,这两种工作方式都是很难以接受的,为此,WINSOCK在尽量与BSD Socket保持一致外,又对它作了必要的扩充

附:

改变TCP收发缓冲区大小

  系统默认为8192,利用如下方式可改变。

SOCKET sConnect;

sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

int nrcvbuf=1024*20;

int err=setsockopt(

sConnect,

SOL_SOCKET,

SO_SNDBUF,//写缓冲,读缓冲为SO_RCVBUF

(char *)&nrcvbuf,

sizeof(nrcvbuf));

if (err != NO_ERROR)

{

TRACE("setsockopt Error!/n");

}

在设置缓冲时,检查是否真正设置成功用

int getsockopt(

SOCKET s,

int level,

int optname,

char FAR *optval,

int FAR *optlen

);

服务方同一端口多IP地址的bind和listen

 在可靠性要求高的应用中,要求使用双网和多网络通道,再服务方很容易实现,用如下方式可建立客户对本机所有IP地址在端口3024下的请求服务。

SOCKET hServerSocket_DS=INVALID_SOCKET;

struct sockaddr_in HostAddr_DS;//服务器主机地址

LONG lPort=3024;

HostAddr_DS.sin_family=AF_INET;

HostAddr_DS.sin_port=::htons(u_short(lPort));

HostAddr_DS.sin_addr.s_addr=htonl(INADDR_ANY);

hServerSocket_DS=::socket( AF_INET, SOCK_STREAM,IPPROTO_TCP);

if(hServerSocket_DS==INVALID_SOCKET)

{

AfxMessageBox("建立数据服务器SOCKET 失败!");

return FALSE;

}

if(SOCKET_ERROR==::bind(hServerSocket_DS,(struct

sockaddr *)(&(HostAddr_DS)),sizeof(SOCKADDR)))

{

int nErrorCode=WSAGetLastError ();

TRACE("bind error=%d/n",nErrorCode);

AfxMessageBox("Socket Bind 错误!");

return FALSE;

}

if(SOCKET_ERROR==::listen(hServerSocket_DS,10))//10个客户

{

AfxMessageBox("Socket listen 错误!");

return FALSE;

}

AfxBeginThread(ServerThreadProc,NULL,THREAD_PRIORITY_NORMAL);

要点二、windows socket api

WINSOCK对BSD Socket的扩充主要是在基于消息、对网络事件的异步存取接口上。下表列出了WINSOCK扩充的函数功能。

       函   数   名

         功            能

WSAAsyncGetHostByAddr()

标准Berkeley函数getXbyY的异步版本,例

WSAAsyncGetHostByName()

如:函数WSAAsyncGetHostByName()就是提

WSAAsyncGetProtoByName()

供了标准Berkeley函数gethostbyname的一

WSAAsyncGetProtoByNumber()

种基于消息的异步实现。

WSAAsyncGetServByName()

WSAAsyncGetServByPort()

WSAAsyncSelect()

函数select()的异步版本

WSACancelAsyncRequest()

取消函数WSAAsyncGetXByY执行中的实例

WSACancelBlockingCall()

取消一个执行中的“阻塞”API调用

WSACleanup()

终止使用隐含的Windows Sockets DLL

WSAGetLastError()

获取Windows Sockets API的最近错误号

WSAIsBlocking()

检测隐含的Windows Sockets DLL是否阻塞了一个当前线索的调用

WSASetBlockingHook()

设置应用程序自己的“阻塞”处理函数

WSASetLastError()

设置Windows Sockets API的最近错误号

WSAStartup()

初始化隐含的Windows Sockets DLL

WSAUnhookBlockingHook()

恢复原来的“阻塞”处理函数

从表1可以看出,WINSOCK的扩充功能可以分为如下几类:

    (1)异步选择机制:

    异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有阻塞的网络I/O例程(如send()和resv()),不管它是已经使用还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。

    (2)异步请求例程:

    异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。函数WSACancelAsyncRequest()允许用户终止一个正在执行的异步请求。

    (3)阻塞处理方法:

    WINSOCK在调用处于阻塞时进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WINSOCK还提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。

    (4)出错处理:

    为了和以后的多线索环境(如Windows/NT)兼容,WINSOCK提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置本线索的最近错误号。

    (5)启动与终止:

    WINSOCK的应用程序在使用上述WINSOCK函数前,必须先调用WSAStartup()函数对Windows Sockets DLL进行初始化,以协商WINSOCK的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSAClearnup()终止对Windows Sockets DLL的使用,并释放资源,以利下一次使用。

    在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用,其原型如下:

int PASCAL FAR WSAAsyncSelect(SOCTET s,HWND hWnd,unsigned int wMsg,long lEvent);

它请求Windows Sockets DLL在检测到在套接字s上发生的lEvent事件时,向窗口hWnd发送一个消息wMsg。它自动地设置套接字s处于非阻塞工作方式。参数lEvent由下列事件的一个或多个组成:

       值              含        义

        FD_READ       希望在套接字s收到数据(即读准备好)时接到通知

        FD_WRITE      希望在套接字s可发送数据(即写准备好)时接到通知

        FD_OOB        希望在套接字s上有带外数据到达时接到通知

        FD_ACCEPT     希望在套接字s上有外部连接到来时接到通知

        FD_CONNECT 希望在套接字s连接建立完成时接到通知

        FD_CLOSE      希望在套接字s关闭时接到通知

       表2.   异步选择网络事件表

    例如,我们要在套接字s读准备好或写准备好时接到通知,可以使用下面的语句:

    rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);

当套接字s上被提名的一个网络事件发生时,窗口hWnd将收到消息wMsg,变量lParam的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。

猜你喜欢

转载自blog.csdn.net/wli_2005/article/details/12129345