winsock编程宝典之资料库函数

Winsock 提供的资料库函数

Winsock 也提供了同步与非同步的网路资料库函式;不过读者们要知道,此处的资料库指的并非如 Informix, Oracle 等商业用途的资料库系统,而是指主机IP 位址及名称、well-known 服务的名称及 Socket 型态及所用的 port number、以及协定(protocol)名称及代码等。

【同步资料库函数】

首先我们来看一下第一组:gethostbyname() 及 gethostbyaddr() 函式这两个函式的用途是让我们可以由某个主机名称求得它的IP 位址,或是由它的 IP 位址求得它的名称。一般我们经常会用到的是由名称求得 IP 位址;因为很少人会去记某台机器的 IP 位址的,另外 TCP/IP 封包的 IP header 上也必须记载送、收主机的 IP 位址,而不是主机名称。

◎ gethostbyname():利用某一 host 的名称来获取该 host 的资料。

格 式: struct hostent FAR * PASCAL FAR gethostbyname( const char FAR *name );

参 数: name host 的名称

传回值: 成功 - 指向一个 hostent 结构的指标

失败 - NULL (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式是利用 host 名称来获取该主机的其他资料,如 host 的位址、别名,位址的型态、长度等。

◎ gethostbyaddr():利用某一 host 的 IP 位址来获取该 host 的资料。

格 式: struct hostent FAR * PASCAL FAR gethostbyaddr( const char FAR *addr, int len, int type );

参 数: addr network 排列方式的位址

len addr 的长度

type PF_INET(AF_INET)

传回值: 成功 - 指向一个 hostent 结构的指标

失败 - NULL (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式是利用 IP 位址来获取该主机的其他资料,如 host 的名称、别名,位址的型态、长度等。

程式中呼叫的方式分别如下:

char host_name[30];

struct hostent far *htptr;

/* 假设 host_name 的值已先设定为我们要求得资料的主机名称 */

htptr = (struct hostent FAR *) gethostbyname( (char far *) host_name )

struct in_addr host_addr;

struct hostent far *htptr;

/* 假设 host_addr 的值已先设定为我们要求得资料的主机的network byte order 方式的 IP 位址*/

htptr = (struct hostent FAR *) gethostbyaddr((char far *)&host_addr, 4, PF_INET)

一般言,程式中呼叫到 gethostbyname() 及 gethostbyaddr() 时,Winsock Stack 会先在 local 的 「hosts」档中找看看是否有这个主机的资料;如果没有, 则可能再透过「领域名称服务」(Domain Name Service)的功能,向「名称伺服器」(Name Server)查询;所以呼叫这两个函式时,有时会等一下子才获得答覆。如果您想让程式执行快一些的话,可将常用主机的资料放在hosts 档中,这样就不必透过 DNS 去查询了。

接下来我们来看 getservbyname() 及 getservbyport() 这两个函式。大部份的读者应该都用过 telnet、mail、ftp、news等服务应用程式;这些应用程式的协定,比如服务名称、伺服器端所用的 port number、以及 Socket 的型态,都是固定的;这些资料,我们就可以利用 getservbyname() 或 getservbyport()来取得,而不必刻意去记颂它们。

◎ getservbyname():依照服务 (service) 名称及通讯协定(tcp/udp)来获取该服务的其他资料。

格 式: struct servent * PASCAL FAR getservbyname( const char FAR *name, const char FAR *proto );

参 数: name 服务名称

proto 通讯协定名称

传回值: 成功 - 一指向 servent 结构的指标

失败 - NULL (呼叫 WSAGetLastError() 可得知原因)

说明: 利用服务名称及通讯协定来获得该服务的别名、使用的 port 号码等。

◎ getservbyport():依照服务 (service) 的 port 号码及通讯协定(tcp/udp)来获取该服务的其他资料。

格 式: struct servent * PASCAL FAR getservbyport( int port, const char FAR *proto );

参 数: port 服务的 port 编号

proto 通讯协定名称

传回值: 成功 - 一指向 servent 结构的指标

失败 - NULL (呼叫 WSAGetLastError() 可得知原因)

说明: 利用 port 编号及通讯协定来获得该服务的名称、别名等。

程式中的使用方法分别为:

char serv_name[20]; 
char proto[10]; 
struct servent far *svptr; 
/* 假设 serv_name 及 proto 已先设好服务名称及通讯协定 */ 
svptr = (struct servent FAR *)getservbyname( (char far *)serv_name, (char far*)proto )

int serv_port; 
char proto[10]; 
struct servent far *svptr; 
/* 假设 serv_port 及 proto 已先设好服务所用的 port number 及通讯协定 */

svptr = (struct servent FAR *)getservbyport( htons(serv_port), (char far*)proto) )

Winsock 环境下,我们能够查询到的服务资料都是存放在 local 的「services」档中;这个档所存放的都是 well-known 的服务,基本上我们是不需去更改它的。读者也可以将自己提供的服务加到这个档中,不过您所用的服务资料要公诸於世,不然别人的services 档中可是没有您的服务的资料哟。

最後的这组 getprotobyname() 及 getprotobynumber() 函式是用来取得一些「协定」的资料,比如 tcp、udp、igmp等。一般而言,我们是不太会用到的。

◎ getprotobyname():依照通讯协定 (protocol) 的名称来获取该通讯协定的其他资料。

格 式: struct protoent FAR * PASCAL FAR getprotobyname( const char FAR *name );

参 数: name 通讯协定名称

传回值: 成功 - 一指向 protoent 结构的指标

失败 - NULL (呼叫 WSAGetLastError() 可得知原因)

说明: 利用通讯协定的名称来得知该通讯协定的别名、编号等资料。

◎ getprotobynumber():依照通讯协定的编号来获取该通讯协定的其他资料。

格 式: struct protoent FAR * PASCAL FAR 
getprotobynumber( int number ); 
参 数: number 以 host order 排列方式的通讯协定编号 
传回值: 成功 - 一指向 protoent 结构的指标 
失败 - NULL (呼叫 WSAGetLastError() 可得知原因) 
说明: 利用通讯协定的编号来得知该通讯协定的名称、别名等资料。 

程式中呼叫方式分别如下:

struct protoent far *ptptr; 
char proto_name[20]; 
/* 假设 proto_name 已先设好协定名称 */

ptptr = (struct protoent FAR *)getprotobyname( (char far *)proto_name)

struct protoent far *ptptr; 
int proto_num; 
/* 假设 proto_num 已先设好协定编号 */ 
ptptr = (struct protoent FAR *)getprotobynumber( proto_num )

Winsock Stack 对於应用程式呼叫 getprotobyname() 及 getprotobynumber()的资料,是取自於 local 的「protocol」档;如无需要,我们也不用去变更这个档案的内容。

(图 2)hello 程式呼叫同步资料库函式

【非同步资料库函数】

Winsock 1.1 针对前面笔者所描述的 6 个同步资料库函式,也提供了相对的6 个非同步资料库函式,它们分别是WSAAsyncGetHostByName()、WSAAsyncGetHostByAddr()、WSAAsyncGetServByName()、WSAAsyncGetServByPort()、WSAAsyncGetProtoByName()、WSAAsyncGetProtoByNumber()。

由於它们取得的资料与同步资料库函式相同,所以笔者仅以WSAAsyncGetHostByName() 为例,说明这些非同步函式,并告诉各位读者,同步和非同步资料库函式不同的地方。

由字面来看,「非同步」的意思就是我们发出问题时,并不会马上得到答覆,而等到系统取到资料时再告知我们。没错,这些非同步资料库函式的作用就是这样。和 WSAAsyncSelect() 函式一样,我们要告诉 Winsock 系统一个接受通知讯息的视窗及讯息代码,以便系统通知我们。

我们呼叫同步资料库函式时,return 值是一个指到相对资料的暂存区,而这个资料暂存区是由系统所提供的;但是呼叫非同步资料库函式时,我们必须自己准备资料暂存区,并将此暂存区的位址当成参数,传给系统,以便系统用来储存取到的资料。读者们必须特别注意一点:在系统通知资料取得成功或失败前,千万不可将传给系统的资料暂存区删除释放,不然当系统取得资料要写入时,资料区已不见了,会导至当机的。除此之外,资料暂存区的大小一定要够大,才足够让系统用来存放取得的资料。(Winsock 规格中的建议值是MAXGETHOSTSTRUCT 1024 bytes 大小的暂存区,笔者认为太大了,100 byets差不多就太够了)

呼叫非同步资料库函式时,得到的 return 值是一个代码,此代码代表的就是此项呼叫在系统内的编号;由於是非同步,所以我们在得到答案前,仍可呼叫 WSACancelAsyncRequest() 函式来取消原先的呼叫,这个取消的动作就要利用到该代码了。另外,当我们收到结果通知时,wParam 的值也是这个代码;我们此时可以利用 WSAGETASYNCERROR(lParam) 来得知资料取得是成功或失败;如果失败的原因是原先传入的暂存区太小的话,我们亦可利用WSAASYNCGETBUFLEN(lParam) 来得知至少要多大的暂存区才够。

◎ WSAAsyncGetHostByName():利用某一 host 的名称来获取该 host 的资料。(非同步方式)

格 式: HANDLE PASCAL FAR WSAAsyncGetHostByName( HWND hWnd, 
unsigned int wMsg, const char FAR *name, char FAR *buf, int buflen );

参 数: hWnd 动作完成後,接受讯息的视窗 handle 
wMsg 传回视窗的讯息 
name host 名称 
buf 存放 hostent 资料的暂存区 
buflen buf 的大小 
传回值: 成功 - 代表此非同步动作的 handle 代码 
失败 - 0 (呼叫 WSAGetLastError() 可得知原因) 
说明: 此函式是利用 host 名称来获取其他的资料,如 host 的位址、别名,位址的型态、长度等。使用者呼叫此函式时必须传入要接收资料的视窗 handle、讯息代码、资料的存放位址指标等,以便得到资料时可以通知该视窗来使用资料。呼叫此函式後会马上回到使用者的呼叫点并传回一个 handle 代码,此代码可用来辨别此非同步动作或用来取消此非同步动作。当资料取得後,系统会送一个讯息到使用者指定的视窗。

◎ WSACancelAsyncRequest():取消某一未完成的非同步要求。

格 式: int PASCAL FAR WSACancelAsyncRequest( HANDLEhAsyncTaskHandle );

参 数:hAsyncTaskHandle 要取消的 task handle 代码 
传回值: 成功 - 0 
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因) 
说明: 此函式是用来取消原先呼叫但尚未完成的WSAAsyncGetXByY(),例如 WSAAsyncGetHostByName(),的动作。参数hAsyncTaskHandle 即为呼叫WSAAsyncGetXByY() 时传回之代码值。若是原先呼叫之非同步要求已经完成,则无法加以取消。

(图 3)hello 程式呼叫非同步资料库函式

【结语】

笔者已经为各位介绍了大部份 Winsock 应用程式设计时会用到的函式,不知读者中是否已有人开始练习自己写 Winsock 网路程式了吗?下一期,笔者会将剩下的函式都介绍完。再此笔者并期待各位除了使用别人设计的网路软体外,大家也都能自己练习设计出一些不错的网路应用软体,让世界其他国家的人知道台湾也有能人的;愿共勉之。

其它

接著笔者要再为各位介绍剩下的几个函式,包括 select()、setsockopt()、getsockopt(),以及变更系统的 Blocking Hook 函式时,所要用到的WSASetBlockingHook() 和 WSAUnhookBlockingHook()。

select【查询读写连接中断状态】

如果写过 UNIX BSD socket 程式的读者,一定都知道这个select()函式是很好用的。因为它可以帮您检查一整组(set)的 sockets是否可以读、写资料,也可以用来检查 socket 是否已和对方连接成功,或者是对方是否已将相对的socket 关闭等。但是在Winsock 1.1 及 MS Windows 3.X 「非强制性多工」的环境下,它是否仍是那麽好用呢?我们在使用它时,是否要注意些什麽呢?

◎ select():检查一或多个 Sockets 是否处於可读、可写或错误的状态。

格式:

int PASCAL FAR select( int nfds, fd_set FAR *readfds, fd_set FAR *writefds, fd_set FAR *exceptfds,

   const struct time val FAR *timeout )

参数:

nfds:     此参数在此并无作用

readfds:  要被检查是否可读的 Sockets

writefds: 要被检查是否可写的 Sockets

exceptfds:要被检查是否有错误的 Sockets

timeout:  此函式该等待的时间

传回值: 成功 - 符合条件的 Sockets 总数 (若 Timeout 发生,则为0)

失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明:使用者可利用此函式来检查 Sockets 是否有资料可被读取,或是有空间可以写入,或是有错误发生。Winsock 1.1 所提供的select() 函式与 UNIX BSD 的 select() 函式,在参数的个数及资料型态上是一样,都有nfds、readfds、writefds、exceptfds、及 timeout五个参数;但是 Winsock 的 nfds 是没有作用的,有这个参数的目的只是为了与 UNIX BSD 的 select() 函式一致。至於 readfds、writefds、exceptfds 同样是一组 sockets 的集合,所以您可以同时设定许多 sockets 的号码在这三个参数里面;当然这些 sockets 必须是属於您的这个应用程式所建立的。如果您设定的 socket 号码中有任一个不是属於您的这个程式的话,呼叫 select() 函式便会失败(错误码为 10038 WSAENOTSOCK)。

Winsock 同样也提供了一些 macros 来让您设定或检查 readfds、writefds、exceptfds 的值,包括有:

²           FD_ZERO(*set) -- 将 set 的值清乾净

²           FD_SET(s, *set) -- 将 s 加到 set 中

²           FD_CLR(s, *set) -- 将 s 从 set 中删除

²           FD_ISSET(s, *set) -- 检查 s 是否存在於 set 中

其中 s 代表的是某一个 socket 的号码,set 代表的就是 readfds、writefds 或 exceptfds。

读者们要知道参数readfds、writefds及exceptfds 都是「called by value- result」。

「called by value-result」的意思就是说,我们在将参数传给系统时,要先设启始值,并将这些参数的位址(address)告诉系统;而系统则会利用到这些值来做些运算或其他用途,最後并将结果再写回这些参数的位址中。因此这些参数的值在传入前和函式回返後可能会不同,所以每次呼叫select() 前对这些参数一定要重新设定它们的值。

假设我们要检查 socket 1 和 2 目前是否可以用来传送资料,以及 socket 3 是否有资料可读,我们不打算检查 sockets 是否有错误发生,所以 exceptfds 设为NULL。步骤大致如下:

FD_ZERO( &writefds );           //清除 writefds

FD_ZERO( &readfds );            //清除 readfds

FD_SET( 1, &writefds );         //将 socket 1 加到 writefds

FD_SET( 2, &writefds );         //将 socket 2 加到 writefds

FD_SET( 3, &readfds );          //将 socket 3 加到 readfds

select( ..., &readfds, &writefds, NULL, ...) /* 呼叫 select() 来检查事件 */

if(FD_ISSET( 1, &writefds ))    //检查 socket 1 是否可写

send( 1, data );           //呼叫 send() 一定成功

if (FD_ISSET( 2, &writefds ))   //检查 socket 2 是否可写

send( 2, data );           //呼叫 send() 一定成功

if (FD_ISSET( 3, &readfds ))    //检查 socket 2 是否可读

recv( 3, data );           //呼叫 recv() 一定成功

select() 函式的第五个参数「timeout」,是让我们用来设定 select 函式要等待(block)多久。兹述说如下:

(1)如果 timeout 设为「NULL」,那麽 select() 就会一直等到「至少」某一个 socket 的事件成立了才会 return,这和其他的blocking 函式一样。

select( ..., NULL )             /* blocking */

(2)如果 timeout 的值设为 {0, 0} (秒, 微秒),那麽 select() 在检查後,不管有没有 socket 的事件成立,都会马上return,而不会停留。

timeout.tv_sec = timeout.tv_usec = 0;

select( ..., &timeout )         /* non-blocking */

(3)如果 timout 设为 {m, n},那麽就会等到至少某一个 socket 的事件发生,或是时间到了(m 秒 n 微秒),才会 return。

timeout.tv_sec = m;

timeout.tv_usec = n;

select( ..., &timeout )         /* wait m secconds n microseconds */

在 UNIX 系统上,我们通常会利用select()来做「polling」的动作,检查事件是否发生;但是在 MS Windows 3.X 的环境下一直做polling 的动作一定要非常小心,不然可能会造成整个 Windows 系统停住(因为 CPU 都被您的程式占用了);所以使用时一定要注意「控制权释放」,不然就是「不要将 timeout 设为 {0,0}」(因为 timeout 设为 {0,0} 的话, Winsock 系统内部可能不会呼叫到Blocking Hook 函式来释放控制权)。UNIX 系统由於是「Time Sharing」的方式,所以并不会有类似的问题。(所谓polling 的动作是指在程式中有一个回圈,而在回圈内一直呼叫像select这样的函式做检查的动作

select()除了可以用来检查socket是否可读写外,对於non-blocking的socket在呼叫connect()後,也可利用select()的 writefds来检查连接是否已经成功了(当这个non-blocking的socket被设定在 writefds,且被 select 成功时);此外,我们亦可利用readfds来检查 TCP socket 连接的对方是否已经关闭了(当此socket被设定在readfds,且被select成功,但呼叫recv去收资料却 return 0 时)。

UNIX 系统上因为没有提供 WSAAsyncSelect() 函式,所以我们要用 select()函式来做 polling 的动作;但是 Winsock 系统上已经有了可以设定非同步事件的WSAAsyncSelect() 函式,为了让 MS Windows「讯息驱动」(message driven)的环境更有效率,读者们应该尽量使用 WSAAsyncSelect(),而少用 select() 的方式;这也是当初为什麽要定义一个 WSAAsyncSelect() 函式的最大目的。

setsockopt【变更socket options】

Winsock 1.1 也提供了一个变更 socket options 的 setsockopt() 函式;由於options 的项目很多,笔者仅就数个较会用到的项目来解说,其馀的项目请读者们自行研究。

◎ setsockopt():设定 Socket 的 options。

格式:

int PASCAL FAR setsockopt( SOCKET s, int level, int optname, const char FAR *optval, int optlen )

参数:

s:        Socket 的识别码

level:    option 设定的 level (SOL_SOCKET 或 IPPROTO_TCP)

optname:  option 名称

optval:   option 的设定值

optlen:   option 设定值的长度

传回值:

成功 – 0

失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式用来设定 Socket 的一些 options,藉以更改其动作。可更改的options 有:(详见 Winsock Spec. 54 页)

Option               Type

SO_BROADCAST         BOOL

SO_DEBUG             BOOL

SO_DONTLINGER        BOOL

SO_DONTROUTE         BOOL

SO_KEEPALIVE         BOOL

SO_LINGER            struct linger FAR*

SO_OOBINLINE         BOOL

SO_RCVBUF            int

SO_REUSEADDR         BOOL

SO_SNDBUF            int

TCP_NODELAY          BOOL

(1)SO_BROADCAST -- 适用於 UDP socket。其意义是允许UDP socket「广播」broadcast讯息到网路上。

(2)SO_DONTLINGER -- 适用於 TCP socket。其意义是让 socket 在呼叫 closesocket() 关闭时,能马上 return,而不用等到资料都送完後才从函式呼叫return;closesocket() 函式 return 後,系统仍会继续将资料全部送完後,才真正地将这个 socket 关闭。一个 TCP socket 在开启时的预设值即是 Don't Linger。

(3)SO_LINGER -- 适用於 TCP socket 来设定 linger 值之用。如果 linger的 值设为 0,那麽在呼叫 closesocket() 关闭socket 时,如果该 socket 的 output buffer 中还有资料的话,将会被系统所忽略,而不会被送出,此时 closesocket() 也会马上 return;如果 linger 值设为 n 秒,那麽系统就会在这个时间内,尝试去送出output buffer 中的资料,时间到了或是资料送完了,才会从 closesocket() 呼叫return。

(4)SO_REUSEADDR -- 允许 socket 呼叫 bind() 去设定一个已经用过的位址(含 port number)。

我们就以设定某个socket的 linger 值为例,看看程式中该如何呼叫 setsockopt() 这个函式:

struct linger Linger;

Linger.l_onoff = 1; //开启 linger 设定

Linger.l_linger = n; //设定 linger 时间为 n 秒

setsockopt( s, SOL_SOCKET, SO_LINGER, &Linger, sizeof(struct linger) )

相对地,如果我们想要知道目前的某个 option 的设定值,那麽就可以利用getsockopt() 函式来取得。

getsockopt【获取socket options】

◎ getsockopt():取得某一 Socket 目前某个 option 的设定值。

格式:

int PASCAL FAR getsockopt( SOCKET s, int level, int optname, char FAR *optval, int FAR *optlen )

参数:

S:         Socket 的识别码

Level:     option 设定的 level

optname:   option 名称

optval:    option 的设定值

Optlen:    option 设定值的长度

传回值:

成功 – 0

失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式用来获取目前 Socket的某些 options 设定值。同样地,我们仍以取得某个 socket 的 linger 值为例,看一下程式中应该如何呼叫 getsockopt():

struct linger Linger;

int opt_len = sizeof(struct linger);

getsockopt( s, SOL_SOCKET, SO_LINGER, &Linger, &opt_len)

WSASetBlockingHook

【什麽是 Blocking Hook 函式及如何设定自己的 Blocking Hook 函式】

什麽是「Blocking Hook」函式呢?在解释之前,我们要先来剖析一下Winsock 1.1 提供的 Blocking 函式(如 accept、connect等)的内部究竟做了哪些事?在 Winsock Stack 的 Blocking 函式内部,除了会检查一些条件外(比如该应用程式是否已呼叫过WSAStartup()?传入的参数是否正确?等等),便会进入一个类似下面的回圈:

for (;;)

{

/* 执行 Blocking Hook 函式 */

while (BlockingHook());

/* 检查使用者是否已经呼叫了 WSACancelBlockingCall() */

if (operation_cancelled()) break;

/* 检查动作是否完成了? */

if (operation_complete()) break;

}

现在我们可以很清楚地知道 Blocking 函式的回圈中,有三件重要的事:

(1)执行 Blocking Hook 函式

(2)检查使用者是否呼叫了 WSACancelBlockingCall()来取消此 Blocking 函式的呼叫?

(3)检查此 Blocking 函式的动作是否已经完成了?

读者们必须注意,不同的 Winsock Stack 在执行这三件事时的顺序可能会不相同;有的 Winsock Stack 可能会先检查 Blocking函式的动作是否已经完成了,然後再执行 Blocking Hook 函式;所以 Blocking Hook 函式有可能不会被呼叫到。待会解释完Blocking Hook 函式的重点後,读者们就可以知道笔者为什麽在前面告诉各位在使用 polling 方式时一定要非常小心了。

由上面的回圈,我们现在可以知道 Blocking Hook 函式的使用时机是让系统在等待 Blocking 函式完成前所呼叫的,它并不是给我们自己的应用程式所使用的。Winsock 系统本身内部就有一个预设的 Blocking Hook 函式;现在我们就来看一下这个预设的Blocking Hook 函式会做些什麽事?

BOOL DefaultBlockingHook(void)

{

MSG msg;

BOOL ret;

/* 取得下一个讯息;如果有,就处理它;如果没有,就释出控制权 */

ret = (BOOL) PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);

if (ret) {

TranslateMessage(&msg);

DispatchMessage(&msg);

}

return ret;

}

Blocking Hook 函式中很重要的地方就是:让 Blocking 函式在等待动作完成前能够处理其他讯息,或是释出 CPU 控制权,以便让其他的应用程式也有执行的机会。

现在回到前面一点的地方,大家仔细想一想:如果在一个 Winsock Stack 的Blocking 函式的回圈内,先检查 Blocking 函式的动作是否已经完成了,然後再执行 Blocking Hook 函式的话;那麽是否就有可能不会释出 CPU 控制权来让其他的程式有执行的机会呢?如果我们的程式中再有类似下面的一个回圈,那麽整个Windows 环境可能就会因我们的程式而 hang 住了。

for (;;)

{

FD_ZERO(&writefds);

FD_SET( s, &writefds );

timeout.tv_sec = timeout.tv_usec = 0;

n = select( 64, NULL, &writefds, NULL, &timeout );

if ( n > 0 )  break;

if ( n == 0)  continue;/* timeout */

...

}

send( s, data ... );

在这个回圈例子中,我们原是希望利用 select() 及 polling 的方式来检查 socket  的 output buffer 中是否尚有空间可写入资料?如果此时 output buffer 恰好满了, select() 函式中一检查到如此的情况,且 timeout 又是 {0,0},那麽就会马上return 0,而不会呼叫到 Blocking Hook 函式来释放 CPU 控制权给 Windows 环境中的其他程式(包括 Winsock 收送的 Protocol Stack);由於没有分配到 CPU 时间,所以 Winsock Kernel 便无法将 output buffer 中任何资料送出; Windows 系统因此就 hang 住了 !

Blocking Hook 函式中除了 CPU 控制权释放的问题外,还需注意什麽呢?大家再看一看前面 Blocking 函式的回圈;回圈内呼叫Blocking Hook 函式是包在另一个无穷的 while 回圈内。如果一个 Blocking Hook 函式的 return 值永远不为 0 的话,那麽也就永远被困在这个无穷回圈内了;所以我们在设计自己的 Blocking Hook 函式时一定也要非常小心这个 return 值。

知道了 Blocking Hook 函式的用途及设计 Blocking Hook 函式该注意的地方後,我们究竟要如何取代掉系统原有的 Blocking Hook 函式呢?那就要利用WSASetBlockingHook() 函式了。

◎      WSASetBlockingHook():建立应用程式指定的 blocking hook 函式。

格式: FARPROC PASCAL FAR WSASetBlockingHook( FARPROC   lpBlockFunc )

参数: lpBlockfunc 指向要装设的 blocking hook 函式的位址的指标

传回值:指向前一个 blocking hook 函式的位址的指标

说明: 此函式让使用者可以设定他自己的 Blocking Hook 函式,以取代原先系统预设的函式。被设定的函式将会在应用程式呼叫到「blocking」动作时执行。唯一可在使用者指定的 blocking hook 函式中呼叫的 Winsock 介面函式只有WSACancelBlockingCall()。假设我们自己设计了一个 Blocking Hook 函式叫 myblockinghook,那麽在程式中向 Winsock 系统注册的方法如下:(其中_hInst代表此task的 Instance)

FARPROC lpmybkhook = NULL;

lpmybkhook = MakeProcInstance( (FARPROC)myblockinghook, _hInst) );

WSASetBlockingHook( (FARPROC)lpmybkhook );

我们在设定自己的 Blocking Hook 程式後,仍可以利用WSAUnhookBlockingHook() 函式,来取消我们设定的 Blocking Hook 函式,而变更回原先系统内定的 Blocking Hook 函式。

◎      WSAUnhookBlockingHook():复原系统预设的 blocking hook 函式。

格  式: int PASCAL FAR WSAUnhookBlockingHook( void )

参  数: 无

传回值: 成功 – 0

         失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式取消使用者设定的 blocking hook 函式,而回复系统原先预设的 blocking hook 函式。

最後笔者要再说明一点,一个应用程式所设定的 Blocking Hook 函式,只会被这个应用程式所使用;其他的应用程式并不会执行到您设定的 Blocking Hook 函式的。另外若非极有必要,最好是不要任意变更系统的 Blocking Hook 函式;因为一旦您没有设计好的话,整个 Windows 环境可能就完蛋了。

【结语】

四期的「Winsock 应用程式设计篇」在此结束了;笔者除了介绍 Winsock API  外,也将自己亲身设计 winsock.dll 的经验与各位读者分享了;希望这几期的文章,对於国内想要在 Winsock 1.1 环境上开发网路应用程式的读者有些许的帮助。谢谢大家。

[Microsoft Windows-specific Extensions]

(1)   WSAAsyncGetHostByAddr():利用某一 host 的位址来获取该 host 的资料。(非同步方式)

格  式: HANDLE PASCAL FAR WSAAsyncGetHostByAddr( HWND hWnd,  unsigned int wMsg, const char FAR *addr, int len, int type, char FAR *buf, int buflen );

参  数:

hWnd 动作完成後,接受讯息的视窗 handle

wMsg  传回视窗的讯息

addr network 排列方式的位址

len addr 的长度

type PF_INET(AF_INET)

buf  存放 hostent 资料的区域

buflen buf 的大小

传回值: 成功 - 代表此 Async 动作的 handle

失败 - 0  (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式是利用位址来获取 host 的其他资料,如 host 的名称、别名, 位址的型态、长度等。使用者呼叫此函式时必须传入要接收资料的视窗handle、讯息代码、资料的存放位置指标等,以便得到资料时可以通知该视窗来使用资料。呼叫此函式後会马上回到使用者的呼叫点并传回一个 handle,此 handle 可用来辨别此非同步动作或用来取消此非同步动作。当资料取得後,会送一个讯息到使用者指定的视窗。

(2)   WSAAsyncGetHostByName():利用某一 host 的名称来获取该 host 的资料。 (非同步方式)

格  式: HANDLE PASCAL FAR WSAAsyncGetHostByName( HWND hWnd, unsigned int wMsg, const char FAR *name, char FAR *buf, int buflen );

参  数:

hWnd 动作完成後,接受讯息的视窗 handle

wMsg  传回视窗的讯息

name host 名称

buf  存放 hostent 资料的区域

buflen  buf 的大小

传回值: 成功 - 代表此 Async 动作的 handle

失败 - 0  (呼叫 WSAGetLastError() 可得知原因)

说明: 此函式是利用 host 名称来获取其他的资料,如 host 的位址、别名, 位址的型态、长度等。使用者呼叫此函式时必须传入要接收资料的视窗handle、讯息代码、资料的存放位置指标等,以便得到资料时可以通知该视窗来使用资料。呼叫此函式後会马上回到使用者的呼叫点并传回一个 handle,此handle 可用来辨别此非同步动作或用来取消此非同步动作。当资料取得後,会送一个讯息到使用者指定的视窗。

(3) WSAAsyncGetProtoByName():依照通讯协定的名称来获取该通讯协定的其他资料。(非同步方式)

格  式: HANDLE PASCAL FAR WSAAsyncGetProtoByName( HWND hWnd, unsigned int wMsg, const char FAR *name, char FAR *buf, int buflen );

参  数: hWnd 动作完成後,接受讯息的视窗 handle

wMsg 传回视窗的讯息

name 通讯协定名称

buf  存放 protoent 资料的区域

buflen buf 的大小

传回值: 成功 - 代表此 Async 动作的 handle

失败 - 0  (呼叫 WSAGetLastError() 可得知原因)

说明: 利用通讯协定的名称来得知该通讯协定的别名、编号等资料。使用者呼叫此函式时必须传入要接收资料的视窗 handle、讯息代码、资料的存放位置指标等,以便得到资料时可以通知该视窗来使用资料。呼叫此函式後会马上回到使用者的呼叫点并传回一个 handle,此  handle可用来辨别此 
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值