版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
第12章WinSock网络程序设计12.1WinSock的基本情况
12.2套接字编程基本概念
12.3Sockets库函数
12.4典型套接字系统调用时序图
12.5WindowsSockets对BerkeleySockets的扩充
12.6基于WinSock函数的网络应用程序开发12.7使用面向对象的开发语言提供的与网络通信相关的类开发网络应用程序12.8使用可视化开发工具的网络控件开发网络应用程序
12.1WinSock的基本情况
在Windows下的各种网络编程接口中,WindowsSockets脱颖而出,越来越得到大家的重视,这是因为WindowsSockets规范是一套开放的、支持多协议的Windows下的网络编程接口。从1991年的1.0版本到1995年的2.0.8版本,WindowsSockets经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已经成为Windows网络编程事实上的标准。
WindowsSocketsAPI是MicrosoftWindows的网络程序接口,它包括一个标准的BerkeleySockets功能调用的集合,以及为Windows所做的重要扩充。扩充的功能调用都以WSA(WindowsSocketsAsynchronous)作前缀,表明它们允许异步的I/O操作,并采用了符合Windows消息机制的网络事件异步选择机制。这些扩充有利于应用程序开发者更好地利用Windows的消息驱动特性,设计出高性能的网络程序。
WindowsSockets的实现一般都由两部分组成:开发组件和运行组件。开发组件是供程序员开发WindowsSockets应用程序使用的,包括介绍WindowsSockets实现的文档、WindowsSockets应用程序接口(API)引入库和一些头文件。头文件WINSOCK.H是WindowsSockets最重要的头文件,包括了WindowsSockets实现所定义的宏、常数值、数据结构和函数调用接口类型。运行组件是WindowsSockets应用程序接口的动态连接库(DLL),文件名为WINSOCK.DLL,应用程序在执行时通过装入它实现网络通信功能。
12.2套接字编程基本概念
12.2.1套接字
套接字(Socket)是网络通信的基本构件,是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连的进程。
套接字存在于通信区域中。通信区域也称为地址族,是一个抽象概念,主要用于把通过套接字通信的进程的共有特性综合在一起。套接字通常只与同一区域中的套接字交换数据(也可跨区域通信,但要执行某种转换进程之后才能实现)。WindowsSockets只支持一个通信区域:网际域(AF-INET),这个域被使用网际协议族通信的进程使用。套接字都具有类型,是根据用户可见的通信特征进行分类的。应用程序被假定为只在同一类型的套接字间通信,不过只要通信协议支持,也可在不同类型的套接字间通信。
TCP/IP的Socket提供三种类型的套接字:
(1)流式套接字(SOCK_STREAM):提供一个面向连接的、可靠的数据传输服务,数据无差错、无重复地发送,且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。文件传送协议(FTP)即使用流式套接字。
(2)数据报式套接字(SOCK_DGRAM):提供一个无连接服务。数据包以独立包形式被发送,不提供无错保证,数据可能丢失、重复或接收顺序混乱。网络文件系统(NFS)使用数据报式套接字。
(3)原始式套接字(SOCK_RAW):该接口允许对较低层协议(如IP、ICMP)直接访问。常用于检验新的协议实现或访问现有服务中配置的新设备。12.2.2网间进程通信
1.端口
网络中可以被命名和寻址的通信端口是操作系统可分配的一种资源。按照OSI协议的描述,传输层与网络层在功能上的最大区别是传输层提供进程通信能力。从这个意义上讲,网络通信的最终地址不仅仅是主机地址,还包括可以描述进程的某种标识符。为此,TCP/IP协议提出了协议端口(protocolport,简称端口)的概念,用于标识通信的进程。端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序(进程)通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应进程所接收,相应进程发给传输层的数据都通过该端口输出。在TCP/IP协议的实现中,端口操作类似一般的I/O操作,进程获取一个端口,相当于获取本地惟一的I/O文件,可以用一般的读写原语访问。
类似于文件描述符,每个端口都拥有一个叫端口号(portnumber)的整数型标识符,用于区别不同的端口。由于TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立。端口号的分配是个重要问题,有两种基本分配方式:全局分配和本地分配。全局分配是一种集中控制方式,由一个公认的中央机构根据用户需要进行统一分配,并将结果公布于众。本地分配又称动态链接,即进程需要访问传输层服务时,向本地操作系统提出申请,操作系统返回一个本地惟一的端口号,进程再通过合适的系统调用,将自己与该端口号联系起来。TCP/IP中端口号的分配综合了上述两种方式。TCP/IP将端口号分为两部分,少量的作为保留端口,以全局方式分配给服务进程,因此每个标准服务器都拥有一个全局公认的端口即周知端口(well-knownport),即使在不同机器上,其端口号也相同。剩余的为自由端口,以本地方式进行分配。TCP和UDP均规定,小于256的端口号才能作为保留端口。
2.地址
网络通信中通信的两个进程在不同的机器上。这两个机器可能位于不同的网络,这些网络通过网络互联设备(网关、网桥、路由器等)连接。因此需要三级寻址:
(1)某一主机与多个网络相连,必须指定一特定网络地址。
(2)网络上每一主机应有惟一的地址。
(3)每一主机上的每一进程应有在该主机上的惟一标识符。
通常主机地址由网络ID和主机ID组成,在TCP/IP协议中用32位整数值表示;TCP和UDP均使用16位端口号标识用户进程。
3.网络字节顺序
不同计算机存放多字节值的顺序不同。为保证数据的正确性,在网络协议中需要指定网络字节顺序。TCP/IP协议使用16位整数和32位整数的高价先存(高位字节在前,低位字节在后)格式,它们均含在协议头文件中。
4.连接
两个进程间的通信链路称为连接。连接在内部表现为一些缓冲区和一组协议机制,在外部表现出比无连接高的可靠性。
5.半相关
网络中用协议、本地地址、本地端口号三者构成的一个三元组在全局中惟一标志一个进程。这个三元组叫半相关(half-association),它指定连接一边的进程部分。
6.全相关
一个完整的网间进程通信需要两个进程组成,且只能使用同一种高层协议。即不可能通信的一端用TCP协议通信,另一端用UDP协议通信。因此一个完整的网间通信需要一个五元组来标识(协议,本地地址,本地端口号,远端地址,远端端口号),这个五元组叫相关或全相关(association),即两个协议相同的半相关才能组成一个合适的相关,或完全指定组成一个连接。12.2.3服务方式
1.面向连接(虚电路)或无连接
面向连接服务是电话系统服务模式的抽象,每一次完整的数据传输都要经过建立连接、使用连接、中止连接的过程。在数据传输过程中,各数据分组,但不携带目的地址,而是使用连接号(connectID)。本质上,连接是一个管道,收发数据不但顺序一致,而且内容相同。TCP协议提供面向连接的虚电路。
无连接服务是邮政系统服务模式的抽象,每个分组都携带完整的目的地址,各分组在系统中独立传送。无连接服务不能保证分组的先后顺序,不进行分组出错的恢复和重传,不保证传输的可靠性。UDP协议提供无连接的数据报服务。
2.顺序
在网络传输中,两个连续报文在端到端通信中可能经过不同的路径,这样到达目的地址时的顺序可能会与发送时不同。“顺序”是指接收数据顺序与发送数据顺序相同。TCP协议提供这项服务。
3.差错控制
差错控制是保证应用程序接收的数据无差错的一种机制。检查差错的方法一般是采用检验“检查和(Checksum)”的方法。而保证传输无差错的方法是双方采用确认应答技术。TCP协议提供这项服务。
4.流控制
流控制是在数据传输过程中控制数据传输速率的一种机制,以保证数据不丢失。TCP协议提供这项服务。
5.字节流
字节流方式是指仅把传输中的报文看作是一个字节序列,不提供数据流的任何边界。TCP协议提供这项服务。
6.报文
接收方要保存发送方的报文边界。UDP协议提供这项服务。
7.全双工/半双工
端与端之间的数据同时以两个方向或一个方向传送。
8.缓存/带外数据
在字节流服务中,由于没有报文边界,用户进程在某一时刻可以读写任意数量的字节。采用有流控制的协议时或为保证传输正确时,要进行缓存。但对某些特殊的需求,如交互式应用程序,又会要求取消这种缓存。
在数据传输过程中,希望不通过常规传输方式传送给用户以便用户及时处理的一类信息称为带外数据。为了将互操作中的问题减到最小,应用程序编写者除非与现有服务互操作时要求带外数据,否则最好不要使用它。12.2.4客户机/服务器模式
客户机/服务器模式在操作过程中采取主动请求方式。首先是服务器方要先启动,并根据请求提供相应的服务,具体操作如下:
(1)打开一通信通道并告知本地主机,它愿意在某公认地址上接收客户请求。
(2)等待客户请求到达该端口。
(3)接收到重复服务请求。处理该请求并发送应答信号。
(4)返回第二步,等待另一客户请求。
(5)关闭服务器。客户方的具体操作如下:
(1)打开一通信通道,并连接到服务器所在地主机的特定端口。
(2)向服务器发送服务请求报文,等待并接收应答。
(3)请求结束后关闭通信通道并中止。
从上面描述的过程可知:
(1)客户与服务器进程的作用是非对称的,因此编码不同。
(2)服务进程一般是先于客户请求而启动的,只要系统运行,该服务进程一直存在,直到正常中止或强迫中止。
12.3Sockets库函数
12.3.1库函数综述
1.套接字函数
WindowsSockets1.1版本支持的Sockets函数如表12.1所示。表12.1WindowsSockets1.1版本中的Sockets函数这些函数根据功能的不同可以分为如下几类:
(1)套接字函数。此类函数包括sockets()、bind()、getpeername()、getsockname()和closesocket(),它们主要完成创建、关闭套接字功能,以及对套接字命名与名字获取。
(2)网络连接函数。此类函数包括listen()、accept()、connect()和shutdown(),它们完成网络连接(如虚电路)的建立与关闭。此类函数中有部分可能阻塞。
(3)数据传输函数。此类函数包括send()、recv()、sendto()和recvfrom(),它们完成网络数据的发送与接收,全部是可以阻塞的函数。
(4)字节定序函数。此类函数包括htonl()、htons()、ntohl()和ntohs(),它们完成主机和网络之间数据字节顺序的转换。
(5)地址转换函数。此类函数包括inet_addr()、inet_ntoa(),它们完成网络字符串地址和Internet地址之间的转换。
(6)套接字控制函数。此类函数包括getsockopt()、setsockopt()、ioctlsocket()和select(),它们设置/获取套接字的选项,控制/检测套接字的工作状态。其中select()函数在必要时可能阻塞。套接字默认的工作模式是处于阻塞方式,一个阻塞操作可能阻塞整个Windows环境。在非抢占式优先的Windows环境中,建议使用非阻塞(异步)操作。也就是说,推荐使用WindowsSockets提供的异步选择函数代替可能阻塞的select()函数,并且用网络事件消息来驱动可能阻塞的网络连接函数(accept()和connect())数据传输函数。这样设计的Windows程序能更好地工作。
2.数据库函数
WindowsSockets定义了如表12.2所示的数据库函数。表12.2WindowsSockets1.1版本定义的数据库函数提供这类函数的目的是为了获取网络特定的信息。在WindowsSockets实现中,可能使用了不依赖于本地数据库文件的方法(如域名服务),但是对应用程序来说,请求这些信息的格式是一致的,并且对应用程序来说是透明的。
调用这些例程所获得的信息存放在由WindowsSockets实现分配的一个结构中,函数返回此结构的地址。因此,应用程序可以通过此结构指针获取所需要的信息,但它不能修改此结构,也不能释放结构的任一部分。另外,对一个线程来说,WindowsSockets实现只分配了结构的一个备份,任何WindowsSocketsAPI调用都可以修改此结构。也就是说,结构指针指向的数据只在此线程的下一次WindowsSocketsAPI调用之前才是正确的,应用程序应该在发布任何其他WindowsSocketsAPI调用之前将任何需要的信息拷贝出来。
数据库函数除了gethostname()之外都是阻塞的。在设计实现WindowsSockets应用程序时,建议使用WindowsSockets提供的数据库函数的异步版本(见下节)。
3. WindowsSockets专用的增设函数
WindowsSockets针对BerkeleySockets标准函数集提供了一些扩充,这些扩充主要用于允许对网络事件基于消息的异步存取。除了WSAStartup()和WSACleanup()函数之外,其他扩充的函数对于基于套接字的程序设计来说不是强制的,但为了和Windows程序设计风格一致,WindowsSockets推荐使用它们。
WindowsSockets提供的Windows专用增设函数在表12.3中列出。表12.3WindowsSockets1.1版本提供的Windows专用增设函数这些函数可以按功能分为如下几类:
(1)启动与中止函数。此类函数包括WSAStartup()和WSACleanup(),它们是WindowsSockets网络程序必须使用的函数。
(2)异步服务函数。此类函数包括WSAAsyncGetXByY()类的6个函数和WSACancelAsyncRequest()。WSAAsyncGetXByY()请求异步服务,WSACancelAsyncRequest()取消一个未完成的异步操作。
(3)异步选择函数。 异步选择函数WSAAsyncSelect()允许应用程序注册一个或几个感兴趣的网络事件。
(4)阻塞处理函数。此类函数包括WSAIsBlocking()、WSACancelBlockingCall()、WSASetBlockingHook()和WSAUnhookBlockingHook()。WSAIsBlocking()函数用来检测阻塞调用是否正在进行,而WSACancelBlockingCall()取消一个正在进行的阻塞调用。它们经常是联合起来使用的,参看下面的程序段:
if(WSAIsBlocking())WSACancelBlockingCall();
函数WSASetBlockingHook()为应用程序设置自己的阻塞处理例程,WindowsSockets推荐下面的编程风格:在安装自己的阻塞处理例程时,保存先前安装的阻塞处理例程的程序实例指针,并在处理结束后恢复,这样的程序段在执行之后不会影响原来环境中的阻塞处理例程。函数WSAUnhookBlockingHook()恢复默认的阻塞处理例程。在多线程版本Windows中,没有默认的阻塞处理例程,调用此函数将关闭应用程序所有的阻塞处理例程,并使任何阻塞调用产生阻塞该调用的线程。
(5)错误处理函数。此类函数包括WSAGetLastError()和WSASetLastError()。上面介绍的WindowsSocketsAPI函数是WindowsSockets1.1版本规范定义的函数标准集,任何WindowsSockets实现都支持这些函数。另外,特定的WindowsSockets实现可能提供了更为丰富的功能。在WindowsSockets规范中规定,从WindowsSocketsDLL引出的附加接口的序数必须在1000以上,1000以内的序数保留给将来的WindowsSockets使用。那些使用了特定的WindowsSockets实现所提供的附加接口的应用程序,可能不会在其他软件商提供的WindowsSockets上正常工作。建议在设计应用程序时,不要静态地把DLL连接到应用程序,而要使用Windows例程LoadLibary()和ProcAddress()动态连接,这样在不支持应用程序所使用的某些API函数时能够给出一个出错消息。12.3.2标准Sockets函数
1. accept()
accept()用于面向连接服务器。参数addr和addrlen存放客户方的地址信息。调用前addr指向一个初始值为空的地址结构,而addrlen的初始值为0;调用accept()后,服务器等待从编号为s的套接字上接受客户连接请求,连接请求是客户方的connect()调用发出的。当有连接请求到达时,accept()调用将请求连接队列上的第一个客户方套接字地址及长度放入addr和addrlen,并创建一个与s有相同特性的新套接字号。新套接字号可用于处理服务器并发请求。
2. bind()
此函数用于未连接的数据报或流套接字,将一个本地地址与套接字相连接,即建立半相关。当用socket()创建一个套接字后,它存在于一名字空间(地址族),但没有赋予名字。bind()通过将一本地名字赋予一未命名的套接字,建立起套接字的本地连接(主机地址/端口号)。
3. closesocket()
closesocket()关闭套接字s,并释放分配给该套接字的资源。如果s涉及一个打开的TCP连接,则该连接被释放。
语法:
intWSAAPIclosesocket(SOCKETs);
参数:
s是待关闭的套接字的描述符。
返回值:
如果没有错误产生,closesocket()返回0,否则;返回SOCKET_ERROR。
4. connect()
此函数用于与对等方建立一个连接。如果套接字s没有绑定,则系统赋予本地相关的惟一值,并且将套接字表示为已绑定的。
语法:
intWSAAPIconnect(SOCKETs,conststructsockaddrFAR*name,intnamelen);
参数:
s是欲建立的本地套接字的描述符(套接字号)。
name指出对方套接字地址结构的指针。
namelen是name指向的socketaddress结构的字节数。返回值:
如果没有错误产生,connect()返回0;否则,返回SOCKET_ERROR。
5. getpeername()
此函数用于获取与套接字连接的对等方的地址,它检索与套接字s连接的对等方的名字,并存在sockaddr结构的name域中。此函数只能用于已连接的数据报或流套接字。对于数据报套接字,只有在先前的connect()调用中指定的对等方的名字被返回,而不会返回先前的sendto()调用指定的名字。语法:
intWSAAPIgetpeername(SOCKETs,structsockaddrFAR*name,intFAR*namelen);
参数:
s是标识已连接的套接字的描述符。
name是指向连接的套接字网际地址的指针,该结构由getpeername()在返回前填写,name的格式由通信发生的区域决定。
namelen是指向name所指向的结构的大小的指针,返回时含有名字的实际字节数。
返回值:
如果没有错误产生,getpeername()返回0;否则,返回SOCKET_ERROR。
6. getsockname()
此函数用于获取套接字的本地名。它检索指定套接字描述符的当前名字,并存放于sockaddr结构的name域中。它用参数s指定一个已绑定或已连接的套接字,返回与该套接字相连的本地地址。当调用connect()函数之前没有调用bind()函数时,该调用特别有用,它提供了惟一一种用来确定系统设置的本地连接的方法。
语法:
intWSAAPIgetsockname(SOCKETs,structsockaddrFAR*name,intFAR*namelen);参数:
s是已绑定(bind())的套接字的描述符。
name是接收套接字的地址(名字)的缓冲区指针。
namelen是指向name缓冲区大小的指针,返回名字的实际字节数。
返回值:
如果没有错误产生,getsockname()返回0;否则,返回SOCKET_ERROR。
7. getsockopt()
此函数用来获取套接字选项。它检索与任何类型、任何状态的套接字相连的套接字选项的当前值,并把结果存入optval。选项可能在多个协议层存在,但它们总是表现在最高的“socket”层。选项影响套接字操作,如一个操作是否阻塞、包的路由选择、带外数据传输等。
语法:
intWSAAPIgetsockopt(SOCKETs,intlevel,intoptname,charFAR*optval,intFAR*optlen);参数:
s为套接字描述符。
level为设置选项的层,只支持SOL_SOCKET和IPPROTO_TCP。
optname是指定要检索的套接字选项的名字。
optval是指向查询选项返回值的缓冲区的指针。
optlen是指向optval缓冲区大小的指针。
返回值:
如果没有错误产生,getsockopt()返回0;否则,返回SOCKET_ERROR。
8. htonl()
此函数将一个u_long类型数(32位无符号整数)从主机字节顺序转换成TCP/IP网络字节顺序。
语法:
u_longWSAAPIhtonl(u_longhostlong);
参数:
hostlong是指主机字节顺序表示的32位无符号整数。
返回值:
返回一个TCP/IP网络字节顺序表示的32位值。
9. htons()
此函数将一个u_short类型数(16位无符号整数)从主机字节顺序转换成TCP/IP网络字节顺序。
语法:
u_shortWSAAPIhtons(u_shorthostshort);
参数:
hostshort是按主机字节顺序表示的16位无符号整数。
返回值:
返回一个TCP/IP网络字节顺序表示的16位值。
10. inet_addr()
此函数将一个用点分表示法表示地址的字符串地址转换成网际地址in_addr形式。所有网际地址都以网络字节顺序(字节顺序从左到右)返回。
语法:
unsignedlongWSAAPIinet_addr(constcharFAR*cp);
参数:
cp是含有用网际标准点分表示法表示地址的字符串。
返回值:
如果没有错误产生,inet_addr()返回一个无符号长整数,用适合网际地址的二进制表示;否则,返回INADDR_NONE。
11. inet_ntoa()
此函数将一个网际地址转换成点分十进制表示法表示的字符串。它接收由参数in指定的网际地址结构,返回以点分表示法表示的地址的ASCII字符串。
语法:
charFAR*WSAAPIinet_ntoa(structin_addrin);
参数:
in表示主机网际地址结构。
返回值:
如果没有错误产生,inet_ntoa()返回一个字符指针,该指针含有以点分十进制表示法表示的正文地址静态缓冲区,否则,返回NULL。
12. ioctlsocket()
此函数控制套接字模式,它用于处在任何状态的任何套接字,用来获取或检索与套接字相关的、独立于协议和通信子系统的操作参数。
语法:
intWSAAPIioctlsocket(SOCKETs,longcmd,u_longFAR*argp);
参数:
s是套接字的描述符。
cmd是在套接字s上执行的命令。
argp是指向cmd命令的参数的指针。返回值:
成功结束则ioctlsocket()返回0;否则,返回SOCKET_ERROR。
13. listen()
此函数只用于流套接字,执行两个操作:若没有为s调用过bind(),则listen()完成套接字s所必需的连接;建立长度为backlog的请求连接队列来存放即将到来的连接请求。
语法:
intWSAAPIlisten(SOCKETs,intbacklog);参数:
s标识一个本地已建立、尚未连接的套接字号,服务器愿意从它上面接收请求。
backlog表示请求连接队列的最大长度,用于限制排队请求的个数,目前允许的最大值为5。
返回值:
如果没有错误产生,listen()返回0;否则,返回SOCKET_ERROR。
14. ntohl()
此函数将一个u_long类型数(32位无符号整数)从TCP/IP网络字节顺序转换成主机字节顺序。
语法:
u_longWSAAPIntohl(u_longnetlong);
参数:
netlong是TCP/IP网络字节顺序表示的32位无符号整数。
返回值:
返回一个主机字节顺序表示的32位值。
15. ntohs()
此函数将一个u_short类型数(16位无符号整数)从TCP/IP网络字节顺序转换成主机字节顺序。
语法:
u_shortWSAAPIntohs(u_shortnetshort);
参数:
netshort是TCP/IP网络字节顺序表示的16位无符号整数。
返回值:
返回一个主机字节顺序表示的16位值。
16. recv()
此函数用于在参数s指定的已连接的数据报或流套接字上读取输入数据。
语法:
intWSAAPIrecv(SOCKETs,charFAR*buf,intlen,intflags);
参数:
s为已连接的套接字描述符。
buf是指向接收输入数据缓冲区的指针。
len为buf所指向的缓冲区的长度。
flags为指定调用方式,可用来与套接字相关的选项一起影响函数的功能。返回值:
如果没有错误产生,recv()返回总共接收的字节数;如果连接被关闭,返回0,否则,返回SOCKET_ERROR。
17. recvfrom()
此函数用于在参数s指定的套接字(可能已连接)上读取输入数据,并捕获发送数据的地址,存入源地址缓冲区。
语法:
intWSAAPIrecvfrom(SOCKETs,charFAR*buf,intlen,intflags,structsockaddrFAR*from,intFAR*fromlen);参数:
s为已连接的套接字的描述符。
buf是指向接收输入数据缓冲区的指针。
len为buf所指向的缓冲区的长度。
flags是指定调用方式,可用来与套接字相关的选项一起影响函数的功能。
from是任选指针,指示源地址缓冲区。
fromlen是任选指针,指示源地址缓冲区的长度。
返回值:
如果没有错误产生,recvfrom()返回收到的字节数;如果连接被关闭,返回0,否则,返回SOCKET_ERROR。
18. select()
此函数用来检测一个或多个套接字的状态。对每个套接字来说,这个调用可以请求读、写或错误状态的信息。请求给定状态的套接字集合由一个fd_set结构指示。在返回时,此结构被更新,以反映那些特定条件的套接字的子集,同时,select()返回满足条件的套接字的数目。
语法:
intWSAAPIselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,fd_setFAR*exceptfds,conststructtimevalFAR*timeout);参数:
nfds指明被检查的套接字描述符的值域,一般忽略该参数。
readfds是指向要做读检测的套接字描述符集合的指针。
writefds是指向要做写检测的套接字描述符集合的指针。
exceptfds是指向要检测是否出错的套接字描述符集合的指针。
timeout是指向select()等待的最大时间。如设为NULL,则为阻塞操作。返回值:
如果没有错误产生,select()返回包含在fd_set结构中已准备好的套接字描述符的总数目;否则,返回SOCKET_ERROR。
19. send()
此函数用于在参数s指定的已连接的数据报或流套接字上发送输出数据。
语法:
intWSAAPIsend(SOCKETs,constcharFAR*buf,intlen,intflags);参数:
s为已建立的本地套接字的描述符。
buf是指向存有发送数据的缓冲区的指针。
len是buf指向缓冲区中数据的长度。
flags是指定调用方式,可用来与套接字相关的选项一起影响函数的功能。
返回值:
如果没有错误产生,send()返回总共发送的字节数;否则,返回SOCKET_ERROR。
20. sendto()
此函数用于在参数s指定的数据报或流套接字上向指定的目的地址发送输出数据。
语法:
intWSAAPIsendto(SOCKETs,constcharFAR*buf,intlen,intflags,conststructsockaddrFAR*to,intFAR*tolen);
参数:
s为已建立的本地套接字的描述符。
buf是指向存有发送数据的缓冲区的指针。
len是buf指向缓冲区中数据的长度。
flags是指定调用方式,可用来与套接字相关的选项一起影响函数的功能。
to是指向目的套接字地址的任选指针。
tolen是to所指向地址的长度。
返回值:
如果没有错误产生,send()返回总共发送的字节数;否则,返回SOCKET_ERROR。
21. setsockopt()
此函数为套接字相关的选项设置当前值,套接字可以是任何类型,可以处于任何状态。尽管选项可存在于多个协议层,但它们总是表现在最高的“socket”层。选项影响套接字操作,如加急数据是否在正常数据流中接收、广播消息是否在套接字上发送等。
语法:
intWSAAPIsetsockopt(SOCKETs,intlevel,intoptname,constcharFAR*optval,intFAR*optlen);参数:
s为套接字描述符。
level为选项定义的层次,只支持SOL_SOCKET和IPPROTO_TCP。
optname是指定套接字选项的名字。
optval是指向请求选项数据缓冲区的指针。
optlen是选项数据optval缓冲区的长度。
返回值:
如果没有错误产生,setsockopt()返回0;否则,返回SOCKET_ERROR。
22. shutdown()
此函数用来切断一个双向连接的接收、发送部分或全部连接。
语法:
intWSAAPIshutdown(SOCKETs,inthow);
参数:
s是套接字描述符。
how是断路方式,有0,1,2三种不同情况,分别是:如how为0(SD_RECEIVE),套接字上后续的接收将被禁止,这对低层的协议没有影响;如how为1(SD_SEND),后续的发送数据被禁止;如how为2(SD_BOTH),将同时禁止后继数据的发送和接收。返回值:
如果没有错误产生,shutdown()返回0;否则,返回SOCKET_ERROR。
23. socket()
此函数建立一个套接字,它给指定的地址族、数据类型和协议分配一个套接字描述符以及相关的资源。根据这三个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此socket()系统调用实际上指定了相关五元组中的“协议”这一元。
语法:
SOCKETWSAAPIsocket(intaf,inttype,intprotocol);参数:
af是一种地址格式描述。现在支持的格式只有PF_INET,为ARPA网际地址格式。
type描述要建立的套接字的类型。
protocol说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为0,使用默认的连接模式。
返回值:
如果没有错误产生,socket()返回一个与建立的套接字相关的描述符;否则,返回INVALID_SOCKET。12.3.3数据库函数
1. gethostbyaddr()
此函数用来通过地址获取主机信息。对于数据库函数中的getXbyY()类型的函数来说,它们返回的指针指向的结构是由WindowsSockets实现分配的。应用程序不能修改此结构或释放它的任一部件。
语法:
structhostentFAR*WSAAPIgethostbyaddr(constcharFAR*addr,intlen,inttype);参数:
addr是指向一个网络字节顺序表示的32位网际地址的指针。
len表示addr的字节数,对PF_INET地址,必须为4。
type是地址类型,必须为PF_INET。
返回值:
如果没有错误产生,gethostbyaddr()返回一个hostent结构指针;否则,返回NULL指针。
2. gethostbyname()
此函数用来通过主机名获取主机信息,它返回hostent结构的指针,此结构包含与给定的主机名相对应的名字和地址。gethostbyname()函数不能理解传递给它的IP地址字符串,这个请求只能当作传送了一个未知的主机名来处理。要分析IP地址字符串,应该使用inet_addr()来将字符串转换成IP地址,然后调用gethostbyaddr()来获得hostent结构。
语法:
structhostentFAR*WSAAPIgethostbyname(constcharFAR*name);参数:
name为所查询主机的名字。
返回值:
如果没有错误产生,gethostbyname()返回一个hostent结构的指针;否则,返回NULL指针。
3. gethostname()
此函数在参数指定的缓冲区中返回本地主机的名字,是一个以NULL结束的字符串。
语法:
intWSAAPIgethostname(charFAR*name,intnamelen);
参数:
name是指向接受主机名的缓冲区的指针。
namelen是缓冲区的长度。
返回值:
如果没有错误产生,gethostname()返回0;否则,返回SOCKET_ERROR。
4. getprotobyname()
此函数用来获取协议信息。
语法:
structprotoentFAR*WSAAPIgetprotobyname(constcharFAR*name);
参数:
name是指向协议名的指针。
返回值:
如果没有错误产生,getprotobyname()返回一个protoent结构指针;否则,返回NULL指针。
5. getprotobynumber()
此函数通过协议号获取协议信息,返回protoent结构指针,此结构包含与给定的协议号相对应的名字和协议号。
语法:
structprotoentFAR*WSAAPIgetprotobynumber(intnumber);
参数:
number是用主机字节顺序表示的协议号。
返回值:
如果没有错误产生,getprotobynumber()返回一个protoent结构指针;否则,返回NULL指针。
6. getservbyname()
此函数用来获取服务信息,返回servent结构指针,此结构内容通过给定服务名name和协议名proto获得,包含服务名和服务端口号。
语法:
structserventFAR*WSAAPIgetservbyname(constcharFAR*name,constcharFAR*proto);参数:
name是指向服务名的指针。
proto是指向协议名的指针,该参数可选。如为NULL,getservbyname()返回服务名s_name或服务别名s_aliases之中与name匹配的第一个服务入口,否则,getservbyname()同时匹配服务名name和协议名proto。
返回值:
如果没有错误产生,getservbyname()返回一个servent结构的指针;否则,返回NULL指针。
7. getservbyport()
此函数用来获取服务信息,返回servent结构的指针,此结构的内容通过给定端口号port和协议名proto获得,包含服务名和服务端口号。
语法:
structserventFAR*WSAAPIgetservbyport(intport,constcharFAR*proto);参数:
port是服务使用的端口号,用网络字节顺序表示。
proto是指向协议名的指针,该参数可选。如为NULL,getservbyport()返回服务端口号s_port与port匹配的第一个服务入口,否则,getservbyport()同时匹配服务端口号port和协议名proto。
返回值:
如果没有错误产生,getservbyport()返回一个servent结构指针;否则,返回NULL指针。12.3.4增设函数
1. WSAAsyncGetHostByAddr()
此函数是gethostbyaddr()的异步版本,用来通过网络地址获取主机的名字和地址信息。
语法:
HANDLEWSAAPIWSAAsyncGetHostByAddr(HWNDhWnd,unsignedintwMsg,constcharFAR*addr,intlen,inttype,charFAR*buf,intbuflen);参数:
hWnd为异步请求完成时,接收消息的窗口句柄。
wMsg为异步请求完成时,接收到的消息。
addr为指向主机网络地址的指针,主机网络地址用网络字节顺序存储。
len为地址长度,对PF_INET族,必须为4。
type为地址类型,必须为PF_INET。
buf为接收主机信息的数据缓冲区的指针,它的长度必须大于hostent结构的长度。
buflen为buf指向缓冲区的长度。
返回值:
如果操作被成功初始化,返回一个HANDLE类型的非零值,它是此请求的异步任务句柄;否则,返回0。
2. WSAAsyncGetHostByName()
此函数是gethostbyname()的异步版本,用来通过主机名字获取主机的名字和地址信息。
语法:
HANDLEWSAAPIWSAAsyncGetHostByName(HWNDhWnd,unsignedintwMsg,constcharFAR*name,charFAR*buf,intbuflen);
参数:
hWnd为异步请求完成时,接收消息的窗口的句柄。
wMsg为异步请求完成时,接收到的消息。
name是指向主机名字的指针。
buf是接收主机信息的数据缓冲区的指针,它的长度必须大于hostent结构的长度。
buflen是buf指向缓冲区的长度。
返回值:
如果操作被成功初始化,返回一个HANDLE类型的非零值,它是此请求的异步任务句柄;否则,返回0。
3. WSAAsyncGetProtoByName()
此函数是getprotobyname()的异步版本,用来通过协议名获取协议的名字和协议号信息。
语法:
HANDLEWSAAPIWSAAsyncGetProtoByName(HWNDhWnd,unsignedintwMsg,constcharFAR*name,charFAR*buf,intbuflen);参数:
hWnd为异步请求完成时,接收消息的窗口的句柄。
wMsg为异步请求完成时,接收到的消息。
name是指向要查询的协议名字的指针。
buf是接收协议信息的数据缓冲区的指针,它的长度必须大于protoent结构的长度。
buflen是buf指向缓冲区的长度。
返回值:
如果操作被成功初始化,返回一个HANDLE类型的非零值,它是此请求的异步任务句柄;否则,返回0。
4. WSAAsyncGetProtoByNumber()
此函数是getprotobynumber()的异步版本,用来通过协议号获取协议的名字和协议号信息。
语法:
HANDLEWSAAPIWSAAsyncGetProtoByNumber(HWNDhWnd,unsignedintwMsg,intnumber,charFAR*buf,intbuflen);参数:
hWnd为异步请求完成时,接收消息的窗口的句柄。
wMsg为异步请求完成时,接收到的消息。
name是指向要查询的协议名字的指针,用主机字节顺序表示。
buf为接收协议信息的数据缓冲区的指针,它的长度必须大于protoent结构的长度。
buflen为buf指向缓冲区的长度。
返回值:
如果操作被成功初始化,返回一个HANDLE类型的非零值,它是此请求的异步任务句柄;否则,返回0。
5. WSAAsyncGetServByName()
此函数是getservbyname()的异步版本,用来通过服务名字和协议名获取服务信息。
语法:
HANDLEWSAAPIWSAAsyncGetServByName(HWNDhWnd,unsignedintwMsg,constcharFAR*name,constcharFAR*proto,charFAR*buf,intbuflen);
参数:
hWnd为异步请求完成时,接收消息的窗口的句柄。
wMsg为异步请求完成时,接收到的消息。
name是指向服务名字的指针。
proto是指向协议名的指针,如为NULL,函数寻找服务名s_name或服务别名s_aliases之一与给定的name相匹配的第一个服务入口;否则,函数同时匹配服务名
name和协议名proto。
buf为接收主机信息的数据缓冲区的指针,它的长度必须大于servent结构的长度。
buflen为buf指向缓冲区的长度。
返回值:
如果操作被成功初始化,返回一个HANDLE类型的非零值,它是此请求的异步任务句柄;否则,返回0。
6. WSAAsyncGetServByPort()
此函数是getservbyport()的异步版本,用来通过端口号和协议名获取服务信息。
语法:
HANDLEWSAAPIWSAAsyncGetServByPort(HWNDhWnd,unsignedintwMsg,intport,constcharFAR*proto,charFAR*buf,intbuflen);
参数:
hWnd为异步请求完成时,接收消息的窗口的句柄。
wMsg为异步请求完成时,接收到的消息。
port为服务端口号,用网络字节顺序表示。
proto是指向协议名的指针,如为NULL,函数寻找服务端口号s_port与给定的端口port相匹配的第一个服务入口;否则,函数同时匹配port和proto。
buf为接收主机信息的数据缓冲区的指针,它的长度必须大于servent结构的长度。
buflen为buf指向缓冲区的长度。
返回值:
如果操作被成功初始化,返回一个HANDLE类型的非零值,它是此请求的异步任务句柄;否则,返回0。
7. WSAAsyncSelect()
此函数为套接字请求基于Windows消息的事件通知,用于请求WindowsSocketsDLL在检测到由参数lEvent指定的网络事件时,向窗口hWnd发送一个消息。发送的消息由参数wMsg指定,要求接到通知的套接字由参数s标识。此函数自动地设置套接字s处于非阻塞方式。
语法:
intWSAAPIWSAAsyncSelect(SOCKETs,HWNDhWnd,unsignedintwMsg,longlEvent);参数:
s是标识请求事件通知的套接字的描述符。
hWnd是标识网络事件发生时,接收消息的窗口的句柄。
wMsg为网络事件发生时,窗口接收到的消息。
lEvent为指定应用程序感兴趣的网络事件组合的位屏蔽。
返回值:
如果应用程序对感兴趣的网络事件集的注册登记成功,返回0;否则,返回SOCKET_ERROR。
8. WSACancelAsyncRequest()
此函数用来取消由WSAAsyncGetXByY()类函数引起的异步操作。被取消的操作由参数hAsyncTaskHandle标识,此参数由原始调用函数返回,是此异步任务的句柄。
语法:
intWSAAPIWSACancelAsyncRequest(HANDLEhAsyncTaskHandle);
参数:
hAsyncTaskHandle是指示将被取消的异步操作的异步任务句柄。
返回值:
如果操作被成功取消,返回0;否则,返回SOCKET_ERROR。
9. WSACancelBlockingCall()
此函数为当前任务取消任何尚未完成的阻塞调用。
语法:
intWSAAPIWSACancelBlockingCall(void);
参数:
无。
返回值:
如果操作被成功取消,返回0;否则,返回SOCKET_ERROR。
10. WSACleanup()
此函数用来结束对WindowsSocketsDLL的使用。
语法:
intWSAAPIWSACleanup(void);
参数:
无。
返回值:
如果操作成功,返回0;否则,返回SOCKET_ERROR。
11. WSAGetLastError()
此函数用来获取最近失败操作的错误码,返回最近产生的网络错误。
语法:
intWSAAPIWSAGetLastError(void);
参数:
无。
返回值:
返回值指示当前线程最近执行的WindowsSocketsAPI函数产生的错误。
12. WSAIsBlocking()
此函数用来检查阻塞调用是否正在进行,允许任务在等待一个先前的阻塞调用完成时,检测此调用是否正在进行。
语法:
BOOLWSAAPIWSAIsBlocking(void);
参数:
无。
返回值:
如果当前线程存在未完成的阻塞函数,返回TRUE;否则,返回FALSE。
13. WSASetBlockingHook()
此函数用来建立特定应用程序的阻塞处理例程。
语法:
FARPROCWSAAPIWSASetBlockingHook(FARPROClpBlockFunc);
参数:
lpBlockFunc是指向要安装的阻塞处理例程的过程实例地址的指针。
返回值:
返回值为先前安装的阻塞处理例程的程序实例指针,调用者应该保存这个返回值以便在必要时恢复;如操作失败,返回NULL指针。
14. WSASetLastError()
此函数允许应用程序为当前线程设置错误码,此代码可由其后的WSAGetLastError()调用获得。
语法:
voidWSAAPIWSASetLastError(intiError);
参数:
iError指示由其后的WSAGetLastError()调用返回的错误码。
返回值:
无。
15. WSAStartup()
此函数初始化WindowsSocketsDLL,必须是应用程序或DLL第一个调用的WindowsSockets函数。它允许应用程序或DLL指定WindowsSocketsAPI要求的版本,以获取指定的WindowsSockets实现的细节。应用程序或DLL只有在一次成功的WSAStartup()执行后才能发布后续的WinSock函数。
语法:
intWSAAPIWSAStartup(WORDwVersionRequested,LPWSADATAlpWSAData);参数:
wVersionRequested为调用者可以使用的WindowsSocketsAPI支持的最高版本号。
lpWSAData是指向接收WindowsSockets实现细节的数据结构WSADATA的指针。
返回值:
如果调用成功,返回0;否则,返回错误码。
16. WSAUnhookBlockingHook()
此函数删除先前安装的任何阻塞处理例程,并重新安装默认的阻塞机制。在多线程版本Windows中,没有默认的阻塞处理例程,调用此函数将关闭应用程序安装的所有阻塞处理例程,并使任何阻塞调用产生阻塞该调用的线程。
语法:
intWSAAPIWSAUnhookBlockingHook(void);
参数:
无。
返回值:
如果操作成功,返回0;否则,返回SOCKET_ERROR。
12.4典型套接字系统调用时序图
TCP/IP协议的应用一般采用客户机/服务器模式。因此,在实际应用中,必须有客户和服务器两个进程,并要先启动服务器。面向连接协议(如TCP)的套接字系统调用时序图如图12.1所示。
服务器必须首先启动,直到执行完accept()调用,进入等待状态后,方能接收客户请求。如客户在此之前启动,则connect()将返回出错代码,连接不成功。图12.1面向连接的协议(如TCP)的套接字系统调用时序图无连接协议的套接字系统调用时序图如图12.2所示。
无连接服务器也必须先启动,否则客户请求传不到服务进程。无连接客户不调用connect(),因此,在数据发送之前,客户与服务器之间尚未建立全相关,但各自通过socket()和bind()建立了半相关。发送数据时,发送方除指定本地套接字号外,还需指定接收方套接字号,从而在数据收发过程中动态的建立全相关。图12.2无连接协议的套接字系统调用时序图 12.5WindowsSockets对Berkeley Sockets的扩充
1.异步选择机制
WindowsSockets的异步选择函数提供了消息机制的网络事件选择。当使用它登记的网络事件发生时,Windows应用程序相应的窗口函数将收到一个消息,消息中指示了发生的网络事件以及与事件相关的一些信息。
2.异步请求函数
异步请求函数允许应用程序用异步方式获得请求的信息,且在请求的服务完成时给应用程序相应的窗口函数发送一个消息。
3.阻塞处理方法
为了实现当应用程序的套接字调用处于阻塞时,能够放弃CPU让其他应用程序运行,WindowsSockets在调用处于阻塞时便进入一个叫“HOOK”的例程。HOOK例程负责接收和分配Windows消息,这使得其他应用程序仍然能够接收到自己的消息并取得控制权。WindowsSockets还提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook()),让用户设置和取消自己的阻塞处理例程,用以支持要求复杂消息处理的应用程序(如多文档界面)。函数WSAIsBlocking()可以检测是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。
4.出错处理
WindowsSockets为了和以后多线程环境(如WindowsNT)兼容,提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置当前线程的最近错误号。
5.启动与中止
由于WindowsSockets的服务是以动态连接库WINSOCK.DLL形式实现的,所以必须要先调用WSAStartup()函数对WindowsSocketsDLL进行初始化,协商版本的支持,并分配必要的资源。在应用程序完成了对WindowsSockets的使用之后,还应调用函数WSACleanup(),中止对WindowsSocketsDLL的使用,并释放资源,以备下一次使用。12.6基于WinSock函数的网络应用程序开发
在介绍了WindowsSocketsAPI的基本情况后,将介绍基于WindowsSocketsAPI的网络程序设计方法。在Windows环境下,有三种基本的网络应用程序设计方法,分别如下:
(1)用基本的WindowsSocketsAPI函数来进行网络应用程序开发。
(2)用面向对象的开发语言提供的与网络通信有关的类来进行网络应用程序开发。
(3)用可视化开发工具中的网络控件来进行网络应用程序开发。这三种方法中所使用的网络应用编程接口,由低级到高级,即使是高一级的编程接口(类和网络控件)也是以基本的WindowsSocketsAPI为基础,只不过是对低级接口在不同程度上的封装而已。
因为Windows环境下程序设计工具的多样性,所以有很多可视化程序开发工具和程序设计语言(如Java,C++等)。比较著名的可视化程序设计工具有:Delphi、PowerBuilder、C++Builder、Jbuilder、VisualC++、VisualBasic等。在这些可视化开发工具中,既提供了基本的套接字API函数,也提供了更高一级的编程接口:网络控件或网络通信类。尽管不同工具中提供的有关网络程序设计的类或控件的数量和名称有所不同,但是其基本思想和原理却是一样的。因此,我们将以MFC为例介绍使用类来进行网络程序设计,以VisualBasic为例介绍基于网络控件的网络程序设计。
另外,Windows环境下的程序设计语言和工具发展很快,类和控件的数量也是处于不断变化之中。因此,需要掌握基本的方法和思想。下面我们以一个例子介绍Winsock网络程序最典型的模式:客户机/服务器工作模式(摘自DouglasE.Comer与DavidL.Stevens合著的InternetWorkingwithTCP/IPVolumeⅢ:ClientServerProgrammingandApplications)。本例说明回显服务(ECHO)是如何实现的,服务器采用的是并发模式,即对每个客户的请求都用一个独立的线程来处理,这样能极大地提高系统效率,减少连接队列中连接请求的大小。整个程序分为两部分:客户程序和服务器程序。
12.6.1服务器程序
下面介绍服务器程序,内容如下。函数passivesock是根据不同的传输协议、要求提供的服务,以及连接队列的大小来创建服务器的监听套接字。函数首先调用getprotobyname取得协议的信息,从而决定使用何种地址类型来创建套接字,调用函数getservbyname取得相关服务信息,以设置相应的端口;其次根据得到的信息来创建适当的套接字;再次,调用bind函数将创建的套接字与本地地址关联起来,形成连接三元组;最后调用listen监听创建的套接字,监听客户端的请求。
函数passiveTCP将传输协议“tcp”传递给passivesock并直接调用它。函数passiveUDP与passiveTCP基本相同,传递“udp”给passivesock。
main函数通用passiveTCP创建一个监听套接字,然后进入主循环,处理客户端的连接请求,每次收到一个连接请求后程序都调用_beginthread函数为之创建一个
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2026及未来5年中国双腔标准型导尿管数据监测研究报告
- 2026事业单位工勤技能-江苏-江苏无损探伤工二级(技师)历年参考题库含答案详解3套试卷
- 2026事业单位工勤技能-江苏-江苏军工电子设备制造工一级(高级技师)历年参考题库含答案详解3套试卷
- 2026事业单位工勤技能-新疆-新疆热处理工四级(中级工)历年参考题库含答案详解3套试卷
- 2026事业单位工勤技能-新疆-新疆堤灌维护工四级(中级工)历年参考题库含答案详解3套试卷
- 2026事业单位工勤技能-广西-广西食品检验工三级(高级工)历年参考题库含答案详解3套试卷
- 深度学习在APP中的应用
- 安全入宪法解读讲解
- 2026年秋季开学高三让努力配得上梦想心理减压课件
- 2026年秋季开学大学一年级新生军训集体主义教学课件
- 培训机构教材管理制度
- 集成电路布图设计委托开发合同
- 砌筑工考试试题及答案
- 店面租赁订金合同范例
- 果园产品购销合同
- 入党申请书专用纸-A4单面打印
- 《临床技术操作规范病理学分册》医院用
- AED(自动体外除颤仪)的使用
- 中国矿业权评估准则(2011年)
- 硫铁矿制酸工艺
- 粉尘防爆知识培训试题
评论
0/150
提交评论