简单的 Winsock 应用程式设计2.docx

上传人:b****6 文档编号:6952534 上传时间:2023-01-13 格式:DOCX 页数:12 大小:21.23KB
下载 相关 举报
简单的 Winsock 应用程式设计2.docx_第1页
第1页 / 共12页
简单的 Winsock 应用程式设计2.docx_第2页
第2页 / 共12页
简单的 Winsock 应用程式设计2.docx_第3页
第3页 / 共12页
简单的 Winsock 应用程式设计2.docx_第4页
第4页 / 共12页
简单的 Winsock 应用程式设计2.docx_第5页
第5页 / 共12页
点击查看更多>>
下载资源
资源描述

简单的 Winsock 应用程式设计2.docx

《简单的 Winsock 应用程式设计2.docx》由会员分享,可在线阅读,更多相关《简单的 Winsock 应用程式设计2.docx(12页珍藏版)》请在冰豆网上搜索。

简单的 Winsock 应用程式设计2.docx

简单的Winsock应用程式设计2

简单的Winsock应用程式设计

(2)

 

 

在前一期的文章中,笔者为大家介绍了如何在Winsock环境下,建立主从

架构(Client/Server)的TCPsocket的连接建立与关闭;今天笔者将继续为大家

介绍如何利用TCPsocket来收送资料,并详细解说WSAAsyncSelect函式中的

FD_READ及FD_WRITE事件(笔者曾发现有相当多人对这两个事件甚不了

解)。

 

相信读者们已经知道TCPsocket的连接是在Client端呼叫connect函式成

功,且Server端呼叫accept函式後,才算完全建立成功;当连接建立成功後,

Client及Server也就可以利用这个连接成功的socket来传送资料到对方,或是

收取对方送过来的资料了。

 

(图1.TCPsocket的资料收送)

 

在介绍资料的收送前,笔者先介绍一下TCPsocket与UDPsocket在传送资

料时的特性:

 

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

资料传送。

 

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

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

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

 

由於UDPSocket在传送资料时,并不保证资料能完整地送达对方,所以我

们常用的一些应用程式(如telnet、mail、ftp、news...等)都是采用TCP

Socket,以保证资料的正确性。

(TCP及UDP封包的传送协定不在我们讨论□

围,想要了解的读者们,请自行参考相关书籍)

 

TCP及UDPSocket都是双向的,所以我们是利用同一个Socket来做传送及

收取资料的动作;一般言TCPSocket的资料送、收是呼叫send()及recv()这两

个函式来达成,而UDPSocket则是用sendto()及recvfrom()这两个函式。

不过

TCPSocket也可用sendto()及recvfrom()函式,UDPSocket同样可用send()及

recv()函式;这一点我们稍後再加以解释。

 

现在我们先看一下send()及recv()的函式说明,并回到我们的前一期程

式。

 

◎send():

使用连接式(connected)的Socket传送资料。

格式:

intPASCALFARsend(SOCKETs,constcharFAR*buf,

intlen,intflags);

参数:

sSocket的识别码

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

lenbuf的长度

flags此函式被呼叫的方式

传回值:

成功-送出的资料长度

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

说明:

此函式适用於连接式的Datagram或StreamSocket来传送资料。

DatagramSocket言,若是datagram的大小超过限制,则将不会送出任何资料,并

会传回错误值。

对StreamSocket言,Blocking模式下,若是传送(transport)系统

内之储存空间(outputbuffer)不够存放这些要传送的资料,send()将会被block

住,直到资料送完为止;如果该Socket被设定为Non-Blocking模式,那麽将视目

前的outputbuffer空间有多少,就送出多少资料,并不会被block住。

使用者亦须

注意send()函式执行完成,并不表示资料已经成功地送抵对方了,而是已经放到

系统的outputbuffer中,等待被送出。

flags的值可设为0或MSG_DONTROUTE

及MSG_OOB的组合。

(参见WINSOCK第1.1版48页)

 

◎recv():

自Socket接收资料。

格式:

intPASCALFARrecv(SOCKETs,charFAR*buf,intlen,intflags);

参数:

sSocket的识别码

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

lenbuf的长度

flags此函式被呼叫的方式

传回值:

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

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

说明:

此函式用来自连接式的DatagramSocket或StreamSocket接收资料。

对StreamSocket言,我们可以接收到目前inputbuffer内有效的资料,但其数量

不超过len的大小。

若是此Socket设定SO_OOBINLINE,且有out-of-band的资

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

对DatagramSocket言,只取

出第一个datagram;若是该datagram大於使用者提供的储存空间,那麽只有该空

间大小的资料被取出,多馀的资料将遗失,且回覆错误的讯息。

另外如果Socket

为Blocking模式,且目前inputbuffer内没有任何资料,则recv()将block到有任

何资料到达为止;如果为Non-Blocking模式,且inputbuffer无任何资料,则会马

上回覆错误。

参数flags的值可为0或MSG_PEEK、MSG_OOB的组合;

MSG_PEEK代表将资料拷贝到使用者提供的buffer,但是资料并不从系统的input

buffer中移走;0则表示拷贝并移走。

(参考WINSOCK第1.1版41页)

 

【Server端的资料收送及关闭Socket?

 

在前一期中,我们说建立的是一个Asynchronous模式的Server;程式中,

我们曾对listen_sd这个Socket呼叫WSAAsyncSelect()函式,并设定

FD_ACCEPT事件,所以当Client与我们连接时,系统会传给我们一个

ASYNC_EVENT讯息(请参见前一期文章内容);我们在收到讯息并判断是

FD_ACCEPT事件,於是呼叫accept()来建立连接。

 

my_sd=accept(listen_sd,(structsockaddrfar*)&sa,&sa_len)

 

我们在呼叫完accept()函式,成功地建立了Server端与Client端的连接後,

此时便可利用新建的Socket(my_sd)来收送资料了。

由於我们同样希望用

Asynchronous的方式,因此要再利用WSAAsyncSelect()函式来帮新建的

Socket设定一些事件,以便事件发生时WinsockStack能主动通知我们。

由於我

们的Server是被动的接受Client的要求,然後再做答覆,所以我们设定

FD_READ事件;我们也希望WinsockStack在知道Client关闭Socket时,能主

动通知我们,所以同时也设定FD_CLOSE事件。

(读者须注意,我们设定事件

的Socket号码是呼叫accept後传回的新Socket号码,而不是原先监听状态的

Socket号码)

 

WSAAsyncSelect(my_sd,hwnd,ASYNC_EVENT,FD_READ|FD_CLOSE)

 

在这里,我们同样是利用hwnd这个视窗及ASYNC_EVENT这个讯息;在

前文中,笔者曾告诉各位,在收到ASYNC_EVENT讯息时,我们可以利用

WSAGETSELECTEVENT(lParam)来判断究竟是哪一事件(FD_READ或

FD_CLOSE)发生了;所以并不会混淆。

那我们到底在什麽时候会收到

FD_READ或FD_CLOSE事件的讯息呢?

 

【FD_READ事件】

 

我们会收到FD_READ事件通知我们去读取资料的情况有:

 

(1)呼叫WSAAsyncSelect函式来对此Socket设定FD_READ事件时,

inputbuffer中已有资料。

(2)原先系统的inputbuffer是空的,当系统再收到资料时,会通知我们。

(3)使用者呼叫recv或recvfrom函式,从inputbuffer读取资料,但是并

没有一次将资料读光,此时会再驱动一个FD_READ事件,表示仍有资料在

inputbuffer中。

 

读者必须注意:

如果我们收到FD_READ事件通知的讯息,但是我们故意

不呼叫recv或recvfrom来读取资料的话,尔後系统又收到资料时,并不会再次

通知我们,一定要等我们呼叫了recv或recvfrom後,才有可能再收到

FD_READ的事件通知。

 

【FD_CLOSE事件】

 

当系统知道对方已经将Socket关闭了的情况下(收到FIN通知,并和对方

做关闭动作的hand-shaking),我们会收到FD_CLOSE的事件通知,以便我

们也能将这个相对的Socket关闭。

FD_CLOSE事件只会发生於TCPSocket,因

为它是connection-oriented;对於connectionless的UDPSocket,即使设了

FD_CLOSE,也不会有作用的。

 

程式中,当Client端送一个要求(request)来时,系统会以

ASYNC_EVENT讯息通知我们的hwnd视窗;我们在利用

WSAGETSELECTEVENT(lParam)及WSAGETSELECTERROR(lParam)知道是

FD_READ事件及检查无误後,便呼叫recv()函式来收取Client端送来的资料。

 

recv(wParam,&data,sizeof(data),0)

 

笔者在前一期文章中也曾提到说,FD_XXXX事件发生,收到讯息时,视

窗handle被呼叫时的参数wParam代表的就是事件发生的Socket号码,所以此

处wParam的值也就是前面提到的my_sd这个Socket号码。

recv()的第四个参

数设为0,表示我们要将资料从系统的inputbuffer中读取并移走。

 

收到要求後,我们要答覆Client端,也就是要送资料给Client;这时我们就

要利用send()这个函式了。

 

我们先将资料放到data这个资料暂存区,然後呼叫send()将它送出,我们

利用的也是wParam(my_sd)这个同样的Socket来做传送的动作,因为它是双向

的。

 

send(wParam,&data,strlen(data),0)

 

Server与Client收送资料一段时间後(资料全部收送完毕),如果Client端

先呼叫closesocket()将它那端的Socket关闭,那麽系统在知道後,会通知我们

一个FD_CLOSE事件的讯息,此时我们也可以呼叫closesocket()将我们这端的

Socket关闭了;当然我们也可以呼叫closesocket()先主动关闭我们这端的

Socket。

 

【Client端的资料收送及关闭Socket】

 

我们例子的Client是采Blocking模式,所以在呼叫connect()函式与Server

连接时,可能会等一下子才成功;connect()函式返回後,且无错误发生的话,

Client与Server端的TCPsocket连接就算成功了。

这时,我们便可利用这个连

接成功的Socket来送收资料了。

由於我们并没有要设定为Asynchronous模式,

所以也不用呼叫WSAAsyncSelect()来设定事件。

 

Client端通常是会先主动发出要求到Server端,因此我们呼叫send()来传送

此一资料。

我们的资料量很小,所以并不会被send()函式Block住;不过如果

您要送的资料量很大,那麽可能会等一段时间才会自send()函式返回;也就是

说必须等资料都放到系统的outputbuffer後才会返回;这是因为我们Client的

Socket是阻拦模式。

如果我们用的是非阻拦模式的Socket,那麽send()函式会

视系统的outputbuffer的空间有多少,只拷贝那麽多的资料到outputbuffer,然

後就返回,并告知使用者送出了多少资料,并不须等所有资料都放到output

buffer才返回。

 

我们将要求放在data资料暂存区,然後呼叫send()将要求送出。

资料送出

後,我们呼叫recv()来等待Server端的答覆。

 

send(mysd,data,strlen(data),0)

 

recv(mysd,&data,sizeof(data),0)

 

由於我们Client端是Blocking模式,所以recv()会一直Block住,直到下

列的情况之一发生,才会返回。

 

(1)Server端送来资料。

(此时return值是读取的资料长度)

(2)Server端将相对的Socket关闭了。

(此时的return值会是0)

(3)Client端自己呼叫WSACancelBlockingCall()来取消recv()的呼叫。

(此时return值是SOCKET_ERROR错误,错误码10004WSAEINTR)

 

同样地,资料全部送收完毕後,我们也呼叫closesocket()来将Socket关

闭。

 

◎WSACancelBlockingCall():

取消目前正在进行中的blocking动作。

格式:

intPASCALFARWSACancelBlockingCall(void);

参数:

传回值:

成功-0

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

说明:

此函式用来取消该应用程式正在进行中的blocking动作。

通常的

使用时机有:

(a)Blocking动作正在进行中,该应用程式又收到某一讯息

(Mouse、Keyboard、Timer等),则可在处理该讯息的段落中呼叫此函式。

(b)

Blocking动作正在进行中,而WindowsSockets又呼叫回应用程式?

「blockinghook」函式时,在该函式内可呼叫此函式来取消blocking动作。

使用者必须注意,在某一Winsockblocking函式动作进行时,除了

WSAIsBlocking()及WSACancelBlockingCall()外,不可以再呼叫其它任何

WindowsSocketsDLL提供的函式,否则会产生错误。

另外若取消的

blocking动作不是accept()或select()的话,那麽该Socket可能会处於未定

状态,使用者最好是呼叫closesocket()来关闭该Socket,而不该再对它做任

何动作。

 

(图2.)demoserv与democlnt在资策会WinKing上收送资料的画面

 

(图3.)demoserv与democlnt在资策会WinKing上关闭Socket後的画面

 

介绍完了TCPSocket的资料收送,笔者接著为读者介绍sendto()及

recvfrom()这两个函式,以及许多人可能很容易搞错的FD_WRITE事件。

 

【sendto及recvfrom函式】

 

一般言,TCPSocket使用的是send()及recv()这两个函式;而UDPSocket

用的是sendto()及recvfrom()函式。

这是因为TCP是Connection-oriented,必须

做完Socket真正的连接程序後,才可以开始收送资料,此时系统已经知道了连

接的对方,所以我们不用再指定资料要送到哪里。

而UDP是Connectionless,

收送资料的双方并没有建立真正的连接,所以我们要利用sendto()及recvfrom()

来指定收资料的对方及获知是谁送资料给我们?

 

TCPSocket也可以用sendto()及recvfrom()来送收资料,只是此时这两个

函式的最後两个参数没有作用,会被系统所忽略。

而UDPSocket如果呼叫了

connect()函式来指定对方的位址(这个connect并不会真的和对方做连接的动

作,而是告知我们本身的系统说我们只想收、送何方的资料),那麽也可以利

用send()及recv()来送收资料。

 

◎sendto():

将资料送到使用者指定的目的地。

格式:

intPASCALFARsendto(SOCKETs,constcharFAR*buf,

intlen,intflags,conststructsockaddrFAR*to,int

tolen);

参数:

sSocket的识别码

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

lenbuf的长度

flags此函式被呼叫的方式

to资料要送达的位址

tolento的大小

传回值:

成功-送出的资料长度

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

说明:

此函式适用於Datagram或StreamSocket来传送资料到指定的

位址。

对DatagramSocket言,若是datagram的大小超过限制,则将不会

送出任何资料,并会传回错误值。

对StreamSocket言,其作用与send()相

同;参数to及tolen的值将被系统所忽略。

若是传送(transport)系统内之储

存空间不够存放这些要传送的资料,sendto()将会被block住,直到资料都被

送出;除非该Socket被设定为non-blocking模式。

使用者亦须注意sendto()

函式执行完成,并不表示资料已经成功地送抵对方了,而可能仍在系统的output

buffer中。

flags的值可设为0、MSG_DONTROUTE及MSG_OOB的组合。

(参见WINSOCK第1.1版51页)

 

◎recvfrom():

读取资料,并储存资料来源的位址。

格式:

intPASCALFARrecvfrom(SOCKETs,charFAR*buf,intlen,intflags,

structsocketaddrFAR*from,intFAR*fromlen);

参数:

sSocket的识别码

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

lenbuf的长度

flags此函式被呼叫的方式

from资料来源的位址

fromlenfrom的大小

传回值:

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

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

说明:

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

对DatagramSocket

(UDP)言,一次读取一个Datagram;对StreamSocket(TCP)言,其作用与

recv()相同,参数from及fromlen的值会被系统忽略。

如果Socket为Blocking模

式,且目前inputbuffer内没有任何资料,则recvftom()将block到有任何资料到

达为止;如果为Non-Blocking模式,且inputbuffer无任何资料,则会马上回覆错

误。

 

【FD_WRITE事件】

 

笔者在前面介绍过FD_READ事件的发生时机,现在继续介绍FD_WRITE

这个较易使人混淆的事件,因为真的有相当多的人对此一事件的发生不明了。

 

由字面上看,FD_WRITE应该是要求系统通知我们某个Socket现在是否可

以呼叫send()或sendto()来传送资料?

答案可以说「是」,但是它和FD_READ

却又有不同的地方。

 

在前面我们知道呼叫一次recv()後,如果inputbuffer中尚有资料未被取出

的话,系统会再通知我们一次FD_READ。

那麽如果我们呼叫一次send()後,

系统的outputbuffer仍有空间可写入的话,它是否会再通知我们一个

FD_WRITE,叫我们继续传送资料呢?

这个答案就是「否定」的了!

系统并不

会再通知我们了。

 

系统会通知我们FD_WRITE事件的讯息,只有下列几种情况:

 

(1)呼叫WSAAsyncSelect()来设定FD_WRITE事件时,Socket已经可以

传送资料(TCPscoket已经和对方连接成功了,或UDPsocket已建立完成),

且目前outputbuffer仍有空间可写入资料。

(2)呼叫WSAAsyncSelect()来设定FD_WRITE事件时,Socket尚不能传

送资料,不过一旦Socket与对方连接成功,马上就会收到FD_WRITE的通

知。

(3)呼叫send()或sendto()传送资料时,系统告知错误,且错误码为

10035WSAEWOULDBLOCK(呼叫WSAGetLastError()得知这项错误),这

时表示outputbuffer已经满了,无法再写入任何资料(此时即令呼叫再多次的

send()也都一定失败);一旦系统将部份资料成功送抵对方,空出outputbuffer

後,便会送一个FD_WRITE给使用者,告知可继续传送资料了。

换句话说,读

者在呼叫send()传送资料时,只要不是返回错误10035的话,便可一直继续呼

叫send()来传送资料;一旦send()回返错误10035,那麽便不要再呼叫send()

传送资料,而须等收到FD_WRITE後,再继续传送资料。

 

【结语】

 

在这一期的文章中,笔者介绍了各位有关TCPSocket的资料收、送方式及

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 幼儿教育 > 幼儿读物

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1