Free mind

Be fresh and eager every morning, and tired and satisfied every night.
posts - 39, comments - 2, trackbacks - 0, articles - 0
   :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

SOCKET 汇总

Posted on 2007-02-09 22:44 morphis 阅读(658) 评论(0)  编辑  收藏 所属分类: 5. P2P

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

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 BSD SOCKET 主要是同步的 ,但有阻塞和非阻塞两种方式。阻塞方式定义与前面定义相同,要解决阻塞有两种方法

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

sockfd = socket(AF_INET, SOCK_STREAM, 0); 

fcntl(sockfd, F_SETFL, O_NONBLOCK);   

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

另一种是使用 select() 函数

同步方式中解决 recv send 阻塞问题

采用 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 的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。

【理解:基于消息驱动的可称之为 异步 。】

[ 1 ]

熟悉 WINSOCK 编程的读者一定会觉得奇怪吧,为什么 INDY 是是完全基于 SOCKET 阻塞工作模式的呢?异步模式(非阻塞模式)是 WINSOCK 的一大特点,为什么不用呢?

  其实,之所以大多数 WINDOWS 下的 INTERNET 程序都使用异步模式,这和 WINSOCK 的历史有关。当 WINSOCK 被移植到 WINDOWS 的时候,当时的 WINDOWS 操作系统还是 WINDOWS 3.1 ,而 WINDOWS 3.1 是不支持多线程的,不象 UNIX 下可以使用 FORK 来运行多进程。在 WINDOWS 3.1 下,如果使用阻塞模式,在通讯时会锁定用户界面使程序没有响应,为了避免这种情况, WINSOCK 就引入异步模式这个新特性。而使用异步模式来编制 INTERNET 程序也就成了 WINDOWS 程序员的经典教条。但是,随着新的 WINDOWS 操作系统的出现,如 WINDOWS 95 NT 98 ME 2000 等,这些操作系统开始支持多线程。异步模式这个教条仍然深入人心,使很多程序员会下意识的拒绝使用阻塞模式。

事实上, UNIX 下的 SOCKET 只支持阻塞模式(现在 UNXI SOCKET 有了一些新的非阻塞特性,不过绝大多数应用仍然使用阻塞模式) 。阻塞模式具有以下几个比异步模式优越的特点:

编程更简单,可以把所有处理 SOCKET 的代码放在一起,顺序执行,而不用分散在不同的事件处理代码段里。

更容易移植到 UNIX ,使用 INDY DELPHI 程序,可以不做太多(甚至不做)修改,就可以把 WINDOWS DELPHI 源代码拿到 LINUX 下,用 Kylix 来编译成 LINUX 下的网络程序。

[ 2 ]

Windows Sockets API Microsoft Windows 的网络程序设计接口,它在继承了 Berkeley Sockets 主要特征的基础上,又对它进行了重要扩充。这些扩充主要是提供了一些异步函数,并增加了符合 Windows 消息驱动特性的网络事件异步选择机制 。这些扩充有利于应用程序开发者编制符合 Windows 编程模式的软件,它使在 Windows 下开发高性能的网络通信程序成为可能


只有注册用户登录后才能发表评论。


网站导航: