简单的 Winsock 应用程式设计(4)





林 军 鼐

笔者在前几期的文章中已经介绍了大部份 Winsock 1.1 所提供的应用程式发

展介面;笔者也相信有读者已经开始利用这些 API 来开发自己的网路应用程式

了。但是可能仍有部份读者还是不清楚自己该先有哪些发展工具才能开发

Winsock 1.1 的应用程式?



基本上,读者当然一定要有 Microsoft C 或 Borland C 之类的编译程式

(Compiler)才能编译您的程式;至於和 Winsock 有关的档案只有两个,一个

是『winsock.h』,另一个是『winsock.lib』。这两个档案,读者们可以利用

anonymous ftp 的方式从 SEEDNET 台北主机「tpts1.seed.net.tw」的

『UPLOAD/WINKING/Winsock_Documents』目录下取得。



接著笔者要再为各位介绍剩下的几个函式,包括 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 timeval 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 的值,包括有:(其中 s 代表的是某一个 socket 的号码,set 代表的就

是 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 中



读者们要知道参数 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

时)。





(图 1.) select 函式的几种不同用途



UNIX 系统上因为没有提供 WSAAsyncSelect() 函式,所以我们要用 select()

函式来做 polling 的动作;但是 Winsock 系统上已经有了可以设定非同步事件的

WSAAsyncSelect() 函式,为了让 MS Windows 「讯息驱动」(message driven)

的环境更有效率,读者们应该尽量使用 WSAAsyncSelect(),而少用 select() 的方

式;这也是当初为什麽要定义一个 WSAAsyncSelect() 函式的最大目的。



【变更 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 目前某个 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)



【什麽是 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) /* timeout */

continue;

...

}

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 中任何资料送出;回圈中由 select() 回*.

後,又回到回圈的最前面,然後又呼叫 select(),马上又 timeout......;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 );





(图 2.)设定自己的 Blocking Hook 函式



我们在设定自己的 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 环境可能就完蛋了。





(图 3.)使用自己的 Blocking Hook 函式时该注意事项



【结语】



四期的「Winsock 应用程式设计篇」在此结束了;笔者除了介绍 Winsock API

外,也将自己亲身设计 winsock.dll 的经验与各位读者分享了;希望这几期的文

章,对於国内想要在 Winsock 1.1 环境上开发网路应用程式的读者有些许的帮

助。谢谢大家。

--

! Origin: ● ZUCAI BBS ● From: 202.121.132.131



? Area: Internet.China ( Internet.中国 ) 哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪?

Msg#: 2223 Date: 24 Jun 96 22:29:48

From: Bg5hag Sky Read: Yes Replied: No

To: Mark: Save

Subj: Winsock函数(一)

哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪

@MSGID: 6:653/5000.0 31d71c51

Posted By: bg5hag (Sky) on board 'iNet'

Title: Winsock函数(一)

Date: Tue Jun 25 11:39:30 1996



== 林军鼐 ==



[简介]



本系统(WinKing)提供之 Windows Sockets API 介面乃是依照 1993年1月20日

公布之 WINSOCK 第1.1版(如附录)中所定义之函式 (routine); 包括了30个

Berkeley Software Distribution (BSD) 的函式以及16个符合 Windows

Message-driven 特性的函式。



BSD 函式包括:



accept() bind() closesocket() connect()

getpeername() getsockname() getsockopt() htonl()

htons() inet_addr() inet_ntoa() ioctlsocket()

listen() ntohl() ntohs() recv()

recvfrom() select() send() sendto()

setsockopt() shutdown() socket() gethostname()

gethostbyaddr() gethostbyname()

getprotobyname() getprotobynumber()

getservbyname() getservbyport()



Microsoft Windows-specific Extensions 函式包括:



WSAAsyncGetHostByAddr() WSAAsyncGetHostByName()

WSAAsyncGetProtoByName() WSAAsyncGetProtoByNumber()

WSAAsyncGetServByName() WSAAsyncGetServByPort()

WSAAsyncSelect() WSACancelAsyncRequest()

WSACancelBlockingCall() WSACleanup()

WSAGetLastError() WSAIsBlocking()

WSASetBlockingHook() WSASetLastError()

WSAStartup() WSAUnhookBlockingHook()



这些 API 介面适用於 Internet Protocol Suite (IPS,通常称之为 TCP/IP),

支援 Stream (TCP) 及 Datagram (UDP) Socket。



Stream (TCP) Socket 提供「双向」、「可靠」、「有次序」、「不重覆」

之资料传送。

Datagram (UDP) Socket 则提供「双向」之沟通,但没有「可靠」、「有次

序」、「不重覆」等之保证; 所以使用者可能会收到无次序、重覆之资料,

甚至资料在传输过程中也可能会遗漏。



[Blocking 与 Non-blocking 模式]



Blocking 模式:使用者呼叫此一模式之函式时,会进入此函式之内部,直到

条件或资料完全符合时再回到呼叫点。



Non-blocking 模式:使用者呼叫此一模式之函式时,进入此函式之内部,

依当时之条件或资料做适当之回覆,并不会停留在函式之内部到条件或资料

完全符合後才回应。



使用者必需注意的是,WINSOCK 定义之 Blocking 模式与一般 Unix 的不太相

同。WINSOCK定义允许应用程式在呼叫 Blocking 函式的同时,依旧能够处理其

它讯息 (Messages),包括Keyboard 及 Mouse 的事件;但是此时应用程式除了

能用 WSACancelBlockingCall() 函式来取消原先之 locking 动作或用

WSAIsBlocking() 函式来检查目前是否有 Blocking 动作仍在进行外,

「不可以」在原先呼叫之 Blocking 函式完成前再呼叫其它的 Socket 函式,

不然後者会失败且产生WSAINPROGRESS 的错误。



使用者呼叫 WSACancelBlockingCall()函式所取消的 Blocking 动作若不是

accept() 或者 select()的话,那麽之後唯一可呼叫的 Socket 函式只有

closesocket(),因为取消一个 Socket 的Blocking 动作会使其变成未定

(Indeterminate) 状态。



[Async (非同步) 模式]



使用者呼叫此一模式的函式时,并不会马上得到要求的资料;而是当要求的

动作完成後,系统再透过另一种方式来通知呼叫者。其好处是使用者不需等

到答覆後才可以再做其它的动作或要求。



WINSOCK定义的 Async 模式是以「PostMessage」的方式告知使用者其要求

已经完成;所以在呼叫此类函式时,必须告知 Windows Sockets DLL一些资

讯,包括接受讯息的视窗 handle及讯息编号等。



[函式概说]



[BSD Socket 程式库]



(1) accept():接受某一Socket的连接要求,以完成 Stream Socket 的连接。



格 式: SOCKET PASCAL FAR accept( SCOKET s,

struct sockaddr FAR *addr,

int FAR *addrlen );



参 数: s Socket的识别码

addr 存放来连接的彼端的位址

addrlen addr的长度



传回值: 成功 - 新的Socket识别码

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



说明: Server 端之应用程式呼叫此一函式来接受 Client 端要求之

Socket 连接动作;如果Server 端之 Socket 是为 Blocking 模式,且没有人

要求连接动作,那麽此一函式会Block 函式马上回覆错误。accept() 函式的答

覆值为一新的 Socket,此 Socket 不可再用来接受其它的连接要求;但是原

先之 Socket 仍可接受其他人的连接要求。





(2) bind():指定 Socket 的 Local 位址 (Address)。



格 式: int PASCAL FAR bind( SOCKET s,

const struct sockaddr FAR *name,

int namelen );



参 数: s Socket的识别码

name Socket的位址值,其格式为

struct sockaddr {

u_short sa_family;

char sa_data[14];

};

namelen name的长度



传回值: 成功 - 0

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



说明: 此一函式是指定 Local 位址及 Port 给某一未定名之 Socket。

使用者若不在意位址或 Port 的值,那麽他可以设定位址为 INADDR_ANY,及

Port 为 0;那麽Windows Sockets 会自动将其设定适当之位址及 Port

(1024 到 5000之间的值),使用者可以在此 Socket 真正连接完成後,呼

叫 getsockname() 来获知其被设定的值。





(3) closesocket():关闭某一Socket。



格 式: int PASCAL FAR closesocket( SOCKET s );



参 数: s Socket 的识别码



传回值: 成功 - 0

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



说明: 此一函式是用来关闭某一 Socket。

若是使用者原先对要关闭之 Socket 设定 SO_DONTLINGER,则在呼叫

此一函式後,会马上回覆,但是此一 Sokcet 尚未传送完毕的资料会继

续送完後才关闭。

若是使用者原先设定此 Socket 为 SO_LINGER,则有两种情况:

(a) Timeout 设为 0 的话,此一 Socket 马上重新设定 (reset),未传完或

未收到的资料全部遗失。

(b) Timeout 不为 0 的话,则会将资料送完,或是等到 Timeout 发生後才

关闭。





(4) connect():要求连接某一Socket到指定的对方。



格 式: int PASCAL FAR connect( SOCKET s,

const struct sockaddr

FAR *name,

int namelen );



参 数: s Socket 的识别码

name 此 Socket 想要连接的对方位址

namelen name的长度



传回值: 成功 - 0

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



说明: 此函式用来向对方要求建立连接。若是指定的对方位址为 0 的话,

会传回错误值。当连接建立完成後,使用者即可利用此一 Socket 来做传送或

接收资料之用了。





(5) getpeername():获取已连接成功之 Socket 的对方位址。



格 式: int PASCAL FAR getpeername( SOCKET s,

struct sockaddr FAR *name,

int FAR *namelen );



参 数: s Socket 的识别码

name 此 Socket 连接的对方位址

namelen name 的长度



传回值: 成功 - 0

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



说明: 此函式可用来取得已连接成功的 Socket 的彼端之位址资料。





(6) getsockname():获取 Socket 的 Local 位址资料。



格式: int PASCAL FAR getsockname( SOCKET s,

struct sockaddr FAR *name,

int FAR *namelen );



参 数: s Socket 的识别码

name 此 Socket 的 Local 位址

namelen name 的长度



传回值: 成功 - 0

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



说明: 此函式是用来取得已设定位址或已连接之 Socket 的本端位址资料。

若是此 Socket 被设定为 INADDR_ANY,则需等真正建立连接成功後才会传回

正确的位址。





(7) getsockopt():要求某一 Socket 目前状态设定的资料。



格式: int PASCAL FAR getsockopt( SOCKET s,

int level,

int optname,

char FAR *optval,

int FAR *optlen );



参 数: s Socket 的识别码

level 选项设定的 level

optname 选项名称

optval 选项的设定值

optlen 选项设定值的长度



传回值: 成功 - 0

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



说明: 此函式用来获取目前 Socket的某些状态设定值。

WINSOCK 提供之 level 只有 SOL_SOCKET 及 IPPROTO_TCP optname

则有以下 之选择:(参见WINSOCK 第 29、30 页之定义)



Value Type

-----------------------------------------------

SO_ACCEPTCONN BOOL

SO_BROADCAST BOOL

*SO_DEBUG BOOL

SO_DONTLINGER BOOL

*SO_DONTROUTE BOOL

*SO_ERROR int

*SO_KEEPALIVE BOOL

SO_LINGER struct linger FAR*

SO_OOBINLINE BOOL

*SO_RCVBUF int

SO_REUSEADDR BOOL

*SO_SNDBUF int

SO_TYPE int

TCP_NODELAY BOOL



(* 表暂不提供此功能选项)





(8) htonl():将一 32 位元 u_long 的值由 host 的排列方式转换成

network 的排列方式。



格 式: u_long PASCAL FAR htonl( u_long hostlong );



参 数: hostlong 一个 32 位元 host 排列方式的数目



传回值: 一个 32 位元 network 排列方式的数目



说明: 因为 network 的排列方式与 host 的排列方式可能不同,

所以我们需要此一函式来做转换。





(9) htons():将一 16 位元u_short 的值由 host 的排列方式转换成

network 的排列方式。



格 式: u_short PASCAL FAR htons( u_short hostshort );



参 数: hostshort 一个 16 位元 host 排列方式的数目



传回值: 一个 16 位元 network 排列方式的数目



说明: 因为 network 的排列方式与 host 的排列方式可能不同,

所以我们需要此一函式来做转换。





(10) inet_addr():将字串格式的位址转换成 32 位元 unsigned long 的格式。



格 式: unsigned long PASCAL FAR inet_addr( const char FAR *cp );



参 数: cp 一个代表位址的「点格式」(dotted) 字串



传回值: 成功 - 一个代表 Internet 位址的 unsigned long

失败 - INADDR_NONE



说明: 此函式将一「点格式」的位址字串转换成适用之Intenet位址。

「点格式」字串可为以下四种方式之任一:

(i) a.b.c.d (ii) a.b.c (iii) a.b (iv) a





(11) inet_ntoa():将一网路位址转换成「点格式」字串。



格 式: char FAR * PASCAL FAR inet_ntoa( struct in_addr in );



参 数: in 一个代表 Internet 位址的结构



传回值: 成功 - 一个代表位址的「点格式」(dotted) 字串

失败 - NULL



说明: 此函式将一 Internet 位址转换成「a.b.c.d」字串格式。





(12) ioctlsocket():控制 Socket 的模式。



格 式: int PASCAL FAR ioctlsocket( SOCKET s,

long cmd,

u_long FAR *argP );



参 数: s Socket 的识别码

cmd 指令名称

argP 指向 cmd 参数的指标



传回值: 成功 - 0

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



说明: 此函式用来获取或设定 Socket 的运作参数。其所提供的指令有:

(参见 WINSOCK 第 1.1 版 35、36 页)



FIONBIO -- 开关 non-blocking 模式

FIONREAD -- 自 Socket 一次可读取的资料量

SIOCATMARK -- OOB 资料是否已被读取完 (*暂不提供此功能)





(13) listen():设定 Socket 为监听状态,准备被连接。



格 式: int PASCAL FAR listen( SOCKET s, int backlog );



参 数: s Socket 的识别码

backlog 未真正完成连接前(尚未呼叫 accept() 前)

彼端的连接要求的最大个数



传回值: 成功 - 0

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



说明: 使用者可利用此函式来设定 Socket 进入监听状态,并设定最多

可有多少个在未真正完成连接前的彼端的连接要求。(目前最大值限制为 5, 最

小值为1)





(14) ntohl():将一 32 位元 u_long 的值由 network 排列方式转换成

host 排列方式。



格 式: u_long PASCAL FAR ntohl( u_long netlong );



参 数: netlong 一个 32 位元 network 排列方式的数目



传回值: 一个 32 位元 host 排列方式的数目



说明: 因为 network 的排列方式与 host 的排列方式可能不同,

所以我们需要此一函式来做转换。





(15) ntohs():将一 16 位元 u_short 的值由 network 排列方式转换成

host 排列方式。



格 式: u_short PASCAL FAR ntohs( u_short netshort );



参 数: netshort 一个 16 位元 network 排列方式的数目



传回值: 一个 16 位元 host 排列方式的数目



说明: 因为 network 的排列方式与 host 的排列方式可能不同,

所以我们需要此一函式来做转换。





(16) recv():自 Socket 接收资料。



格 式: int PASCAL FAR recv( SOCKET s,

char FAR *buf,

int len,

int flags );



参 数: s Socket 的识别码

buf 存放接收到的资料的暂存区

len buf 的长度

flags 此函式被呼叫的方式



传回值: 成功 - 接收到的资料长度 (若对方 Socket 已关闭,则为 0)

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



说明: 此函式用来自连接式的 Datagram Socket 或 Stream Socket

接收资料。对 Stream Socket 言,我们可以接收到目前有效的 (available)

资料,但其数量不超过 len 的大小。若是此 Socket 设定 SO_OOBINLINE,

且有 out-of-band 的资料未被读取,那麽只有 out-of-band 的资料被取出。

对 Datagram Socket 言,只取出第一个 datagram;若是该 datagram 大於使

用者提供的储存空间,那麽只有该空间大小的资料被取出,多馀的资料

将遗失,且回覆错误的讯息。 flags 的值可为 MSG_PEEK、MSG_OOB

(*暂不提供此功能)的组合。(参考 WINSOCK 第1.1版41 页)





(17) recvfrom():读取一个 Datagram,并储存资料来源的位址。



格 式: int PASCAL FAR recvfrom( SOCKET s,

char FAR *buf,

int len,

int flags,

struct socketaddr FAR *from,

int FAR *fromlen );



参 数: s Socket 的识别码

buf 存放接收到的资料的暂存区

len buf 的长度

flags 此函式被呼叫的方式

from 资料来源的位址

fromlen from 的大小



传回值: 成功 - 接收到的资料长度 (若对方 Socket 已关闭,则为 0)

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



说明: 此函式用来读取资料并记录资料来源的位址。

对 Stream Socket 言,其作用与 recv() 相同,参数 from 及 fromlen 将不

被用到。





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



格 式: int PASCAL FAR select( int nfds,

fd_set FAR *readfds,

fd_set FAR *writefds,

fd_set FAR *exceptfds,

const struct timeval FAR *timeout );



参 数: nfds 此参数在此并无作用

readfds 要被检查是否可读的 Sockets

writefds 要被检查是否可写的 Sockets

exceptfds 要被检查是否有错误的 Sockets (*暂无作用)

timeout 此函式该等待的时间。若为 NULL 时,

表示 blocking,此函式会等到有事件发生。



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

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



说明: 使用者可利用此函式来检查 Sockets 是否有资料可被读取,

或是有空间可以写入,或是有错误发生。



(19) send():使用连接式的 Socket 传送资料。



格 式: int PASCAL FAR send( SOCKET s,

const char FAR *buf,

int len,

int flags );



参 数: s Socket 的识别码

buf 存放要传送的资料的暂存区

len buf 的长度

flags 此函式被呼叫的方式



传回值: 成功 - 送出的资料长度

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



说明: 此函式适用於连接式的 Datagram 或 Stream Socket 来传送资料。

对 Datagram Socket 言,若是 datagram 的大小超过限制,则将不会送出

任何资料,并会传回错误值。若是传送 (transport) 系统内之储存空间不

够存放这些要传送的资料,send() 将会被 block 住,除非该 Socket 被设

定为 non-blocking 模式。使用者亦须注意 send()函式执行完成,并不表

示资料已经成功地送抵对方了。 flags 的值可设为 MSG_DONTROUTE

(*暂不提供此功能)及 MSG_OOB 的组合。(参见 WINSOCK第1.1版48页)





(20) sendto():将资料送到指定的目的地。



格 式: int PASCAL FAR sendto( SOCKET s,

const char FAR *buf,

int len,

int flags,

const struct sockaddr FAR *to,

int tolen );



参 数: s Socket 的识别码

buf 存放要传送的资料的暂存区

len buf 的长度

flags 此函式被呼叫的方式

to 资料要送达的位址

tolen to 的大小



传回值: 成功 - 送出的资料长度

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



说明: 此函式适用於 Datagram 或 Stream Socket 来传送资料到指定的位址。

对 Datagram Socket 言,若是 datagram 的大小超过限制,则将不会送出

任何资料,并会传回错误值。对 Stream Socket 言,其作用与 send() 相

同;参数 to 及 tolen 在此并无作用。 若是传送 (transport) 系统内之储

存空间不够存放这些要传送的资料,sendto() 将会被 block 住,除非该

Socket 被设定为 non-blocking 模式。使用者亦须注意 sendto() 函式执行

完成,并不表示资料已经成功地送抵对方了。 flags 的值可设为 MSG_DONTROUTE

(*暂不提供此功能)及 MSG_OOB 的组合。(参见 WINSOCK第1.1版51页)





(21) setsockopt():设定 Socket 的状态。



格 式: int PASCAL FAR setsockopt( SOCKET s,

int level,

int optname,

const char FAR *optval,

int optlen );



参 数: s Socket 的识别码

level 选项设定的 level

optname 选项名称

optval 选项的设定值

optlen 选项设定值的长度



传回值: 成功 - 0

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



说明: 此函式用来设定 Socket 的一些选项,藉以更改其动作。

可更改的选项有: (参见WINSOCK第1.1版54页)



Value 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



(* 表暂不提供此功能选项)





(22) shutdown():停止 Socket 接收/传送的功能。



格 式: int PASCAL FAR shutdown( SOCKET s, int how );



参 数: s Socket 的识别码

how 代表该停止那些动作的标帜



传回值: 成功 - 0

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



说明: 此函式用来停止 Socket 的後续接收或传送的功能。

若 how 的值为 0,则不再接收资料。

若 how 的值为 1,则不再允许传送资料。

若 how 的值为 2,则不再接收且不再传送资料。

shutdown() 函式并没有将 Socket 关闭,所以该 Socket 所占用之资源必

须在呼叫closesocket() 之後才会释放。





(23) socket():建立Socket。



格 式: SOCKET PASCAL FAR socket( int af,

int type,

int protocol );



参 数: af 目前只提供 PF_INET(AF_INET)

type Socket 的型态 (SOCK_STREAM、SOCK_DGRAM)

protocol 通讯协定(如果使用者不指定则设为0)



传回值: 成功 - Socket 的识别码

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



说明: 此函式用来建立一 Socket,并为此 Socket 建立其所使用的资源。

Socket 的型态可为 Stream Socket 或 Datagram Socket。





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



格 式: struct hostent FAR * PASCAL FAR

gethostbyaddr( const char FAR *addr, int len, int type );



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

len addr 的长度

type PF_INET(AF_INET)



传回值: 成功 - 指向 struct hostent 的指标

struct hostent {

char FAR * h_name;

char FAR * FAR * h_aliases;

short h_addrtype;

short h_length;

char FAR * FAR * h_addr_list;

}

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



说明: 此函式是利用位址来获取 host的其他资料,如 host 的名称、

别名,位址的型态、长度等。





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



格 式: struct hostent FAR * PASCAL FAR

gethostbyname( const char FAR *name );



参 数: name host 的名称



传回值: 成功 - 指向 struct hostent 的指标

struct hostent {

char FAR * h_name;

char FAR * FAR * h_a








像一只青蛙在井底跳啊跳,什么时候才能跳出去呢…………………………

2002-12-12 08:22 #35



风清高
中校

会员



UID 186
精华 6
积分 2088
帖子 990
阅读权限 40
注册 2002-11-4
第三十篇


简单实用的网络编程调试工具

作者:杨沙洲


调试是程序设计的一大难关,甚至难度超过编程本身,而网络应用程序由
于具有异地性、多进程性、交互性等特点,更难于用普通的调试工具查找
逻辑错误。特别是开发基于Client/Server结构的网络应用时,进行程序跟
踪、中断输出等传统的调试手段都难以确切找出错误所在。本人在开发类
似Zmud的MUD客户端程序时,就曾经因为一个字符回显错误而调试了四五个
小时。
---- 如果开发一种类似管道一样的网络中间程序,将客户方和服务方的通
讯过程依次记录下来,然后进行离线分析,则往往会收到事半功倍的效果。

---- 程序结构很简单,仅由两个进程组成,后台启动后驻留等待连接。父
进程监听本地预定的临时端口,收到客户方请求后向服务方发连接请求,
连接建立后生成子进程,自身负责接收客户方输出并传送给服务方;子进程
继承父进程的Socket描述符,负责接收服务方输出并传送给客户方。整体结
构如下图:(略)

---- 程序在完成通讯的同时将双方的通讯内容记录在工作目录的预定文件
中,并冠以Client 和Server的前缀,以区分不同的信息源。值得注意的是,
标准I/O库函数是缓冲输出的,不能记录通讯的顺序,必须使用原始I/O(read/write)

---- 结果文件以字节为单位进行记录,包括传输的ASCII码和相应的可显示
字符。以编写处理Telnet的程序为例,为了了解Telnet协议中建立登录连接
以前的协商过程,可以使用本程序。

---- 假设本程序的可执行文件名为netpipe,本地IP为172.10.33.28,远地
Telnet服务器IP为172.20.8.144,程序预先指定监听本地端口8888。首先运
行netpipe,然后运行Telnet :

$netpipe 172.20.8.144 23&
$telnet 172.10.33.28 8888
.
.
.
Ctrl-D
省缺的记录文件将如下显示:
Server :
255
253
24
255
253
32
255
253
35 #
255
253
39 '
Client :
255
251
.
.
.


---- 在外观上本次Telnet会晤与直接Telnet没有任何区别,唯一不同的就
是本次通讯将生成一个记录文件。本程序在Linux Kernel 2.2.5下用gcc 2.3编
译通过,可方便地移植到不同系统,包括Windows 95/NT。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值