您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 管理学资料 > 简单的 Winsock 应用程式设计
TCP连接建立与关闭相信各位读者现在对於Winsock的定义、系统环境,以及一些WinsockStack及Winsock应用程式,都有基本的认识了。接下来笔者希望能分几期为各位读者介绍一下简单的Winsock网路应用程式设计。我们将以Winsock1.1规格所定义的46个应用程式介面(API)为基础,逐步来建立一对TCPsocket主从架构(Client/Server)的程式。在这两个程式中,Server将使用Winsock提供的「非同步」(asynchronous)函式来建立socket连结、关闭、及资料收送等等;而Client则采类似传统UNIX的「阻拦式」(blocking)。由於我们的重点并不在於MSWindowsSDK的程式设计,所以我们将使用最简便的方式来显示讯息;有关MSWindows程式的技巧,请各位读者自行研究相关的书籍及文章。今天我们先要看一下主从架构TCPsocket的建立连结(connect)及关闭(close)。以前笔者曾简单地介绍过主从架构的概念,现在我们再以生活上更浅显的例子来说明一下,读者稍後也较容易能明白笔者的叙述。我们可以假设Server就像是电信局所提供的一些服务,比如「104查号台」或「112障碍台」。(1)电信局先建立好了一个电话总机,这就像是呼叫socket()函式开启了一个socket。(2)接著电信局将这个总机的号码定为104,就如同我们呼叫bind()函式,将Server的这个socket指定(bind)在某一个port。当然电信局必须让用户知道这个号码;而我们的Client程式同样也要知道Server所用的port,待会才有办法与之连接。(3)电信局的104查号台底下会有一些自动服务的分机,但是它的数量是有限的,所以有时你会拨不通这个号码(忙线)。同样地,我们在建立一个TCP的Serversocket时,也会呼叫listen()函式来监听等待;listen()的第二个参数即是waitingqueue的数目,通常数值是由1到5。(事实上这两者还是有点不一样。)(4)用户知道了电信局的这个104查号服务,他就可以利用某个电话来拨号连接这个服务了。这就是我们Client程式开启一个相同的TCPsocket,然後呼叫connect()函式去连接Server指定的那个port。当然了,和电话一样,如果waitingqueue满了、与Server间线路不通、或是Server没提供此项服务时,你的连接就会失败。(5)电信局查号台的总机接受了这通查询的电话後,它会转到另一个分机做服务,而总机本身则再回到等待的状态。Server的listeningsocket亦是一样,当你呼叫了accept()函式之後,Server端的系统会建立一个新的socket来对此连接做服务,而原先的socket则再回到监听等待的状态。(6)当你查询完毕了,你就可以挂上电话,彼此间也就离线了。Client和Server间的socket关闭亦是如此;不过这个关闭离线的动作,可由Client端或Server端任一方先关闭。有些电话查询系统不也是如此吗?接下来,我们就来看主从架构的TCPsocket是如何利用这些Winsock函式来达成的;并利用资策会资讯技术处的「WinKing」这个WinsockStack中某项功能来显示sockets状态的变化。文章中仅列出程式的片段,完整的程式请看附录的程式。Server进入监听状态首先我们先看Server端如何建立一个TCPsocket,并使其进入监听等待的状态。在图1.上,我们可以看到最先被呼叫到的是WSAStartup()函式。WSAStartup格式:intPASCALFARWSAStartup(WORDwVersionRequested,LPWSADATAlpWSAData);参数:wVersionRequested欲使用的WindowsSocketsAPI版本lpWSAData指向WSADATA资料的指标传回值:成功–0失败-WSASYSNOTREADY/WSAVERNOTSUPPORTED/WSAEINVAL说明:此函式「必须」是应用程式呼叫到WindowsSocketsDLL函式中的第一个,也唯有此函式呼叫成功後,才可以再呼叫其他WindowsSocketsDLL的函式。此函式亦让使用者可以指定要使用的WindowsSocketsAPI版本,及获取设计者的一些资讯。程式中我们要用Winsock1.1,所以我们在程式中有一段为:WSAStartup((WORD)((18)|1),(LPWSADATA)&WSAData)其中((WORD)((18)|1)表示我们要用的是Winsock「1.1」版本,而WSAData则是用来储存由系统传回的一些有关此一WinsockStack的资料。socket再来我们呼叫socket()函式来开启Server端的TCPsocket。socket():建立Socket。格式:SOCKETPASCALFARsocket(intaf,inttype,intprotocol);参数:af目前只提供PF_INET(AF_INET)typeSocket的型态(SOCK_STREAM、SOCK_DGRAM)protocol通讯协定(如果使用者不指定则设为0)传回值:成功-Socket的识别码失败-INVALID_SOCKET(呼叫WSAGetLastError()可得知原因)说明:此函式用来建立一Socket,并为此Socket建立其所使用的资源。Socket的型态可为StreamSocket或DatagramSocket。我们要建立的是TCPsocket,所以程式中我们的第二个参数为SOCK_STREAM,我们并将开启的这个socket号码记在listen_sd这个变数。listen_sd=socket(PF_INET,SOCK_STREAM,0)bind接下来我们要指定一个位址及port给Server的这个socket,这样Client才知道待会要连接哪一个位址的哪个port;所以我们呼叫bind()函式。bind():指定Socket的Local位址(Address)。格式:intPASCALFARbind(SOCKETs,conststructsockaddrFAR*name,intnamelen);参数:s:Socket的识别码name:Socket的位址值namelen:name的长度传回值:成功–0失败-SOCKET_ERROR(呼叫WSAGetLastError()可得知原因)说明:此一函式是指定Local位址及Port给某一未定名之Socket。使用者若不在意位址或Port的值,那麽他可以设定位址为INADDR_ANY,及Port为0;那麽WindowsSockets会自动将其设定适当之位址及Port(1024到5000之间的值),使用者可以在此Socket真正连接完成後,呼叫getsockname()来获知其被设定的值。bind()函式要指定位址及port,这个位址必须是执行这个程式所在机器的IP位址,所以如果读者在设计程式时可以将位址设定为INADDR_ANY,这样Winsock系统会自动将机器正确的位址填入。如果您要让程式只能在某台机器上执行的话,那麽就将位址设定为该台机器的IP位址。由於此端是Server端,所以我们一定要指定一个port号码给这个socket。读者必须注意一点,TCPsocket一旦选定了一个位址及port後,就无法再呼叫另一次bind来任意更改它的位址或port。在程式中我们将Server端的port指定为7016,位址则由系统来设定。structsockaddr_insa;sa.sin_family=PF_INET;sa.sin_port=htons(7016);//portnumbersa.sin_addr.s_addr=INADDR_ANY;//addressbind(listen_sd,(structsockaddrfar*)&sa,sizeof(sa))我们在指定port号码时会用到htons()这个函式,主要是因为各机器的数值读取方式不同(PC与UNIX系统即不相同),所以我们利用这个函式来将hostorder的排列方式转换成networkorder的排列方式;相同地,我们也可以呼叫ntohs()这个相对的函式将其还原。hostorder各机器不同,但networkorder都相同;htons是针对short数值,对於long数值则用hotnl及ntohl。listen指定完位址及port之後,我们呼叫listen()函式,让这个socket进入监听状态。一个Server端的TCPsocket必须在做完了listen的呼叫後,才能接受Client端的连接。格式:intPASCALFARlisten(SOCKETs,intbacklog);参数:s:Socket的识别码backlog:未真正完成连接前(尚未呼叫accept前)彼端的连接要求的最大个数传回值:成功–0失败-SOCKET_ERROR(呼叫WSAGetLastError()可得知原因)说明:使用者可利用此函式来设定Socket进入监听状态,并设定最多可有多少个在未真正完成连接前的彼端的连接要求。(目前最大值限制为5,最小值为1)程式中我们将backlog设为1。listen(listen_sd,1)呼叫完listen後,此时Client端如果来连接的话,Client端的连接动作(connect)会成功,不过此时Server端必须再呼叫accept()函式,才算正式完成Server端的连接动作。但是我们什麽时候可以知道Client端来连接,而适时地呼叫accept呢?在这里我们就要利用WSAAsyncSelect函式,将Server端的这个socket转变成Asynchronous模式,让系统主动来通知我们有Client要连接了。WSAAsyncSelect格式:intPASCALFARWSAAsyncSelect(SOCKETs,HWNDhWnd,unsignedintwMsg,longlEvent);参数:s:Socket的编号hWnd:动作完成後,接受讯息的视窗handlewMsg:传回视窗的讯息lEvent:应用程式有兴趣的网路事件传回值:成功–0失败-SOCKET_ERROR(呼叫WSAGetLastError()可得知原因)说明:此函式是让使用者用来要求WindowsSocketsDLL在侦测到某一Socket有网路事件时送讯息到使用者指定的视窗;网路事件是由参数lEvent设定。呼叫此函式会主动将该Socket设定为Non-blocking模式。lEvent的值可为以下之「OR」组合:(参见WINSOCK第1.1版88、89页)FD_READ、FD_WRITE、FD_OOB、FD_ACCEPT、FD_CONNECT、FD_CLOSE使用者若是针对某一Socket再次呼叫此函式时,会取消对该Socket原先之设定。若要取消对该Socket的所有设定,则lEvent的值必须设为0。我们在程式中要求Winsock系统知道Client要来连接时,送一个ASYNC_EVENT的讯息到程式中hwnd这个视窗;由於我们想知道的只有accept事件,所以我们只设定FD_ACCEPT。WSAAsyncSelect(listen_sd,hwnd,ASYNC_EVENT,FD_ACCEPT)读者必须注意一点,WSAAsyncSelect的设定是针对「某一个socket」;也就是说,只有当您设定的这个socket(listen_sd)的那些事件(FD_ACCEPT)发生时,您才会收到这个讯息(ASYNC_EVENT)。如果您开启了很多sockets,而要让每个socket都变成asynchronous模式的话,那麽就必须对「每一个socket」都呼叫WSAAsyncSelect来一一设定。而如果您想将某一个socket的as
本文标题:简单的 Winsock 应用程式设计
链接地址:https://www.777doc.com/doc-3983309 .html