Download presentation
Presentation is loading. Please wait.
1
第8章 Winsock API 8.1 字节排序函数 8.2 IP地址转换函数 8.3 网络信息获取函数(数据库函数)
8.4 套接口选项函数 8.5 套接口I/O处理函数 8.6 事件对象I/O管理 8.7 错误处理函数 8.8 Winsock 2支持的其他函数 习题
2
8.1字节排序函数 8.1.1 4字节主机字节顺序的数转化为网络字节顺序——htonl( )和WSAHtonl( ) 1.函数格式
在Winsock 1中提供的htonl( )函数的格式是: u_long htonl( u_long hostlong ); 在Winsock 2中提供的WSAHtonl( )函数的扩展格式是:
3
int WSAHtonl( SOCKET s, u_long hostlong, u_long FAR * lpnetlong ); 2.函数参数说明 这两个函数中各参数的说明如下: ● hostlong:传入参数,它是一个用主机字节顺序表示的将要被转换为网络字节顺序的32位数(即4个字节的数),u_long表示无符号长整型数。 ● s:传入参数,在Winsock 2提供的扩展格式中增加了标识套接口的描述字。 ● lpnetlong:传出参数,一个指向32位的网络字节顺序数的指针。
4
3.函数返回信息 这两个函数的返回值差别较大,在使用时一定要注意。 对于htonl( )函数来说,如果该函数被调用时正确执行,则这个函数返回一个32位的TCP/IP网络字节顺序的数。 对于WSAHtonl( )函数来说,如果该函数被调用时正确执行,则这个函数的返回值为0,函数返回的32位网络字节顺序的数据是指针参数lpnetlong所指向的数。如果在调用时该函数发生错误,则返回SOCKET_ERROR错误信息,可以进一步使用WSAGetLastError( )函数取得对该错误的具体描述。错误代码如下:
5
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSAENOTSOCK:描述字不是一个套接口。 ● WSAEFAULT:指针参数lpnetlong不在有效的用户地址空间中。 4.函数使用说明 该函数的使用比较简单,主要用来将一个32位的IP地址从主机字节顺序转化为网络字节顺序,具体用法可以参考第7章的网络程序实例。
6
8.1.2 2字节主机字节顺序的数转化为网络字节顺序——htons( )和WSAHtons( )
1.函数格式 在Winsock 1中提供的htons( )函数的格式是: u_short htons( u_short hostshort ); 在Winsock 2中提供的WSAHtons( )函数的扩展格式是: u_short WSAHtons( SOCKET s, u_short hostshort, u_short FAR * lpnetshort
7
2.函数参数说明 这两个函数中各参数的说明如下: ● hostshort:传入参数,它是一个以主机字节顺序表示的将要被转换为网络字节顺序的16位数(即2字节数),u_short表示无符号短整型数。 ● s:传入参数,Winsock 2提供的扩展格式中增加的标识套接口的描述字。 ● lpnetshort:传出参数,一个指向16位网络字节顺序数的指针。
8
3.函数返回信息 htons( )函数如果被调用时正确执行,则这个函数返回一个16位TCP/IP网络字节顺序的数。 WSAHtons( )函数如果被调用时正确执行,则这个函数返回0,函数返回的16位网络字节顺序的数据是指针参数lpnetshort所指向的数。如果在调用时该函数发生错误,则返回SOCKET_ERROR错误信息,可以进一步使用WSAGetLastError( )函数取得对该错误的具体描述。错误代码与WSAHtonl( )函数的错误代码类似。 4.函数使用说明 该函数一般用来将一个16位的TCP或UDP端口号从主机字节顺序转化为网络字节顺序,用法可以参考第7章的程序实例。
9
8.1.3 4字节网络字节顺序的数转化为主机字节顺序——ntohl( )和WSANtohl( )
1.函数格式 在Winsock 1中提供的ntohl( )函数的格式是: u_long ntohl( u_long netlong ); 在Winsock 2中提供的WSANtohl( )函数的扩展格式是: u_long WSANtohl( SOCKET s, u_long netlong, u_long FAR * lphostlong
10
2.函数参数说明 这两个函数的参数说明如下: ● netlong:传入参数,一个以网络字节顺序表示的32位数。 ● s:传入参数,在Winsock 2中提供的扩充格式中增加的标识套接口的描述字。 ● lphostlong:传出参数,指向一个32位主机字节顺序数的指针。 3.函数返回信息 ntohl( )函数在调用成功后,返回一个主机字节顺序的32位数。
11
WSANtohl( )函数在调用正确时返回0,函数返回的32位主机字节顺序的数据是指针参数lphostlong所指向的数。如果调用错误,则返回错误信息SOCKET_ERROR,可以进一步使用WSAGetLastError( )函数取得对该错误的具体描述。具体错误代码与WSAHtonl( )函数的错误代码类似。 4.函数使用说明 该函数常用来将一个网络字节顺序表示的32位IP地址转化为主机字节顺序。
12
8.1.4 2字节网络字节顺序的数转化为主机字节顺序——ntohs( )和WSANtohs( )
1.函数格式 在Winsock 1中提供的ntohs( )函数的格式是: u_short ntohs( u_short netshort ); 在Winsock 2中提供的WSANtohs( )函数的扩展格式是: u_short WSANtohs( SOCKET s, u_short netshort, u_short FAR * lphostshort
13
2.函数参数说明 这两个函数的参数说明如下: ● netshort:传入参数,一个以网络字节顺序表示的16位数。 ● s:传入参数,在Winsock 2提供的扩充格式中增加的一个标识套接口的描述字。 ● lphostshort:传出参数,指向一个16位主机字节顺序数的指针。
14
3.函数返回信息 ntohs( )函数在调用成功后,返回一个主机字节顺序的16位数。 WSANtohs( )函数在调用正确时返回0,函数返回的16位主机字节顺序的数据是指针参数lphostshort所指向的数。如果调用错误,则返回错误信息SOCKET_ERROR,可以进一步使用WSAGetLastError( )函数取得对该错误的具体描述。具体错误代码与WSAHtonl( )函数的错误代码类似。 4.函数使用说明 该函数常用来将一个网络字节顺序表示的16位TCP或UDP端口号转化为主机字节顺序。
15
8.2 IP地址转换函数 8.2.1 点分十进制数表示的IP地址转换为网络字节顺序的IP地址——inet_addr( ) 1.函数格式
unsigned long inet_addr( const char FAR* cp );
16
2.函数参数说明 该函数中的参数cp为传入参数,是一个以“.”间隔的字符串,即一个点分十进制数表示的IP地址。 3.函数返回信息 该函数调用成功后,返回一个无符号长整型数(Unsigned Long),它是以网络字节顺序表示的32位二进制IP地址。如果传入的字符串不是一个合法的Internet地址,如当“a.b.c.d”地址中任一项超过255时,则inet_addr( )返回INADDR_NONE提示信息。
17
4.函数使用说明 当IP地址用点分十进制数表示时,即4个字节的数以“.”间隔,则用这种格式书写的IP地址可有下列4种表示方式。 (1) a.b.c.d:当四个部分都有固定的值时,则每一个部分被解释成一个字节的数据,从左到右组成Internet 4字节地址。请注意,当一个Internet地址在Intel机器上表示成一个32位整型数时,则上述数据在计算机内部的表示为“d.c.b.a”。这是因为在Intel处理器内部,字中的字节是按由低到高的顺序存储的,即“小序在前”。
18
(2) a.b.c:对于一个只有三个部分组成的IP地址,最后一部分被解释成16位数据,并作为网络地址最右边的两个字节。
(3) a.b:对于一个只有两个部分组成的IP地址,最后一部分解释成24位数据,并作为网络地址最右边的三个字节。 (4) a:对于一个仅有一个部分的IP地址,将它的值直接存入网络地址而不做任何字节重组。 该函数的用法见第7章中的客户程序实例。
19
8.2.2 网络字节顺序的IP地址转换为点分十进制数表示的IP地址——inet_ntoa( )
1.函数格式 inet_ntoa( )函数的格式如下: char *inet_ntoa( struct in_addr in ); 2.函数参数说明 该函数中的参数in为传入参数,表示一个结构型的IP主机地址。
20
3.函数返回信息 该函数调用成功后,返回一个指向字符的指针,该指针指向一个文本型的缓冲区,缓冲区中存有用点分十进制形式表示的IP地址。如果函数调用失败的话,则返回一个空指针(NULL)。 4.函数使用说明 该函数的用法参见第7章中的服务器程序实例。
21
8.3 网络信息获取函数(数据库函数) 8.3.1 获得主机名——gethostname( )
1.函数格式 该函数的格式如下: int gethostname( char FAR* name, int namelen );
22
2.函数参数说明 gethostname( )函数中各参数的说明如下: ● name:传出参数,一个指向将要存放主机名的缓冲区指针,当函数调用完成时,主机名被存入该缓冲区中。 ● namelen:传入参数,缓冲区的长度。 3.函数返回信息 如果函数调用时没有发生错误,则gethostname( )函数返回0。如果函数调用失败,则返回SOCKET_ERROR错误信息。应用程序可以通过WSAGetLastError( )来得到一个特定的错误代码,错误代码说明如下:
23
● WSAEFAULT:名字长度参数太小。
● WSANOTINTIALISED:在应用这个API前,必须成功调用WSAStartup( )。 ● WSAENETDOWN:Windows Sockets实现检测到了网络子系统的错误。 ● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。 4.函数使用说明 该函数把本地主机名存放入由name参数指定的缓冲区中。主机名的形式取决于Windows Sockets系统的实现,它可能是一个简单的主机名,或者是一个域名。不管是哪种形式,该函数返回的名字必定可以在gethostbyname( )和WSAAsyncGetHostByName( )函数中使用。
24
8.3.2 获得与套接口相连的远程协议地址——getpeername( )
1.函数格式 getpeername( )函数的格式如下: int getpeername( SOCKET s, struct sockaddr FAR* name, int FAR* namelen );
25
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,一个已建立连接的套接口描述字。 ● name:传出参数,指向返回的远程协议地址。 ● namelen:传出参数,远程协议地址长度。 3.函数返回信息 调用该函数时若无错误发生,则getpeername( )函数返回0。如果调用失败,则返回SOCKET_ERROR错误信息,应用程序可通过调用WSAGetLastError( )函数来获取对该错误的进一步描述。可获得的错误代码如下:
26
● WSANOTINITIALISED:在使用此API之前应成功调用WSAStartup( )。
● WSAENETDOWN:Windows套接口实现检测到网络子系统失效。 ● WSAEFAULT:namelen参数不够大。 ● WSAEINPROGRESS:一个阻塞的Windows套接口调用正在运行中。 ● WSAENOTCONN:套接口未建立连接。 ● WSAENOTSOCK:描述字不是一个套接口。
27
4.函数使用说明 getpeername( )函数用于从套接口s中获取与它绑定的远程协议的地址信息,并把它存放在sockaddr类型的name结构中。它只能用于已经建立连接的套接口。对于数据报类型的套接口来说,它只能返回先前调用connect( )函数时使用的对等端信息,在sendto( )函数中使用过的对等端信息不能被返回。
28
8.3.3 获得套接口本地协议地址——getsockname( )
1.函数格式 getsockname( )函数的格式如下: int getsockname( SOCKET s, struct sockaddr FAR* name, int FAR* namelen );
29
2.函数参数说明 该函数的参数说明如下: ● s:传入参数,标识一个套接口的描述字。 ● name:传出参数,指向返回的本地协议地址的指针。 ● namelen:传出参数,本地协议地址长度。当函数调用完成时, 它可以返回实际的本地地址长度。
30
3.函数返回信息 调用该函数时若无错误发生,则getsockname( )函数返回0。如果调用失败,则返回SOCKET_ERROR错误信息,应用程序可通过WSAGetLastError( )函数来获取如下的错误代码: ● WSANOTINITIALISED:在使用此API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:Windows套接口实现检测到网络子系统失效。 ● WSAEFAULT:namelen参数不够大。 ● WSAEINPROGRESS:一个阻塞的Windows套接口调用正在运行中。 ● WSAENOTSOCK:描述字不是一个套接口。 ● WSAEINVAL:套接口未用bind( )捆绑。
31
4.函数使用说明 getsockname( )函数用于获取一个套接口的协议地址,它用于一个已绑定或已连接套接口。本调用特别适用于如下情况:未调用bind( )就调用了connect( ),这时惟有getsockname( )调用可以获知系统内定的本地地址。在返回时,namelen参数包含了名字的实际字节数。 若一个套接口与INADDR_ANY绑定,即该套接口可以用任意的主机地址,此时除非调用connect( )或accept( )来连接,否则getsockname( )将不会返回主机IP地址的任何信息。除非套接口被连接,Windows套接口应用程序不应假设IP地址会从INADDR_ANY变成其他地址。这是因为对于一个有多个IP地址的主机来说,除非套接口被连接,否则该套接口所用的IP地址是不可知的(即不能被确定是哪一个)。
32
8.3.4 根据主机名取得主机信息——gethostbyname( )或WSAAsyncGetHostByName( )
gethostbyname( )和WSAAsynGetHostByName( )这两个Winsock API函数从主机数据库中取回与指定的主机名对应的主机信息。这两个函数均返回一个hostent结构型的量,所以先介绍一下该结构的格式。hostent结构的定义如下: struct hostent { char FAR * h_name; /* official name of host */ char FAR * FAR * h_aliases; /* alias list */ short h_addrtype; /* host address type */
33
short h_length; /* length of address */
char FAR * FAR * h_addr_list; /*list of addresses */ #define h_addr h_addr_list[0] /* address, for backward compat */ };
34
该结构中各字段的含义如下: ● h_name:该字段是正式的主机名。 ● h_aliases:它是二维字符指针,返回一台主机的所有别名(即别名列表)。 ● h_addrtype:表示主机地址类型,如AF_INET表示IPv4地址。 ● h_length:该字段可以返回主机地址的字节数。 ● h_addr_list:该字段返回一个主机的所有IP地址,因为一台主机可以分配若干个IP地址(当然这主要是对服务器而言的)。这个数组中的每个地址都是按网络字节顺序返回的。一般情况下,应用程序都采用该数组中的第一个地址。但是,如果返回的地址不止一个,应用程序就会相应地选择一个最恰当的地址,而不是一直都用第一个地址。
35
● h_addr:是为了保持向后兼容而定义的字段,一般很少使用。
1.函数格式 在Winsock 1中提供的gethostbyname( )函数的格式是: struct hostent FAR *gethostbyname( const char FAR* name ); 在Winsock 1中提供的异步扩WSAAsyncGetHostByName( )函数的格式是:
36
HANDLE WSAAsyncGetHostByName(
HWND hWnd, unsigned int wMsg, const char FAR * name, char FAR * buf, int buflen );
37
2.函数参数说明 gethostbyname( )函数中各参数的说明如下: ● name:传入参数,是一个指向主机名的指针。 WSAAsyncGetHostByName( )函数是一个Windows异步扩展函数,它在函数结束时,利用Windows消息向应用程序发出通知,其各参数的含义是: ● hWnd:传入参数, 是一个窗口句柄, 表示当异步请求完成时,该窗口句柄应该收到一条消息。 ● wMsg:传入参数,当异步请求完成时,将要接收的消息。
38
● name:传入参数,指向主机名的指针。
● buf:传出参数, 接收hostent数据的数据区指针。注意该数据区必须大于hostent结构的大小,这是因为Windows Sockets实现不仅要用该数据区容纳hostent结构,而且hostent结构的成员引用的所有数据也要在该区域内。建议用户提供一个MAXGETHOSTSTRUCT字节大小的缓冲区。 ● buflen:传入参数,上述数据区的大小。
39
3.函数返回信息 调用该函数时如果没有错误发生,gethostbyname( )函数返回如上所述的一个指向hostent结构的指针。如果调用失败则返回一个空指针,应用程序可以通过WSAGetLastError( )来得到一个特定的错误代码,错误代码说明如下: ● WSANOTINTIALISED:在应用这个API前,必须成功调用WSAStartup( )。 ● WSAENETDOWN:Windows Sockets实现检测到了网络子系统的错误。 ● WSAHOST_NOT_FOUND:没有找到授权应答主机。 ● WSATRY_AGAIN:没有找到非授权主机,或者服务器故障(SERVERFAIL)。
40
● WSANO_RECOVERY:无法恢复的错误,如查询有格式错误(FORMERR)、拒绝服务 (REFUSED)没有通信处理机、(NOTIMP)。
● WSANO_DATA:有效的名字,但没有关于请求类型的数据记录。 ● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。 ● WSAEINTR:阻塞调用被WSACancelBlockingCall( )取消了。
41
4.异步扩展函数返回信息 WSAAsyncGetHostByName( )函数是gethostbyname( )函数的Windows异步扩展函数。所谓“异步”,指的是Windows Sockets的实现启动该操作后立刻回到调用方,并传回一个异步任务句柄,应用程序可以用它来标识该操作。 当异步操作完成时,如果成功则将主机名和地址信息拷贝到buf缓冲区中,同时向句柄为hWnd的应用程序窗口发送一条消息(wMsg)。wParam参数包含了初次函数调用时返回的异步任务句柄,lParam的高16位包含着错误代码,该代码可以是Winsock2.h中定义的任何错误。错误代码为0说明异步操作成功,在成功完成的情况下,提供给初始函数调用的缓冲区中包含了一个hostent结构。为存取该结构中的元素,初始的缓冲区指针应置为hostent结构的指针。
42
若错误代码为WSAENOBUFS,则说明在初始调用时由buflen指出的缓冲区大小对于容纳所有的结果信息来说太小了。在这种情况下,lParam的低16位提供所有信息所需的缓冲区大小。如果应用程序认为获取的数据不够,它就可以在设置了足够容纳所需信息的缓冲区(也就是大于lParam低16位提供的数值)后,重新调用WSAAsyncGetHostByName( )函数。 错误代码和缓冲区大小应使用WSAGETASYNCERROR和WSAGETASYNCBUFLEN宏从lParam中取出。这两个宏的定义如下:
43
#define WSAGETASYNCERROR(lParam) HIWORD(lParam)
#define WSAGETASYNCBUFLEN(lParam) LOWORD(lParam) 使用这些宏可以最大限度地提高应用程序源代码的可移植性。 函数的返回值指出异步操作是否成功启动,注意它并不隐含操作本身的成功或失败(后面要介绍的其他异步函数的返回值也与它类似)。
44
若操作成功启动,则WSAAsyncGet-HostByName( )返回一个HANDLE类型的非0值,它用来标识该异步请求任务的句柄。通过该句柄使用WSACancelAsyncRequest( )函数可取消该操作,也可通过检查wParam消息参数,以匹配异步操作和完成消息。如果异步操作不能启动,WSAAsyncGetHostByName( )返回一个0值,并且可使用WSAGetLastError( )函数来获取如下的错误代码: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。
45
● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。
● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。 ● WSAEWOULDBLOCK:本异步操作此时由于Windows Sockets实现的资源或其他限制的制约而无法调度。 在应用程序的窗口收到消息时可能会设置下列错误代码,它们可以通过WSAGETA-SYNCERROR宏从应答的消息lParam中取出:
46
● WSAENOBUFS:可用的缓冲区空间不足或没有。
● WSAHOST_NOT_FOUND:未找到授权应答主机。 ● WSATRY_AGAIN:未找到非授权应答主机,或SERVERFAIL。 ● WSANO_RECOVERY:不可恢复性错误。 ● WSANO_DATA:无请求类型的数据记录。
47
8.3.5 根据主机地址取得主机信息——gethostbyaddr( )或WSAAsyncGetHostByAddr( )
这两个函数可以根据主机的IP地址取得主机名和主机地址等信息。 1.函数格式 在Winsock 1中提供的gethostbyaddr( )函数的格式是: struct hostent FAR *gethostbyaddr( const char FAR* addr, int len, int type );
48
在Winsock 1中提供的异步扩展WSAAsyncGetHostByAddr( )函数的格式是:
HANDLE WSAAsyncGetHostByAddr( HWND hWnd, unsigned int wMsg, const char FAR* addr, int len, int type, char FAR* buf, int buflen );
49
2.函数参数说明 gethostbyaddr( )函数的参数说明如下: ● addr:传入参数,指向网络字节顺序地址的指针。 ● len:传入参数,地址的长度,如果是IPv4类型的地址,则该值为4。 ● type:传入参数,地址类型,如为AF_INET。 WSAAsyncGetHostByAddr( )函数是gethostbyaddr( )函数的异步版本。它的其他参数以及返回信息与WSAAsyncGetHostByName( )函数完全相同。
50
3.函数返回信息 这两个函数返回的信息与通过名称获取主机信息的gethostbyname( )函数相同。 根据协议名取得主机协议信息——getprotobyname( )或WSAAsync-GetProtoByName( ) 函数getprotobyname()WSAAsyncGetProtoByName()可以根据协议名称返回对应的相关协议信息。它们都要使用到一个与协议有关的结构,该结构的定义如下:
51
struct protoent{ char FAR* p_name; char FAR* FAR * p_aliases; short p_proto; }; 该结构的成员有: ● p_name:正式的协议名。 ● p_aliases:它是二维字符指针,返回一个协议的所有别名。 ● p_proto:以主机字节顺序排列的协议号。
52
1.函数格式 在Winsock 1中提供的getprotobyname( )函数的格式是: struct protoent FAR *getprotobyname( const char FAR * name ); 在Winsock 1中提供的异步扩展WSAAsyncGetProtoByName( )函数的格式是: HANDLE WSAAsyncGetProtoByName( HWND hWnd, unsigned int wMsg, const char FAR * name, char FAR * buf, int buflen
53
2.函数参数说明 getprotobyname( )函数中的参数name为传入参数,指向协议名的指针。 异步扩展格式中的buf是接收protoent数据的缓冲区指针,buflen为该缓冲区的大小。其他参数与WSAAsyncGetHostByName( )函数中的参数相同,返回信息的含义也一样。 3.函数返回信息 如果没有错误发生,getprotobyname( )返回如上所述的一个指向protoent结构的指针,如果调用失败,则返回一个空指针。应用程序可以通过WSAGetLastError( )来得到一个如下所示的特定错误代码:
54
● WSANOTINTIALISED:在应用这个API前,必须成功调用WSAStartup( )。
● WSAENETDOWN:Windows Sockets实现检测到了网络子系统的错误。 ● WSANO_RECOVERY:无法恢复的错误,如FORMERR、REFUSED、NOTIMP等。 ● WSANO_DATA:有效的名字,但没有关于请求类型的数据记录。
55
● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。
● WSAEINTR:阻塞调用被WSACancelBlockingCall( )取消了。 ● WSAHOST_NOT_FOUND:没有找到协议。 ● WSATRY_AGAIN:非正式的协议没有找到或服务器失败。 ● WSAEFAULT:name参数不在有效的用户地址空间。
56
8.3.7 根据协议号取得主机协议信息——getprotobynumber( )或WSAAsyncGetProtoByNumber( )
1.函数格式 在Winsock 1中提供的getprotobynumber( )函数的格式是: struct protoent FAR *getprotobynumber( int number );
57
在Winsock 1中提供的异步扩展WSAAsyncGetProtoByNumber( )函数的格式是:
HANDLE WSAAsyncGetProtoByNumber( HWND hWnd, unsigned int wMsg, int number, char FAR * buf, int buflen );
58
2.函数参数说明 参数number表示传入参数,是一个以主机字节顺序排列的协议号。异步扩展格式中的其他参数与WSAAsyncGetHostByName( )函数中的参数含义相同。 3.函数返回信息 函数的返回信息与通过协议名获取主机协议信息时的情况相同。
59
8.3.8 根据服务名取得相关服务信息—— getservbyname( )或WSAAsync-GetServByName( )
getservbyrvame( )和WSAAsyncGetServByName( )函数用于返回对应于给定服务名和协议名的相关服务信息。这两个函数都要用到一个如下所示的结构: struct servent{ char FAR* s_name; char FAR* FAR* s_aliases; short s_port; char FAR * s_proto; };
60
该结构中各成员的含义如下: ● s_name:正规的服务名。 ● s_aliases:所有其他的可选服务名。 ● s_port:连接该服务时需要用到的端口号,返回的端口号是以网络字节顺序排列的。 ● s_proto:连接该服务时用到的协议名。
61
1.函数格式 在Winsock 1中提供的getservbyname( )函数的格式是: struct servent FAR *getservbyname( const char FAR* name, const char FAR* proto ); 在Winsock 1中提供的异步扩展WSAAsyncGetServByName( )函数的格式是:
62
HANDLE WSAAsyncGetServByName(
HWND hWnd, unsigned int wMsg, const char FAR* name, const char FAR* proto, char FAR * buf, int buflen );
63
2.函数参数说明 这两个函数中各参数的说明如下: ● name:传入参数,一个指向服务名的指针。 ● proto:传入参数,指向协议名的指针(可选)。如果这个指针为空,getservbyname( )返回第一个name与s_name或者某一个s_aliases匹配的服务条目。否则,getservbyname( )对name和proto都进行匹配。 其他参数与WSAAsyncGetHostByName( )函数中的参数含义相同。
64
8.3.9 根据端口号取得相关服务信息——getservbyport( )或WSAAsync-GetServByPort( )
1.函数格式 在Winsock 1中提供的getservbyport( )函数的格式是: struct servent FAR *getservbyport( int port, const char FAR *proto );
65
在Winsock 1中提供的异步扩展WSAAsyncGetServByPort( )函数的格式是:
HANDLE WSAAsyncGetServByPort( HWND hWnd, unsigned int wMsg, int port, const char FAR * proto, char FAR * buf, int buflen );
66
2.函数参数说明 这两个函数中需要说明的参数如下: ● port:传入参数,给定的端口号,以网络字节顺序排列。 ● proto:传入参数,指向协议名的指针(可选)。如果这个指针为空,getservbyport( )返回第一个port与s_port匹配的服务条目。否则,getservbyport( )对port和proto都进行匹配。 其他参数与WSAAsyncGetHostByName( )函数中的参数含义相同。
67
网络信息获取函数应用实例 本小节举一个实例来说明以上介绍的网络信息获取函数的用法。本实例只使用了三个比较典型的函数,其他函数的用法与此类似。要说明的是,虽然这只是一个关于网络信息获取函数用法的实例,但该程序也是一个非常有用的实用程序,它可以获得一台主机的主机名、主机别名(如果有的话)、主机IP地址列表等信息。
68
/*************************************************************************
调试环境:VC++6.0 程序名称:host.cpp 程序功能:该程序使用网络信息获取函数取得主机的有关信息,在程序中使用了以下三个函数: gethostname( ) gethostbyname( ) getprotobyname( ) 命令格式:host **************************************************************************/
69
#include<winsock2.h>
#include<stdio.h> #include<stdlib.h> void main( ) { WSADATA wsaData; int n; //存放主机名称 char hostname[256];
70
//主机信息指针 hostent * pHostent; //主机协议信息指针 protoent * pProtoent; struct sockaddr_in sa; if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0) { printf("Failed to load Winsock.\n "); return; }
71
printf("---------------------------------------------\n");
//获得主机名 if(gethostname(hostname, sizeof(hostname))!= 0) { printf("gethostname( ) Error: %u\n", WSAGetLastError( )); return; }
72
printf("以下信息由gethostname( )函数取得\n");
printf("Local host name:%s\n", hostname); printf(" \n"); //根据主机名获取主机信息 pHostent=gethostbyname(hostname); if (pHostent==NULL) { printf("gethostbyname( ) Error: %u\n", WSAGetLastError( )); return ; }
73
//解析返回的hostent结构中名称、别名、地址类型和地址长度信息
printf("以下信息由gethostbyname( )函数取得\n"); printf("name:%s\naliases:%s\naddrtype:%d\nlength:%d\n", pHostent->h_name, pHostent->h_aliases, pHostent->h_addrtype, pHostent->h_length);
74
//解析hostent结构中的主机地址 for (n=0; pHostent->h_addr_list[n]; n++) { memcpy(&sa.sin_addr.s_addr, pHostent->h_addr_list[n],pHostent->h_length); //输出主机IP地址. printf("Address: %s\n", inet_ntoa(sa.sin_addr)); } printf(" \n"); //根据协议名获得协议信息 pProtoent=getprotobyname("tcp"); if (pProtoent==NULL)
75
{ printf("getprotobyname( ) Error: %u\n", WSAGetLastError( )); return ; } //解析protoent结构中的信息 printf("以下信息由getprotobyname( )函数取得\n"); printf("name:%s\nproto:%d\n", pProtoent->p_name, pProtoent->p_proto);
76
for (n=0; pProtoent->p_aliases[n]; n++)
{ printf("aliases: %s\n",pProtoent->p_aliases[n] ); } WSACleanup( ); 程序的运行结果如图8-1所示。
77
图8-1 网络信息获取函数使用实例程序的运行结果
78
8.4 套接口选项函数 8.4.1 套接口选项函数说明 1.函数格式 套接口选项获取函数getsockopt( )的格式如下:
套接口选项函数说明 1.函数格式 套接口选项获取函数getsockopt( )的格式如下: int setsockopt( SOCKET s, int level, int optname, const char FAR* optval, int optlen );
79
套接口选项设置函数setsockopt( )的格式如下:
int getsockopt( SOCKET s, int level, int optname, char FAR* optval, int FAR* optlen );
80
2.函数参数说明 setsockopt( )函数和getsockopt( )函数的参数是一样的,但 setsockopt( )函数的optval和optlen选项应该由应用程序填写,而getsockopt( )函数的这两个选项是返回值,由系统来填写。 函数中各参数的含义如下: ● s:传入参数,参数s指定一个有效的套接口,我们要对这个套接口的选项进行操作。 ● level:传入参数,套接口选项定义的级别(层次)。常用的级别有SOL_SOCKET、IPPROTO_IP和IPPROTO_TCP,在8.4.2~8.4.4节将对这些级别对应的选项进行专门介绍。
81
● optname:传入参数,需设置或获取的套接口选项,这些选项的名称均是在Winsock头文件内定义的常数值。
● optval:对于setsockopt( )函数来说, 它是传入参数;对于getsockopt( )函数来说,它是传出参数。该参数是指向存放选项值缓冲区的指针。 ● optlen:对于setsockopt( )函数来说, 它是传入参数;对于getsockopt( )函数来说,它既是传出参数也是传入参数。该参数是指向optval缓冲区长度(或套接口选项变量的长度)的指针。
82
3.函数返回信息 若函数调用时无错误发生,则setsockopt( )函数和getsockopt( )函数都返回0。如果调用发生错误,则返回SOCKET_ERROR错误信息,应用程序可通过WSAGetLastError( )函数获取对错误信息的进一步说明。以下是错误代码的含义说明: ● WSANOTINITIALISED:在使用此API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:Windows套接口实现已检测到网络子系统失效。 ● WSAEFAULT:setsockopt( )函数返回时,表示optval不是进程地址空间中的一个有效部分;getsockopt( )函数返回时,表示optlen参数非法。
83
● WSAEINPROGRESS:一个阻塞的Windows套接口调用正在运行中。
● WSAEINVAL:level值非法,或optval中的信息非法。 ● WSAENETRESET:当SO_KEEPALIVE设置后连接超时。 ● WSAENOPROTOOPT:未知或不支持选项。其中,SOCK_STREAM类型的套接口不支持SO_BROADCAST选项,SOCK_DGRAM类型的套接口不支持SO_DONTLINGER、SO_KEEPALIVE、SO_LINGER和SO_OOBINLINE选项。 ● WSAENOTCONN:当设置SO_KEEPALIVE后连接被复位。 ● WSAENOTSOCK:描述字不是一个套接口。
84
SOL_SOCKET选项级别 SOL_SOCKET选项级别主要针对传输层协议(TCP或UDP)。在SOL_SOCKET选项级别下,套接口的选项有两种类型:一种是值为布尔型(BOOL)的选项,这种选项可以允许或禁止一种特性;另一种是值为整型(int)或结构型(Struct Linger)的选项,这种选项可以用来设置系统工作时的某些参数。
85
允许一个布尔型选项,则将optval指向一个非零的整型数;禁止一个布尔型选项,则可以将optval指向一个等于零的整型数。对于布尔型选项,optlen应等于sizeof(int)。对于非布尔型的其他选项,optval应该指向包含所需选项的整型量或结构量,而optlen则为整形量或结构量的长度。 还要注意一个问题,套接口的有些属性值既可以设置(即可以使用setsockopt( )函数),也可以获取(即使用getsockopt( )函数),但有些套接口属性只能获取或只能设置。 后面要介绍的其他两种选项级别(IPPROTO_IP和IPPROTO_TCP)与SOL_SOCKET选项级别的情况类似。 表8-1所示为在SOL_SOCKET选项级别下的各种选项。
86
表8-1 SOL_SOCKET选项级别下的各选项
88
(1) SO_ACCEPTCONN:该选项只能获取。如果已通过listen( )函数调用, 套接口将进入监听模式,这时获取这个选项时就会返回TRUE。数据报类型的套接口(SOCK_DGRAM)不支持这一选项。 (2) SO_BROADCAST: 该选项可以获取也可以设置。如果指定的套接口该选项已经设置为TRUE,则允许套接口广播收发信息。该选项只有对非SOCK_STREAM类型的所有套接口才是有效的。
89
广播是一种特殊的数据发送方法,使本地子网上的所有机器都能收到相同的数据。广播通信的缺点在于假如同时有许多进程发送广播数据,网络立刻就会拥挤不堪,造成网络性能大大降低,甚至有可能陷入瘫痪。要想接收一条广播消息,首先必须启用广播选项,然后使用某个数据报接收函数,比如recvfrom( )或WSARecvFrom( )。该选项的设置方法如下:
90
…… BOOL bBroadcast=TRUE; //创建一个数据报套接口 s=socket(AF_INET,SOCK_DGRAM,0); //设置广播选项 if(setsocketopt(s,SOL_SOCKET,SO_BROADCAST,(char*)&bBroadcast,sizeof (BOOL))==SOCKET_ERROR) { printf(''setsocketopt( ) Error:u%\n'',WSAGetLastError( )); return; }
91
(3) SO_CONNECT_TIME:该选项只能获取。它是一个微软Windows操作系统专用选项,用于返回连接建立的时间,以秒为单位。该选项可以在客户端的SOCKET句柄上调用,以判断连接是否已经建立,以及建立了多长时间。若套接口当前尚未建立连接,返回值便是0xFFFFFFFF。 (4) SO_DEBUG:该选项值可以获取,也可以设置。在应用程序设置了SO_DEBUG选项的情况下,系统将记录调试信息。至于调试信息如何展示,要取决于服务提供者的基层实施方式。要想打开调试信息,可调用setsockopt( )函数设置SO_DEBUG为TRUE。若用SO_DEBUG选项调用getsockopt( )函数,会返回TRUE或FALSE,分别代表允许和禁止记录调试信息。但目前尚无任何一种Win32平台支持的SO_DEBUG选项。
92
(5) SO_LINGER:该选项值可以获取,也可以设置。该选项不能使用在SOCK_DGRAM类型的套接口上。它用于控制在closesocket( )函数已执行的情况下,如何处理套接口数据排列上未发送完毕的数据。该选项要通过linger结构来设置,该结构的定义如下: struct linger{ int l_onoff; int l_linger; };
93
l_onoff字段用来控制SO_LINGER选项的打开或关闭,为了使该选项起作用,该字段应该设置为一个非0的值。l_linger字段表示延迟时间,以秒为单位。
当l_onoff和l_linger字段取不同的值时,系统的操作方式也不同,它们的取值情况如表8-2所示。
94
表8-2 SO_LINGER选项取值情况说明
95
(6) SO_DONTLINGER:该选项值可以获取,也可以设置。若在一个流类套接口上设置了SO_DONTLINGER选项,也就是说将linger结构的l_onoff域设为0,则closesocket( )调用立即返回,但是,如果可能,排队的数据将在套接口关闭之前被发送。SO_DONTLINGER和SO_LINGER选项将直接影响使用closesocket( )函数关闭一个套接口时系统对套接口的操作行为。表8-3总结了这两个选项对套接口关闭方式的影响。
96
表8-3 SO_DONTLINGER和SO_LINGER选项
对套接口关闭方式的影响
97
(7) SO_DONTROUTE:该选项可以获取,也可以设置。
(8) SO_ERROR:该选项只能获取,用于返回以具体套接口为基础的错误代码,在返回后系统一般将该选项设置为0。 (9) SO_KEEPALIVE:该选项可以获取,也可以设置,它只适用于以TCP为基础的套接口。在Windows 95及Windows 98操作系统中,这两个键都位于下述注册表路径中: \HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\VxD\MSTCP
98
而在Windows NT和Windows 2000中,这两个键位于下述位置:
\HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\TCPIP\Parameters 服务器(例如FTP服务器)一般要设置该选项,这样当客户在非正常终止连接的情况下,经过一段时间后,服务器也能终止该连接。 (10) SO_MAX_MSG_SIZE:该选项只能获取。 (11) SO_OOBINLINE:该选项可以获取,也可以设置。
99
(12) SO_PROTOCOL_INFO:该选项只能获取,获取WSAPROTCOL_INFO结构中与套接口关联在一起的协议特征信息。该选项由Winsock 2支持。
(13) SO_RCVBUF:该选项可以获取,也可以设置。 (14) SO_SNDBUF:该选项可以获取,也可以设置。与SO_RCVBUF选项相对应,该选项用于确定发送缓冲区的大小。 (15) SO_TYPE:该选项只能获取, 用于返回指定套接口的类型。可能的套接口类型包括SOCK_DGRAM、SOCK_STREAM和SOCK_RAW。 (16) SO_SNDTIMEO:该选项可以获取,也可以设置。
100
(17) SO_RCVTIMEO:该选项可以获取,也可以设置。
(18) SO_REUSEADDR:该选项可以获取,也可以设置,表示该套接口能和一个已在使用中的地址捆绑。 (19) SO_EXCLUSIVEADDRUSE:该选项可以获取,也可以设置。该选项的作用是禁止其他进程在一个本地地址上使用SO_REUSEADDR(重复使用地址)。
101
IPPROTO_IP选项级别 该选项级别包括的主要选项如表8-4所示。
102
表8-4 IPPROTO_IP级别套接口选项
103
(1) IP_OPTIONS:该选项可以获取,也可以设置。它是专门针对IP数据报选项字段的,通过该选项字段,用户可以获取IP数据报头选项字的设置情况,也可以对IP选项字段进行设置。IP数据报选项的格式如图2-12所示。 ● 记录路由:IP数据报在传输过程中,每经过一个路由器,路由器就将自己的IP地址添加到IP数据报头内的选项字段位置。 ● 记录时间戳:IP数据报在传输过程中,每经过一个路由器,路由器就将自己的IP地址及时间添加到IP数据报头内的选项字段位置。
104
● 宽松源路由选择:IP数据报在传输过程中,要经过IP数据报选项内列出的每个IP地址,但也可以经过其他的IP地址。
下面我们以记录路由为例说明如何进行IP_OPTIONS选项的设置。 第一步:先声明一个数据结构,它包括选项码、选项长度、偏移量与选项数据4个字段。要注意的是,由于IP选项最长为40字节,所以所定义的结构长度不能超过这个数值。
105
第二步:根据要设置的选项,对说明的选项数据结构各字段进行初始化。选项码字段的值见2.3.3小节。
第三步:使用setsockopt( )函数对IP_OPTIONS选项进行设置。 以上三步对应的代码如下: //定义一个数据结构 struct ip_oprion_hdr { unsigned char code; unsigned char length; unsigned char offset; unsigned long addrs[9]; } opthdr; ……
106
//对定义的结构进行初始化 byzero((char*)&opthdr,sizeof(opthdr)); //记录路由选项码 opthdr.code=0x87; //总的选项长度 opthdr.length=39; //选项数据的初始位置 opthdr.offset=4; //设置记录路由选项 ret=setsockopt(s,IPPROTO_IP,IP_OPTIONS,(char*)&opthdr,sizeof(opthdr)); ……
107
(2) IP_HDRINCL:该选项可以获取,也可以设置。
(3) IP_TOS:该选项可以获取,也可以设置。 (4) IP_TTL:该选项可以获取,也可以设置。 (5) IP_MULTICAST_TTL:该选项可以获取,也可以设置。 (6) IP_MULTICAST_IF:该选项可以获取,也可以设置,用于为IP多播设置一个本地接口,本地机器以后发出的任何多播数据都会经由该接口传送出去。
108
(7) IP_MULTICAST_LOOP:该选项可以获取,也可以设置。该选项用于控制发送的多播数据是否进入本地套接口的输入数据队列中。
(8) IP_ADD_MEMBERSHIP:该选项只能设置。该选项实际是由Winsock1提供的一个方法,可将一个套接口加入一个指定的IP多播组,此时需要用socket函数来创建地址族为AF_INET的一个套接口,同时将套接口的类型设为SOCK_DGRAM。要想将套接口加入一个多播组,要使用如下结构:
109
srtuct ip_mreq{ struct in_addr imr_multiaddr; struct in_addr imr_interface; }; 在ip_mreq结构中,imr_multiaddr对应于套接口将要加入的那个多播组的二进制格式地址;而imr_interface对应于将要用来收发多播数据的一个本地接口。
110
(9) IP_DROP_MEMBERSHIP:该选项只能设置,用于将套接口从指定的IP组内删去,即撤销其成员资格。该选项正好与IP_ADD_MEMBERSHIP相反。使用ip_mreq结构调用该选项,并在该结构中放入与当初加入指定多播组时相同的值,套接口便会从那个组内删去,脱离成员关系。 (10) IP_DONTFRAGMENT:该选项可以获取,也可以设置,用来控制是否对IP数据报进行分段。如果设置为TRUE,IP数据报在传输过程中就不需要对IP数据报进行分段处理,当然这样就存在一个问题,当一个IP数据报的大小已经超过了最大数据传输单元(MTU)的值时,这个数据报就会被丢弃,同时向发送者返回一条ICMP错误消息,提示用户数据需要分段,但未设置分段标志位。
111
IPPROTO_TCP选项级别 该选项是针对TCP协议的。在Winsock中仅有一个IPPROTO_TCP级别的选项,即TCP_NODELAY选项,该选项用来打开或关闭Nagle算法。如果该选项的值为TRUE,则在对应的套接口上禁止使用Nagle算法。在系统默认情况下,Nagle算法是打开的,该选项只适用于流式套接口(SOCK_STREAM),其地址族为AF_INET。这个选项可用在所有Winsock版本上,并得到了所有Win 32平台的支持。
112
Nagle算法将未确认的数据存入缓冲区,直到蓄足一个包后一起发送,这样做的好处是可以减少主机发送的零碎小数据包的数目,以减少网络通信的开销,提高系统的吞吐量。但对于某些应用来说,这种算法将降低系统性能,例如交互性较强的Telnet应用程序,用户可通过它登录至一台远程机器,然后向其传送命令。通常,用户每秒只会进行少量的键击,若使用Nagle算法,便会造成响应的迟钝,甚至造成对方主机不予应答的错觉。此时,使用TCP_NODELAY选项可将此算法关闭,以适应一些交互性较强的应用程序。应用程序编写者只有在确切了解它的效果并在确实需要的情况下,才可设置TCP_NODELAY选项,因为在一般的应用中如果设置该选项,则对网络性能有明显的负面影响。
113
套接口属性设置和获取实例 在本实例程序中,使用getsockopt( )函数来获取套接口的类型、接收缓冲和发送缓冲的大小,然后使用setsockopt( )函数对套接口接收缓冲进行重新设置,并用getsockopt( )函数获取重新设置以后的大小。实例程序如下: /************************************************************************* 调试环境:VC++6.0 程序名称:socketopt.cpp 程序功能:该程序演示了getsockopt( )函数和setsockopt( ) 函数在程序中的用法 命令格式:socketopt **************************************************************************/
114
#include<winsock2.h>
#include<stdio.h> #include<stdlib.h> void main( ) { WSADATA wsaData; SOCKET s; //存放选项值 int optname; //选项的长度 int optlen; if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
115
{ printf("Failed to load Winsock.\n"); return; } //创建一个套接口 s=socket(AF_INET,SOCK_STREAM,0); if(s==INVALID_SOCKET) printf("socket( ) Error.\n");
116
optlen=sizeof(optname);
printf(" \n"); //获得套接口类型 if(getsockopt(s,SOL_SOCKET,SO_TYPE, (char*)&optname,&optlen)==SOCKET_ERROR) { printf("getsockopt( ) Error: %u\n", WSAGetLastError( )); return; } //把获取的套接口类型进行显示 printf("以下信息是由getsockopt( )函数取得的SO_SOCKET级别选项实例\n");
117
switch(optname) { case SOCK_STREAM: printf("SO_TYPE:SOCK_STREAM\n");break; case SOCK_DGRAM: printf("SO_TYPE:SOCK_DGRAM\n");break; case SOCK_RAW: printf("SO_TYPE:SOCK_RAW\n"); }
118
//获取接收缓冲的大小 if(getsockopt(s,SOL_SOCKET,SO_RCVBUF, (char*)&optname,&optlen)==SOCKET_ERROR) { printf("getsockopt( ) Error: %u\n", WSAGetLastError( )); return; } printf("SO_RCVBUF:%d\n",optname); //获取发送缓冲的大小
119
if(getsockopt(s,SOL_SOCKET,SO_SNDBUF,. (char
if(getsockopt(s,SOL_SOCKET,SO_SNDBUF, (char*)&optname,&optlen)==SOCKET_ERROR) { printf("getsockopt( ) Error: %u\n", WSAGetLastError( )); return; } printf("SO_SNDBUF:%d\n",optname); //使用setsockopt( )函数设置接收缓冲 optname=16*1024;
120
if(setsockopt(s,SOL_SOCKET,SO_RCVBUF,(char
if(setsockopt(s,SOL_SOCKET,SO_RCVBUF,(char*)&optname,sizeof(optname))==SOCKET_ERROR) { printf("setsockopt( ) Error: %u\n", WSAGetLastError( )); return; } //获取重新设置的接收缓冲大小 if(getsockopt(s,SOL_SOCKET,SO_RCVBUF, (char*)&optname,&optlen)==SOCKET_ERROR)
121
{ printf("getsockopt( ) Error: %u\n", WSAGetLastError( )); return; } printf(" \n"); printf("使用setsockopt( )函数设置后取得的SO_RCVBUF数值\n"); printf("SO_RCVBUF:%d\n",optname); WSACleanup( );
122
图8-2是该程序的运行结果。 图8-2 套接口选项设置与获取实例程序的运行结果
123
8.5 套接口I/O处理函数 8.5.1 阻塞与非阻塞通信方式
阻塞与非阻塞通信方式 在Windows环境下,套接口的通信方式分为两种:阻塞方式和非阻塞方式。因此,在编写网络应用程序时,要选择套接口的工作方式,就必须要了解套接口阻塞方式和非阻塞方式的工作特点。 阻塞方式下工作的套接口在进行I/O操作时,函数要等待到相关操作完成以后才能返回(或者可以使用WSACancelBlockingCall( )调用唤起一个阻塞操作)。
124
非阻塞方式下工作的套接口在进行I/O操作时,无论操作是否成功,调用都会立即返回。
阻塞方式和非阻塞方式的套接口各有其优点和缺点。阻塞方式的套接口编程简单,易于实现。正因为如此,一个套接口的默认操作模式被设置为阻塞方式。如果要使套接口工作在非阻塞方式下,就要使用ioctlsocket( )调用进行设置。阻塞方式的套接口在下面几种情况下会显得难于管理: ● 当有多个已建立连接的套接口需要进行管理时; ● 当发送的数据量不均匀或接收的数据量不均匀时; ● 当发送或接收的数据时间不确定时。
125
非阻塞方式的套接口可以很好地处理上述情况,因此其功能强大,编程灵活,尤其是非阻塞方式下的套接口能够很好地在非抢先的Windows环境下工作,因此,在进行程序设计时,应该尽量使用非阻塞方式(异步方式)的操作。但非阻塞方式的套接口编程较为复杂,并且由于操作常常失败,因此在程序中就要考虑操作失败时应该如何进行处理。 当然对有些操作来说,阻塞方式和非阻塞方式并没有什么不同。
126
8.5.2 设置套接口的工作方式——ioctlsocket( )和WSAIoctl( )
1.函数格式 ioctlsocket( )函数的格式如下: int ioctlsocket( SOCKET s, long cmd, u_long FAR* argp );
127
在Winsock 2中引入了一个新的功能更强大的函数WSAIoctl( ),它的格式如下:
int WSAIoctl( SOCKET s, DWORD dwIoControlCode, LPVOID lpvInBuffer, DWORD cbInBuffer, LPVOID lpvOutBuffer, DWORD cbOutBuffer, LPDWORD lpcbBytesReturned, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine );
128
2.函数参数说明 ioctlsocket( )函数中各参数的说明如下: ● s:传入参数,标识一个套接口的描述字。 ● cmd:传入参数,预定义好的标志,表示对套接口s的操作控制命令,具体支持的命令下面将单独介绍。 ● argp:传入参数/传出参数,指向cmd命令所带参数的指针。
129
WSAIoctl( )函数与ioctlsocket( )函数前两个参数的含义基本相同。但WSAIoctl( )函数增添了许多新的选项:首先,它将单个argp参数分解成了一系列输入参数,用于容纳传递到函数内部的值;其次,它提供了一系列输出参数,用于容纳来自调用返回的数据。其各参数的含义如下: ● s:传入参数,标识一个套接口的句柄。 ● dwIoControlCode:传入参数,指示将要进行的操作的控制代码。 ● lpvInBuffer:传入参数,它指向函数的输入参数(用于描述函数输入参数缓冲区的地址)。 ● cbInBuffer:传入参数,用于描述输入缓冲区的大小。该参数和lpvInBuffer参数一起确定函数的输入参数。
130
● lpvOutBuffer:传出参数,它指向函数的返回参数(用于描述函数返回数据缓冲区的地址)。
● cbOutBuffer:传入参数,用于描述返回参数缓冲区的大小。该参数和lpvOutBuffer参数一起确定函数的返回数值。 ● lpcbBytesReturned:传出参数,指向函数实际返回的字节数的地址。 ● lpOverlapped:传入参数,WSAOVERLAPPED结构的地址。 ● lpCompletionRoutine:传入参数,一个指向操作结束后调用的例程指针。该参数和前一个参数lpOverlapped使用在重叠I/O中。
131
3.函数返回信息 ioctlsocket( )函数和WSAIoctl( )函数成功调用后,都返回0;调用不成功,则ioctlsocket( )函数返回SOCKET_ERROR错误信息,而WSAIoctl( )函数返回INVALID_SOCKET错误信息。应用程序可通过WSAGetLastError( )获取如下的错误代码: ● WSANOTINITIALISED:在使用此API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:Windows套接口实现已检测到网络子系统失效。 ● WSAEINVAL:cmd为非法命令,或者argp所指参数不适用于该cmd命令,或者该命令不适用于此种类型的套接口。
132
● WSAEINPROGRESS:一个阻塞的Windows套接口调用正在运行中。
● WSAENOTSOCK:描述字不是一个套接口。 ● WSAEOPNOTSUPP:指定的ioctl命令无法实现,例如在SIO_SET_QOS或SIO_SET_GROUP_QOS中指定的流描述无法实现。 ● WSA_IO_PENDING:一个重叠操作被成功启动,过后将报告完成情况。 ● WSAEWOULDBLOCK:套接口标志为非阻塞,且所需操作将产生阻塞。
133
4.I/O控制命令 ioctlsocket( )和WSAIoctl( )函数支持的标准I/O命令有FIONBIO、FIONREAD和SIOCATMARK。 (1) FIONBIO: (2) FIONREAD: (3) SIOCATMARK:
134
8.5.3 套接口I/O状态查询——select( )
1.函数格式 select( )函数的格式如下: int select( int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout );
135
2.函数参数说明 select( )函数中各参数的说明如下: ● nfds:传入参数,本参数被忽略,仅起到与Berkeley API套接口兼容的作用。 ● readfds、writefds和exceptfds:这三个参数分别用于检查可读性、可写性和例外数据。这三个参数既是传入参数,也是传出参数。
136
fd_set是一个结构类型说明符,代表着一系列特定套接口的集合,它的定义如下:
struct fd_set { u_int fd_count; /* how many are SET? */ SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */ } fd_set; 其中,各字段的含义如下:fd_count表示套接口的数目;fd_array表示数组中存放的套接口号;FD_SETSIZE是常量,定义为64。
137
● timeout:表示本次select( )调用最长的等待时间。
timeval是一个结构类型,它的定义如下: struct timeval { long tv_sec; /* seconds */ long tv_usec; /* and microseconds */ }; 其中,tv_sec字段和tv_usec字段都表示等待时间,只是tv_sec字段以秒为单位,而tv_usec字段以毫秒为单位。它们的设置可分为如下三种情况:
138
(1) 如果在调用select( )函数时将等待时间tv_sec和tv_usec都设置为0,则select( )调用在检查完套接口描述符后立即返回,这可用于探询所选套接口的状态。如果处于这种状态,则select( )调用可认为是非阻塞的。 (2) 如果在调用select( )函数时将timeout指向NULL,则进行阻塞等待,即被监视的描述符中只有当其中的任何一个准备好读写操作时,select( )调用才返回。 (3) 如果等待时间tv_sec和tv_usec不全为0,则当等待时间没有超时时,select( )函数在被检查的描述符中有任何一个套接口准备好读写时返回。
139
3.函数返回信息 select( )函数调用后,返回处于就绪状态并且已经包含在fd_set结构中的套接口描述符,也就是说,它要修改集合,删除那些不能进行指定操作的套接口。但如果超时则返回0;如果发生错误,则返回SOCKET_ERROR,应用程序可通过WSAGetLastError( )获取如下的错误代码: ● WSANOTINITIALISED:在使用此API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:Windows套接口实现已检测到网络子系统失效。 ● WSAEINVAL:超时时间值非法。
140
●WSAEINTR:通过一个WSACancelBlockingCall( )来取消一个(阻塞的)调用。
● WSAEINPROGRESS:一个阻塞的Windows套接口调用正在运行中。 ● WSAENOTSOCK:描述字集合中包含有非套接口的元素。
141
4.select( )函数使用说明 select( )函数可用于检查一个或多个套接口的状态。对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息。用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select( )返回满足条件的套接口的数目。 为了便于对readfds、writefds和exceptfds集合进行操作,Winsock实现中已经定义好了如下4个宏,以简化程序的设计,程序员可以在程序中直接使用它们对集合进行设置:
142
● FD_SET(s,*set):向set集合添加套接口描述符s。
● FD_CLR(s,*set):从set集合中删除套接口描述字s。 ● FD_ISSET(s,*set):检查s是否为set集合中的一员,如果是则返回“真”(TRUE)。 ● FD_ZERO(*set):将set集合初始化为空集。 这样,使用select( )函数对一个或多个套接口进行检查的过程如下: (1) 使用FD_ZERO宏,初始化要检查的每一个集合。 (2) 使用FD_SET宏,将要检查的套接口加入到一个集合中。
143
(3) 设置等待时间,即对timeval中的tv_sec和tv_usec字段进行设置。
(4) 调用select( )函数。 (5) 当select( )函数正确返回值时,使用FD_ISSET检查一个选定的套接口是否在指定的集合中。 具体在程序中使用select( )函数时,还要注意如下几个问题: (1) readfds参数中包括的套接口标识符有可读入数据的套接口,正处于监听listen( )状态且有连接请求到达的套接口,已经关闭、重设或终止的套接口。
144
(2) writefds参数中包括的套接口标识符有可写的套接口。如果一个套接口正在connect( )连接(非阻塞),可写性意味着连接顺利建立。
(3) exceptfds参数中包括的套接口标识符有带外数据可读。假如已完成了对一个非阻塞连接调用的处理,连接尝试就会失败。 (4) 如果不想对readfds、writefds或exceptfds进行监视,则可将其置为空NULL,但三组参数不能同时全为NULL。
145
5.select( )函数使用举例 下面的程序片段中使用select( )函数来测试一个套接口是否可写。 …… SOCKET s; fd_set fdread; timeval tv; int status; //以下创建一个套接口,并且建立连接 s=socket(……);
146
for( ; ; ) { //初始化要检查的集合 FD_ZERO(&fdread); //将要检查的套接口s加入到集合中 FD_SET(s,& fdread); //设置等待时间 tv.tv_sec=0; tv.tv_usec=6000;//设置等待时间为6秒
147
//调用select( )函数。 status=select(0,&fdread,NULL,NULL,&tv) if((status==SOCKET_ERROR) { //进行错误处理 } else if((status==0) //进行超时处理
148
else { //用FD_ISSET检查套接口s是否在集中 if(FD_ISSET(s, &fdread) //可以从套接口s上读数据 …… }
149
8.5.4 异步事件通知——WSAAsyncSelect( )
WSAAsyncSelect( )是Winsock提供的一个适合于Windows编程使用的函数,它允许在一个套接口上当发生特定的网络事件时,给Windows网络应用程序(窗口或对话框)发送一个消息(事件通知)。 1.函数格式 WSAAsyncSelect( )函数的格式如下: int WSAAsyncSelect( SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent );
150
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,标识一个需要事件通知的套接口描述符。 ● hWnd:传入参数,标识一个在网络事件发生时要想收到消息的窗口或对话框的句柄。 ● wMsg:传入参数,在网络事件发生时要接收的消息,该消息会投递到由hWnd句柄指定的窗口或对话框。 ● lEvent:传入参数,位屏蔽码,用于指明应用程序感兴趣的网络事件集合。常用的网络事件如表8-5所示。
151
表8-5 WSAAsyncSelect( )函数常用的网络事件
152
3.函数返回信息 若应用程序感兴趣的网络事件声明成功,则返回0;如果声明失败,则返回SOCKET_ERROR错误信息。可进一步通过调用WSAGetLastError( )函数返回如下的特定错误代码: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。 ● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。 ● WSAEINVAL:指定的参数之一是非法的。 ● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。
153
附加的错误代码可能在应用程序窗口接收到消息时被设置(见下面该函数的使用说明)。这些代码可以用WSAGETSELECTERROR宏从lParam中取出,对应于每个网络事件的可能错误代码说明如下。
(1) 网络事件FD_CONNECT可能的错误代码为: ● WSAEADDRINUSE:给定的地址已被使用。 ● WSAEADDRNOTAVAIL:指定的地址在本地机器不能使用。 ● WSAEAFNOSUPPORT:指定族的地址不能和本套接口同时使用。 ● WSAECONNREFUSED:连接的尝试被拒绝。
154
● WSAEDESTADDRREQ:需要一个目的地址。
● WSAEFAULT:namelen参数不正确。 ● WSAEINVAL:套接口已经约束到一个地址。 ● WSAEISCONN:套接口已经连接。 ● WSAEMFILE:没有可用的文件描述符。 ● WSAENETUNREACH:此时网络不能从该主机访问。 ● WSAENOBUFS:无可用的缓冲区空间,套接口不能连接。
155
● WSAENOTCONN:套接口没有连接。
● WSAENOTSOCK:该描述符是文件,不是套接口。 ● WSAETIMEDOUT:试图连接超时,未建立连接。 (2) 网络事件FD_CLOSE可能的错误代码为: ● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。 ● WSAECONNRESET:连接由远端重建。 ● WSAECONNABORTED:由于超时或其他失败放弃连接。 (3) 网络事件FD_READ、FD_WRITE、FD_OOB和FD_ACCEPT可能的错误代码为
156
4.函数使用说明 (1) 若应用程序对一个套接口s调用了WSAAsyncSelect( )函数,那么套接口s的模式会自动从阻塞模式变成非阻塞模式,这一点我们在前面已经提到过。 (2) 如果应用程序同时对多个网络事件感兴趣,那么只需对各种类型的网络事件执行按位或(OR)的运算即可。 (3) 特别要注意的是,进行一次WSAAsyncSelect( )调用,将使为同一个套接口启动的所有以前的WSAAsyncSelect( )调用作废。
157
FD_READ和FD_WRITE调用WSAAsyncSelect( ):
rc=WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE); 而不能使用如下的调用方式,因为第二次调用将会使第一次调用的作用失效: rc=WSAAsyncSelect(s, hWnd, wMsg1, FD_READ); rc=WSAAsyncSelect(s, hWnd, wMsg2, FD_WRITE);
158
(4) 如果要取消所有的通知,也就是指出Windows Sockets的实现不再在套接口上发送任何和网络事件相关的消息,则把lEvent字段置为0,然后调用WSAAsyncSelect( )。格式如下: rc=WSAAsyncSelect(s, hWnd, 0, 0); (5) 当某一套接口s上发生了一个已命名的网络事件时,应用程序窗口hWnd会接收到消息wMsg。应用程序窗口例程的wParam参数标识了网络事件发生的套接口。lParam参数的低位字指明了发生的网络事件,高位字则含有一个错误代码,该错误代码可以是Winsock2.h中定义的任何错误。错误代码和事件可以通过WSAGETSELECTERROR和WSAGETSELECTEVENT宏从lParam中取出,宏的定义如下:
159
#define WSAGETSELECTERROR(lParam) HIWORD(lParam)
#define WSAGETSELECTEVENT(lParam) LOWORD(lParam) 若应用程序发现套接口上没有产生任何错误,接着便应检查lParam的低位字,以弄清到底是哪个网络事件类型造成了这条Windows消息的触发。
160
8.5.5 取消正在执行的阻塞调用—WSACancelBlockingCall( )
如果在应用程序中想取消正在执行的阻塞调用,就要使用WSACancelBlockingCall( )函数。要注意的是,在Winsock 2的实现规范中已经不包括该函数。 1.函数格式 WSACancelBlockingCall( )函数的格式为 int WSACancelBlockingCall( void );
161
2.函数返回信息 该函数没有传入参数。如果阻塞操作被成功地取消,则函数返回0;否则返回SOCKET_ERROR错误信息,可通过WSAGetLastError( )获得如下的错误代码: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。 ● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。 ● WSAEINVAL:指定的参数之一是非法的。
162
3.函数使用说明 本函数取消任何本任务中尚未完成的阻塞操作。通常用于以下两种情况: (1) 在一个阻塞调用进行时,应用程序也在处理接收到的消息,在这种情况下,WSAIsBlocking( )返回True。 (2) 一个阻塞调用在进行时,Windows Sockets已经回调了应用程序的“阻塞钩子”函数(如WSASetBlockingHook( ))。
163
8.5.6 判断是否有阻塞调用——WSAIsBlocking( )
1.函数格式 WSAIsBlocking( )函数的格式为 BOOL WSAIsBlocking ( void ); 2.函数返回信息 该函数没有传入参数。如果存在一个尚未完成的阻塞函数在等待完成,则函数返回TRUE,否则函数返回FALSE。
164
3.函数使用说明 在Winsock 2的实现规范中已经不包括该函数。在Winsock 1.1中使用该函数时要注意它禁止对每一线程多于一个未完成的调用。 取消未完成的一个异步操作—WSACancelAsyncRequest( ) WSACancelAsyncRequest( )函数用于取消一个未完成的异步操作。
165
1.函数格式 WSACancelAsyncRequest( )函数的格式如下: int WSACancelAsyncRequest( HANDLE hAsyncTaskHandle ); 2.函数参数说明 此函数中的参数hAsyncTaskHandle是传入参数,指明将要被取消的异步操作。
166
3.函数返回信息 如果该操作成功地取消了异步操作,则函数返回0,否则返回SOCKET_ERROR错误信息。可通过WSAGetLastError( )调用获得对错误的进一步描述,错误代码如下: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。 ● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。 ● WSAEINVAL:指示异步操作句柄非法。 ● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。 ● WSAEALREADY:被取消的异步操作已经完成。
167
4.函数使用说明 WSACancelAsyncRequest( )函数用于取消一次异步操作,该异步操作应该是以一个WSAAsyncGetXByY( )函数的形式(例如WSAAsyncGetHostByName( ))启动的。hAsyncTaskHandle参数标识了要取消的操作,它应由初始函数作为异步任务句柄返回。 试图取消一个已存在的异步操作WSAAsyncGetXByY( )可能失败(错误代码WSAEALREADY),这有两种原因:一是,原来的操作已经完成,并且应用程序已经处理了结果消息;二是原来的操作已经完成,但结果消息仍在应用程序窗口队列中等待。
168
8.6 事件对象I/O管理 8.6.1 创建事件对象——WSACreateEvent( ) 该函数的格式如下:
WSAEVENT WSACreateEvent( void ); 调用该函数不需要传入参数,函数的返回值也很简单。如果调用成功,就是一个创建好的事件对象句柄;如果调用失败,则返回WSA_INVALID_EVENT。应用程序可通过调用WSAGetLastError( )函数获取如下的错误信息:
169
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSA_NOT_ENOUGH_MEMORY:没有足够的内存创建事件对象。 网络事件注册——WSAEventSelect( ) 事件对象创建成功之后,就要将其与某个特定的套接口关联在一起,同时,与使用WSAAsyncSelect( )函数类似,要注册自己感兴趣的网络事件(具体可以使用的网络事件与WSAAsyncSelect( )是一样的)。完成这些工作就要调用WSAEventSelect( )函数。
170
1.函数格式 WSAEventSelect( )函数的格式如下: int WSAEventSelect ( SOCKET s, WSAEVENT hEventObject, long lNetworkEvents );
171
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,一个标识套接口的描述字。 ● hEventObject:传入参数,是一个由WSACreateEvent( )函数创建的事件对象句柄,用于标识与所提供的FD_XXX网络事件集合相关的一个事件对象。 ● lNetworkEvents:传入参数,指定应用程序感兴趣的各种网络事件(FD_XXX)的组合,见表8-5。
172
3.函数返回值说明 如果应用程序指定的网络事件与其相应的事件对象注册成功,则函数返回0。否则,返回INVALID_SOCKET错误,应用程序可以通过WSAGetLastError( )来获取如下的错误代码: ● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:网络子系统失效。 ● WSAEINVAL:参数中有非法值,或者指定的套接口处于非法状态。
173
● WSAEINPROGRESS:一个阻塞的Winsock调用正在进行中,或者服务提供者仍在处理一个回调函数。
● WSAENOTSOCK:描述字不是一个套接口。 4.函数使用说明 该函数用于确定与所提供的FD_XXX网络事件集合相关的一个事件对象,即WSAEventSelect( )函数把一个或多个FD_XXX网络事件与一个事件对象关联在一起。
174
8.6.3 事件对象状态复位——WSAResetEvent( )
为WSAEventSelect( )函数创建的网络事件对象有“signaled”和“nonsignaled”两种工作状态,分别表示“已置信”和“未置信”。如果一个网络事件触发了与一个套接口关联在一起的事件对象,则该对象的工作状态便会从“未置信”转变成“已置信”。因此,在完成了一个I/O请求的处理之后,应用程序需要负责将工作状态从“已置信”更改为“未置信”,完成该功能的函数就是WSAResetEvent( )。
175
1.函数格式 WSAResetEvent( )函数的格式如下: BOOL WSAResetEvent( WSAEVENT hEvent ); 2.函数参数说明 该函数中的参数hEvent为传入参数,用来标识一个开放的事件对象句柄。
176
3.函数返回值说明 如果函数调用成功,则返回TRUE;调用失败,则返回FALSE。可调用WSAGetLast-Error( )函数来获取进一步的错误信息,最常见的错误信息是WSA_INVALID_ HANDLE,它表示hEvent不是一个合法的事件对象句柄。
177
8.6.4 事件对象状态置位——WSASetEvent( )
WSASetEvent( )函数与WSAEventSelect( )函数的功能相反,它将一个事件对象的状态从“nonsignaled”转化为“signaled”。该函数的格式与用法同WSAEventSelect( )函数类似。其格式如下: BOOL WSASetEvent( WSAEVENT hEvent );
178
8.6.5 关闭事件对象——WSACloseEvent( )
1.函数格式 WSACloseEvent( )函数的格式如下: BOOL WSACloseEvent( WSAEVENT hEvent );
179
2.函数参数说明 该函数只有一个参数hEvent,为传入参数,用来标识一个开放的即将关闭的事件对象句柄。 3.函数返回值说明 如果函数顺利完成,返回值为TRUE;如果失败,返回值为FALSE。可用WSAGetLast-Error( )调用获取如下的错误信息: ● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:网络子系统失效。 ● WSA_INVALID_HANDLE:hEvent不是一个合法的事件对象句柄。
180
8.6.6 等待事件对象——WSAWaitForMultipleEvents( )
一个套接口通过调用WSAEventSelect( )函数与一个事件对象句柄关联在一起后,就要等待网络事件触发事件对象句柄的工作状态,然后再进行I/O处理工作。用来等待一个或多个事件对象句柄的函数是WSAWaitForMultipleEvents( )。 1.函数格式 WSAWaitForMultipleEvents( )函数的格式如下:
181
DWORD WSAWaitForMultipleEvents(
DWORD cEvents, const WSAEVENT FAR *lphEvents, BOOL fWaitAll, DWORD dwTimeout, BOOL fAlertable );
182
2.函数参数说明 该函数中各参数的说明如下: ● cEvents:传入参数,指出lphEvents所指数组中事件对象句柄的数目。事件对象句柄的最大值为WSA_MAXIMUM_WAIT_EVENTS,该值被定义为64。因此,针对发出WSAWaitForMultipleEvents( )函数调用的每个线程,该I/O模型一次最多只能支持64个套接口。假如想让这个模型同时管理不止64个套接口,必须创建额外的工作者线程,以便等待更多的事件对象。 ● lphEvents:传入参数,指向一个事件对象句柄数组的指针。
183
● fWaitAll:传入参数,指定如何等待在事件数组中的对象。若该参数为TRUE,则当lphEvents数组中的所有事件对象都进入“signaled”时,函数才返回;但若设置为FALSE,则任何一个事件对象进入“signaled”状态时,函数就会立即返回。 ● dwTimeout:传入参数,指定超时间隔(以毫秒计)。若超时间隔到,不论fWaitAll参数所指定的条件是否满足,函数都会立即返回。如果dwTimeout为0,则函数测试指定的时间对象的状态并立即返回。如果dwTimeout是WSA_INFINITE(永远不超时),则函数的超时间隔永远不会到,只有函数的fWaitAll参数指定的条件满足时才返回。
184
● fAlertable:传入参数,指定当系统将一个输入/输出完成例程放入队列以供执行时,函数是否返回。若为真(TRUE),则函数返回且执行完成例程;若为假(FALSE),则函数不返回,不执行完成例程。请注意,在Win 16中忽略该参数。 3.函数返回值说明 只要指定事件对象中的一个或全部处于有信号状态,或者超时间隔到时函数就返回。如果函数调用成功,返回值指出造成函数返回的事件对象。如果函数失败,返回值为WSA_WAIT_FAILED。可调用WSAGetLastError( )来获取进一步的错误代码信息:
185
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSA_NOT_ENOUGH_MEMORY:无足够内存完成该操作。 ● WSA_INVALID_HANDLE:lphEvents数组中的一个或多个值不是合法的事件对象句柄。 ● WSA_INVALID_PARAMETER:cEvents参数未包含合法的句柄数目。
186
4.函数使用说明 在调用WSAWaitForMultipleEvents( )函数时,如果收到一个事件对象的网络事件通知,便会返回一个值,指出造成函数返回的事件对象。根据该事件对象就可以知道到底是哪个套接口所产生的网络事件,并可进一步判别发生了什么类型的网络事件,这就要使用下面介绍的WSAEnumNetworkEvents( )函数。
187
8.6.7 网络事件查询——WSAEnumNetworkEvents( )
1.函数格式 该函数的格式如下: int WSAEnumNetworkEvents ( SOCKET s, WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents, );
188
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,对应产生网络事件的套接口。 ● hEventObject:传入参数,该参数是可选的,用于标识一个需要复位的事件对象句柄。如果不使用该参数对一个事件对象进行复位,就可以使用前面介绍的WSAResetEvent( )函数。 ● lpNetworkEvents:传出参数,它是一个指针,指向WSANETWORKEVENTS结构。该结构用于接收套接口上发生的网络事件类型以及可能出现的任何错误代码,其定义如下:
189
typedef struct _WSANETWORKEVENTS
{ long lNetworkEvents; int iErrorCodes[FD_MAX_EVENTS]; } WSANETWORKEVENTS, FAR * LPWSANETWORKEVENTS;
190
该结构中lNetworkEvents字段定义发生的FD_XXX网络事件,iErrorCodes参数指定同lNetworkEvents关联在一起的错误代码数组。针对每个网络事件类型,都存在着一个特殊的事件索引。事件索引的名字与事件类型的名字类似,只要在事件名字后面添加一个“_BIT”后缀字符串即可。例如,对FD_READ事件类型来说,iErrorCodes数组的索引标识符便是FD_READ_BIT。 3.函数返回值说明 如果操作成功,则WSAEnumNetworkEvents( )函数返回0;否则将返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError( )来获取相应的错误代码:
191
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSAEINVAL:参数中有非法值。 ● WSAEINPROGRESS:一个阻塞的Winsock调用正在进行中,或者服务提供者仍在处理一个回调函数。 ● WSAENOTSOCK:描述符不是一个套接口。
192
事件对象I/O管理程序实例 下面是一个使用事件对象进行一个或多个套接口I/O管理的服务器程序框架。 …… struct sockaddr_in InternetAddr; SOCKET Scoket[WSA_MAXIMUN_EVENTS]; WSAEVENT NewEvent; WSAEVENT Event[WSA_MAXIMUN_EVENTS]; SOCKET Accept,Listen;
193
DWORD EventTotal=0; DWORD Index; //以下创建一个流式套接口 Listen=socket(AF_INET,SOCK_STREAM,0); InternetAddr.sin_family=AF_INET; InternetAddr.sin_addr.s_addr=htonl(INADDR_ANY); InternetAddr.sin_port=htons(5050); bind(Listen,(PSOCKADDR)&InternetAddr,sizeof(InternetAddr));
194
//创建一个事件对象 NewEvent=WSACreateEvent( ); //在Listen套接口上注册套接口连接和关闭的网络事件 WSAEventSelect(Listen,NewEvent,FD_ACCEPT|FD_CLOSE); Listen(Listen,5); Socket[EventTotal]=Listen; Event[EventTotal]=NewEvent; EventTotal++; While(1)
195
{ //在所有套接口上等待网络事件的发生 Index=WSAWaitForMultipleEvents(EventTotal,EventArray,FALSE,WSA_INFINITE,FALSE); WSAEnumNetworkEvents (SocketArray[Index_WSA_WAIT_EVENT_0], EventArray[Index_WSA_WAIT_EVENT_0], &NetworkEvents); //查检FD_ACCEPT网络事件 if(NetworkEvent.lNetworkEvents&FD_ACCEPT)
196
{ if(NetworkEvents.iErrorCode[FD_ACCEPT_BIT]!=0) printf(''FD_ACCEPT failed with error %d\n'',NetworkEvents.iErrorCode[FD_ACCEPT_BIT]); break; }
197
//接受新的连接,并将其存入套接口数组 Accept=accept(SocketArray[Index_WSA_WAIT_EVENT_0],NULL,NULL); //当套接口的数量超界时,关闭该套接口 if(EventTotal>WSA_MAXIMUM_WAIT_EVENTS) { printf(''Too many connections''); closesocket(Accept); break; }
198
NewEvent=WSACreateEvent( );
WSAEventSelect(Accept,NewEvent,FD_READ|FD_WRITE | FD_CLOSE); Event[EventTotal]=NewEvent; Socket[EventTotal]=Accept; EventTotal++; Prinft(''Scoket %d connected\n'', Accept); } //以下处理FD_READ通知 if(NetworkEvents.lNetworkEvents&FD_READ)
199
{ if(NetworkEvents.iErrorCode[FD_READ_BIT]!=0) prinft(''FD_READ failed with error %d\n'',NetworkEvents.iErrorCode[FD_READ_BIT]); break; } //从套接口读入数据 recv(Socket[index-WSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
200
//以下处理FD_WRITE通知 if(NetworkEvents.lNetworkEvents&FD_WRITE) { if(NetworkEvents.iErrorCode[FD_WIRTE_BIT]!=0) prinft(''FD_WRITE failed with error %d\n'', NetworkEvents.iErrorCode[FD_WRITE_BIT]); break; } //发送数据 send(Socket[indexWSA_WAIT_EVENT_0],buffer,sizeof(buffer),0);
201
//以下处理FD_CLOSE通知 if(NetworkEvents.lNetworkEvents&FD_CLOSE) { if(NetworkEvents.iErrorCode[FD_CLOSE_BIT]!=0) prinft(''FD_CLOSE failed with error %d\n'', NetworkEvents.iErrorCode[FD_CLOSE_BIT]); break; }
202
//关闭套接口 closesocket(Socket[index-WSA_WAIT_EVENT_0]); //从套接口数组和事件数组中删除关闭的套接口的有关信息 CompressArrays(Event,Socket,&EventTotal); } ……
203
8.7 错误处理函数 8.7.1 获得错误操作代码——WSAGetLastError( )
int WSAGetLastError ( void ); 本函数返回最新产生的网络错误,当在使用某一特定的Windows Sockets API函数指出一个错误已经发生时,接着就应调用本函数以获得对应的错误代码。
204
8.7.2 设置错误操作代码——WSASetLastError( )
WSASetLastError( )函数可以设置被WSAGetLastError( )函数接收的错误代码。 1.函数格式 此函数的格式如下: void WSASetLastError ( int iError );
205
2.函数参数说明 该函数的参数iError为传入参数,指明后续的WSAGetLastError( )调用时将要返回的错误代码。 3.函数使用说明 本函数没有返回值。它允许应用程序为当前线程设置错误代码,并可由后来的WSAGetLastError( )调用返回该错误代码。要注意的是,任何由应用程序调用的后续Windows Sockets函数都将覆盖本函数设置的错误代码。
206
8.8 Winsock 2支持的其他函数 8.8.1 共享套接口——WSADuplicateSocket( )
1.函数格式 WSADuplicateSocket( )函数的格式如下: int WSADuplicateSocket( SOCKET s, DWORD dwProcessId, LPWSAPROTOCOL_INFO lpProtocolInfo );
207
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,一个本地套接口描述字。 ● dwProcessId:传入参数,将使用共享套接口的目标进程标识。 ● lpProtocolInfo:传出参数,指向一个足够容纳WSAPROTOCOL_INFO结构信息的缓冲区。
208
3.函数返回值说明 若无错误发生,WSADuplicateSocket( )返回0,否则返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError( )来获取相应的错误代码。错误代码如下: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。 ● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。 ● WSAEINVAL:指定的参数之一是非法的。 ● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。
209
● WSAEMFILE:没有可用的程序描述符。
● WSAENOBUFS:无可用的缓冲区空间。 ● WSAENOTSOCK:该描述符不是套接口。 4.函数使用说明
210
8.8.2 获取传送协议信息——WSAEnumProtocols( )
Winsock 2的一个重要的特征是支持多种协议。如果需要知道一台主机中安装了什么类型的协议,就要使用WSAEnumProtocols( )函数。 1.函数格式 WSAEnumProtocols( )函数的格式如下: int WSAEnumProtocols ( LPINT lpiProtocols, LPWSAPROTOCOL_INFO lpProtocolBuffer, LPDWORD lpdwBufferLength );
211
2.函数参数说明 该函数中各参数的说明如下: ● lpiProtocols:传入参数,一个以NULL结尾的协议标识号数组。本参数可选,如果lpiProtocols为NULL,则返回所有可用协议的信息,否则只返回数组中所列出的协议信息。 ● lpProtocolBuffer:传出参数,一个用PROTOCOL_INFO结构填充的缓冲区。PROTOCOL_INFO结构的定义如下: typedef struct _WSAPROTOCOL_INFOW { DWORD dwServiceFlags1; DWORD dwServiceFlags2;
212
DWORD dwServiceFlags3;
DWORD dwProviderFlags; GUID ProviderId; DWORD dwCatalogEntryId; WSAPROTOCOLCHAIN ProtocolChain; int iVersion; int iAddressFamily;
213
int iMaxSockAddr; int iMinSockAddr; int iSocketType; int iProtocol; int iProtocolMaxOffset; int iNetworkByteOrder; int iSecurityScheme; DWORD dwMessageSize; DWORD dwProviderReserved; WCHAR szProtocol[WSAPROTOCOL_LEN+1]; } WSAPROTOCOL_INFOW, FAR * LPWSAPROTOCOL_INFOW; 其中各字段的含义可参看Winsock 2。
214
● lpdwBufferLength:传入/传出参数,输入时,存有传递给WSAEnumProtocols( )函数的lpProtocolBuffer缓冲区长度;输出时,表示为获取所有信息需传递给WSAEnumProtocols( )函数的缓冲区长度。本函数不能重复调用,传入的缓冲区必须足够大以能存放所有的元素,这个规定降低了该函数的复杂度。由于一个机器上装载的协议数目往往是很小的,所以并不会产生问题。
215
3.函数返回值说明 若无错误发生,WSAEnumProtocols( )返回协议的数目,否则返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError( )来获取相应的错误代码。错误代码如下: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。 ● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。 ● WSAEINVAL:指定的参数之一是非法的。 ● WSAEINPROGRESS:一个阻塞的Windows Sockets操作正在进行。 ● WSAENOBUFS:无可用的缓冲区空间。
216
8.8.3 初始化服务质量——WSAGetQOSByName( )
WSAGetQOSByName( )函数用来根据一个模板初始化服务质量(QOS)。 1.函数格式 WSAGetQOSByName( )函数的格式如下: BOOL WSAGetQOSByName( SOCKET s, LPWSABUF lpQOSName, LPQOS lpQOS );
217
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,一个标识套接口的描述字。 ● lpQOSName:传入参数,指定QOS模板的名字。 ● lpQOS:传出参数,指向待填充QOS结构的指针。 3.函数返回值说明 如果WSAGetQOSByName( )函数调用成功,则返回真(TRUE);如果函数调用失败,则返回假(FALSE),可调用WSAGetLastError( )来获取进一步的错误信息。错误代码如下: ● WSANOTINITIALISED:在使用本API前必须进行一次成功的WSAStartup( )调用。
218
● WSAENETDOWN:Windows Sockets实现已检测到网络子系统故障。
● WSAENOTSOCK:该描述符不是套接口。 ● WSAEFAULT:lpQOS参数不在用户有效的地址空间中,或为lpQOS准备的缓冲区长度太小。 ● WSA_INVAL:描述QOS模板的名称无效。
219
8.8.4 返回重叠操作结果—WSAGetOverlappedResult( )
重叠操作是指让应用程序使用一个重叠的数据结构,一次进行一个或多个Winsock I/O请求。如果想在一个套接口上使用重叠I/O操作,那么在使用WSASocket( )函数创建一个套接口时,最后一个参数必须为WSA_FLAG_OVERLAPPED。 1.函数格式 WSAGetOverlappedResult( )函数的格式如下: BOOL WSAGetOverlappedResult( SOCKET s,
220
LPWSAOVERLAPPED lpOverlapped,
LPDWORD lpcbTransfer, BOOL fWait, LPDWORD lpdwFlags ; 2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,标识套接口,这就是调用重叠操作(WSARecv( )、WSARecvFrom( )、WSASend( )、WSASendTo( )或WSAIoctl( ))时指定的那个套接口。
221
● lpOverlapped:传入参数,指向调用重叠操作时指定的WSAOVERLAPPED结构。
● lpcbTransfer:传出参数,指向一个32位变量,该变量用于存放一个发送或接收操作实际传送的字节数,或WSAIoctl( )传送的字节数。 ● fWait:传入参数,指定函数是否等待挂起的重叠操作结束。若为真(TRUE),则函数在操作完成后才返回。若为假(FALSE)且函数挂起,则函数返回FALSE,WSAGetLastError( )函数返回WSA_IO_INCOMPLETE。 ● lpdwFlags:传出参数,指向一个32位变量,该变量存放完成状态的附加标志位。如果重叠操作为WSARecv( )或WSARecvFrom( ),则本参数包含lpFlags参数所需的结果。
222
3.函数返回值说明 如果该函数调用成功,则返回值为真(TRUE),它意味着重叠操作已经完成,lpcbTransfer所指向的值已经被刷新。如果该函数调用失败,则返回值为假(FALSE),它意味着要么重叠操作未完成,要么由于一个或多个参数的错误导致无法决定完成状态。失败时,lpcbTransfer指向的值不会被刷新。应用程序可用WSAGetLastError( )来获取失败的原因。错误代码如下:
223
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSAENOTSOCK:描述字不是一个套接口。 ● WSA_INVALID_HANDLE: WSAOVERLAPPED结构的hEvent字段不是事件对象的有效句柄。 ● WSA_INVALID_PARAMETER:有错误参数。 ● WSA_IO_INCOMPLETE:fWait参数的值是FALSE,I/O操作还未完成。
224
8.8.5 叶结点加入多点会话——WSAJoinLeaf( )
1.函数格式 WSAJoinLeaf( )函数的格式如下: SOCKET WSAJoinLeaf ( SOCKET s, const struct sockaddr FAR * name,
225
int namelen, LPWSABUF lpCallerData, LPWSABUF lpCalleeData, LPQOS lpSQOS, LPQOS lpGQOS, int iFlags );
226
2.函数参数说明 该函数中各参数的说明如下: ● s:传入参数,标识一个多点套接口的描述字。 ● name:传入参数,标识将与套接口连接的远端名字。 ● namelen:传入参数,名字长度。 ● lpCallerData:传入参数,一个指针,指向多点会话创建时传送给远端的用户数据。 ● lpCalleeData:传出参数, 一个指针,指向多点会话创建时从远端传送回来的用户数据。
227
● lpSQOS:传入参数,一个指向套接口s的流描述的指针,每个方向一个。
● lpGQOS:传入参数,一个指向套接口组(如果存在)流描述的指针。 ● iFlags:传入参数,标志位,用于指定套接口作为发送者、接收者或身兼二者。 3.函数返回值说明
228
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSAEADDRINUSE:指定的地址已经在使用中。 ● WSAEINTR:通过WSACancelBlockingCall( )函数取消(阻塞)调用。 ● WSAEINPROGRESS:一个阻塞的Winsock调用正在进行中,或者服务提供者仍在处理一个回调函数。
229
● WSAEALREADY:在指定的套接口上正在运行一个非阻塞的WSAJoinLeaf( )调用。
● WSAEADDRNOTAVAIL:本地主机无法获得指定的地址。 ● WSAEAFNOSUPPORT:所指定地址族中的地址无法与本套接口一起使用。 ● WSAECONNREFUSED:试图加入请求被强制拒绝。 ● WSAEFAULT:name或namelen参数不是用户地址空间的一个有效部分,namelen参数太小,lpCalleeData、lpSQOS和lpGQOS的缓冲区太小,lpCallerData缓冲区太大。 ● WSAEISCONN:套接口已是多点会话的一个成员。
230
8.8.6 终止套接口上的数据接收—— WSARecvDisconnect( )
1.函数格式 WSARecvDisconnect( )函数的格式如下: int WSARecvDisconnect ( SOCKET s, LPWSABUF lpInboundDisconnectData );
231
2.函数参数说明 该函数中的参数说明如下: ● s:传入参数,一个标识套接口的描述字。 ● lpInboundDisconnectData:传出参数,一个指向终止连接数据的指针。 3.函数返回值说明 若无错误发生,WSARecvDisconnect( )返回0,否则返回SOCKET_ERROR,应用程序可通过调用WSAGetLastError( )函数来获取相应的错误代码。错误代码如下: ● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。 ● WSAENETDOWN:网络子系统失效。
232
● WSAEFAULT:由lpInboundDisconnectData参数提供的缓冲区太小。
● WSAENOPROTOOPT:指定是协议族不支持终止连接数据。 ● WSAEINPROGRESS:一个阻塞的Winsock调用正在进行中,或者服务提供者仍在处理一个回调函数。 ● WSAENOTCONN:套接口没有连接(仅对面向连接的套接口而言)。 ● WSAENOTSOCK:描述字不是一个套接口。
233
8.8.7 终止套接口上的数据发送——WSASendDisconnect( )
1.函数格式 WSASendDisconnect( )函数的格式如下: int WSASendDisconnect ( SOCKET s, LPWSABUF lpOutboundDisconnectData );
234
2.函数参数说明 该函数中的参数说明如下: ● s:一个标识套接口的描述字。 ● lpOutboundDisconnectData:指向发出的终止连接数据的指针。 3.函数返回值说明 若无错误发生,WSASendDisconnect( )返回0,否则返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError( )来获取相应的错误代码:
235
● WSANOTINITIALISED:在调用本API之前应成功调用WSAStartup( )。
● WSAENETDOWN:网络子系统失效。 ● WSAENOPROTOOPT:lpOutboundDisconnectData参数非NULL,复位提供者不支持中止连接数据。 ● WSAEINPROGRESS:一个阻塞的Winsock调用正在进行中,或者服务提供者仍在处理一个回调函数。 ● WSAENOTCONN:套接口未连接(仅适用于面向连接的套接口)。 ● WSAENOTSOCK:描述字不是一个套接口。 ● WSAEFAULT:lpOutboundDisconnectData参数没有全部包含在有效的用户地址空间中。
236
习题 1.Winsock API提供的函数包括哪些类型?各完成什么功能? 2.字节排序函数有哪几个?说明它们的功能和调用方法。
3.说明IP地址转换函数的功能和调用方法。 4.套接口可以设置哪几个级别的选项? 5.编写一个程序,获取本机的IP地址和主机名。
Similar presentations