第五章 WinSock API

Winsock是访问众多基层协议的与协议无关的首选网络编程接口。

第五章 网络原理与协议

 表5-1 实用协议的特性
------------------------------------------------------------------------------------------------------------------------
协议    名称      消息  连接   可靠性 数据包的 从容  广播 多点传 服务 最大消息
       类型  类型      次序性   关闭  支持 输支持 质量 量(字节)
------------------------------------------------------------------------------------------------------------------------
IP     MSAFD TCP     流    连接   有     有       有  无   无     无   无
    MSAFD UDP      消息  无连接 无     无       无  有   有     无   65467
    RSVP TCP      流    连接   有     有       有  无   无     有   无
    RSVP UDP      消息  无连接 无     无       无  有   有     有   65467
------------------------------------------------------------------------------------------------------------------------
IPX/SPX    MSAFD nwlnkipx[IPX]    消息  无连接 无     无       无  有   有     无   576
    MSAFD nwlnkspx[SPX]    消息  连接   有     有       无  无   无     无   无
    MSAFD nwlnkspx[SPX] [伪流] 消息  连接   有     有       无  无   无     无   无
    MSAFD nwlnkspx[SPXII] 消息  连接   有     有       有  无   无     无   无
    MSAFD nwlnkspx[SPXII] [伪流] 消息  连接   有     有       有  无   无     无   无
------------------------------------------------------------------------------------------------------------------------
NetBIOS    连续数据包      消息  连接   有     有       无  无   无     无   64KB(65535)
    数据报      消息  无连接 无     无       无         有① 无     无   64KB(65535)
                                                                         (SP25)
------------------------------------------------------------------------------------------------------------------------
AppleTalk  MSAFD AppleTalk[ADSP] 消息  连接   有     有       有  无    无    无   64KB(65535)    
    MSAFD AppleTalk[ADSP] [伪流] 消息  连接   有     有       有         无    无    无   无
    MSAFD AppleTalk[PAP]  消息  连接   有     有       有         无    无    无   4096
    MSAFD AppleTalk[RTMP] 流    无连接 无     无       无         无    无    无   无
    MSAFD AppleTalk[ZIP]  流    无连接 无     无       无         无    无    无   无
------------------------------------------------------------------------------------------------------------------------
ATM     MSAFD ATM AAL5   流    连接   无     有       无         无    有    有   无
    本地 AT M ( A A L 5 )  消息  连接   无     有       无         无    有    有   无
------------------------------------------------------------------------------------------------------------------------
红外线    MSAFD lrda[lrDA]   流    连接   有     有       有         无    无    无   无
套接字
------------------------------------------------------------------------------------------------------------------------
①NetBIOS支持发送到一个或一组NetBIOS客户机的数据报。但不支持全面广播。


要想获得系统中安装的网络协议的相关信息,可以调用WSAEnumProtocols(),它的定义如下:
int WSAEnumProtocols(
 LPINT lpiProtocols,
 LPWSAPROTOCOL_INFO lpPortocolBuffer,
 LPDWORD lpdwBufferLength
);
该函数返回一个WSAPROTOCOL_INFO结构的数组,WSAPROTOCOL_INFO结构定义如下:
typedef struct _WSAPROTOCOL_INFOW{
 DWORD dwServiceFlags1;
 DWORD dwServiceFlags2;
 DWORD dwServiceFlags3;
 DWORD dwServiceFlags4;
 DWORD dwProviderFlags;
 GUID ProviderId;
 DWORD dwCatalogEntryId;
 WSAPROTOCOLCHAIN ProtocolChain;
 int iVersion;
 int iAddressFamily;
 int iMaxSockAddr;
 int iMinSockAddr;
 int iSocketType;
 int iProtocol;
 int iProtocolMaxOffset;
 int iNetworkByteOrder;
 int iSecuritySchema;
 DWORD dwMessageSize;
 DWORD dwProviderReserved;
 WCHAR szProtocol[WSAPROTOCOL_LEN+1];
} WSAPROTOCOL_INFW, FAR * LPWSAPROTOCOL_INFOW;

在调用一个Winsock函数之前,必须先加载一个版本正确的Winsock库,需要调用WSAStartup(),定义如下:
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);
其中wVersionRequested是要加载的Winsock库版本号,就目前的Win32平台来说,Winsock 2库的最新版本是2.2,(windows CE只支持1.1
版本),如果需要2.2版本,给这个参数赋值0x0202或使用宏MAKEWORD(2,2),高位字节表示副版本号,低位字节表示主版本号。第二个参数
是WSADATA结构,它是在调用后返回的,该结构包含了WSAStartup加载的关于Winsock版本的信息,它的定义如下:
typedef struct WSADATA{
 WORD wVersion;  //调用者希望使用的Winsock版本号
 WORD wHighVersion;  //加载的Winsock库所支持的最高Winsock版本,通常与wVersion相同
 char szDescription[WSADESCRIPTION_LEN+1];  //加载的Winsock库的文本说明
 char szSystemStatus[WSASYS_STATUS_ELN+1];  //一个字串,包含了相应的状态或配置信息
 unsigned short iMaxSockets;  //套接字的最大编号(Winsock2或后面版本忽略该字段)
 unsigned short iMaxUdpDg;  //UDP数据报的最大容量(Winsock2或后面版本忽略该字段)
 char FAR * lpVendorInfo;  //厂商专有的信息(Winsock2或后面版本忽略该字段)
} WSADATA, FAR * LPWSADATA;
在WSADATA结构中唯一有用的信息是wVersion和wHighVersion,其它信息应该从正在使用的特定协议目录条目中获取(调用WSAEnumProtocols)

在不需要调用任何Winsock库函数时,应该调用WSACleanup()来释放相关的资源,该函数定义如下:
int WSACleanup(void);

要调用WSAEnumProtocols函数,最简单的方法是利用相当于NULLl和lpdwBufferLength的lpProtocolBuffer,令初次调用为0。调用失败,返回
WSAENOBUFS错误,但lpdwBufferLength中包含了返回所有协议信息所需的缓冲区长度。一旦分配了恰当的缓冲区长度,便可利用这个缓冲区进
行下一次调用。

WSAPROTOCOL_INFO结构最常用的字段是dwServiceFlags1,它是代表不同的协议属性。表5-2 列出了可在该字段中设置的各种位标志,并对各
属性的含义进行了说明。要查看特定属性,将相应属性标志和dwServiceFlags1字段进行按位和运算。如果结果值非零,这个属性就会出现在
指定协议中;反之,则不会出现。
 表5-2   协议标志
-----------------------------------------------------------------------------------------------------------------------
属 性      含 义
-----------------------------------------------------------------------------------------------------------------------
XPl_CONNECTIONLESS   该协议提供无连接的服务。如果不设,则支持面向连接的数据传输
XPl_GUARANTEED_DELIVERY  该协议保证发送出去的所有数据都将到达既定接收端
XPl_GUARANTEED_ORDER   保证数据按其发送顺序到达接收端,且数据不会重复。但是,该协议不能保证投递的准确性
XPl_MESSAGE_ORIENTED   实现消息边界
XPl_PSEUDO_STREAM   该协议是面向消息的,但接收端会忽略消息边界
XPl_GRACEFUL_CLOSE   支持二相关闭:通知各个通信方另一方打算关闭通信信道。如果不设,则只执行失败关闭
XPl_EXPEDITED_DATA   该协议提供紧急数据(带外数据)
XPl_CONNECT_DATA   该协议支持带有连接请求的数据传输
XPl_DISCONNECT_DATA   该协议支持带有无连接请求的数据传输
XPl_SUPPORT_BROADCAST   该协议支持广播机制
XPl_SUPPORT_MULTIPOINT   该协议支持多点或多播机制
XPl_MULTIPOINT_CONTROL_PLANE  如果设置了这个协议标志,就会启动控制面板。反之,则不启动
XPl_MULTIPOINT_DATA_PLANE  如果设置了这个协议标志,就会启动数据面板。反之,则不启动
XPl_QOS_SUPPORTED   该协议标志支持QoS请求
XPl_UNI_SEND    该协议在发送方向上是单向的
XPl_UNI_RECV    该协议在接收方向上是单向的
XPl_IFS_HANDLES   提供者返回的套接字描述符是Installable File System(可安装文件系统)句柄,可用于ReadFile Wr i t e F i l e之类的A P I函数中
XPl_PARTIAL_MESSAGE  WSASend和WSASendTo中均支持MSG_PARTIAL标志
-----------------------------------------------------------------------------------------------------------------------
iProtocol字段定义该条目属于哪个协议。对支持多种行为的协议来说(比如说面向流的连接或数据报连接)iSocketType字段非常之重要。
最后,iAddressFamily字段用于为指定协议区分正确的定址结构。在为指定协议建立套接字时,这三个条目都很重要。

套接字:
套接字就是一个指向传输提供者的句柄,它的类型是SOCKET。套接字由两个函数建立:
SOCKET WSASocket(
 int af,
 int type,
 int protocol,
 LPWSAPROTOCOL_INOF lpProtocolInfo,
 GROUP g,
 DWORD dwFlags
);
SOCKET socket(int af, int type, int protocol);
说明:第一个参数af是协议的地址家族,比如,如果想建立一个UDP或TCP套接字,可用常量AF_INET来指代互联网协议(IP);第二个参数
type是协议的套接字类型,取值有:SOCK_STREAM、SOCK_DGRAM、SOCK_SEQPACKET、SOCK_RAW和SOCK_RDM;第三个参数是protocol,当指定
的地址家族和套接字类型有多个条目时,就可以用这个参数来限定使用特定传输。下表列出了这三个参数的取值情况:
 套接字参数
---------------------------------------------------------------------------------------------------------------------
协议   地址家族(af) 套接字类型(type)     协议字段(protocol)
---------------------------------------------------------------------------------------------------------------------
Internet Protocol(IP) AF_INET  TCP         SOCK_STREAM IPPROTO_IP
     UDP         SOCK_DGRAM  IPPROTO_UDP
     Raw sockets        SOCK_RAW  IPPROTO_RAW
            IPPROTO_ICMP
---------------------------------------------------------------------------------------------------------------------
IPX/SPX   AF/NS  MSAFD nwlnkipx[IPX]       SOCK_DGRAM  NSPROTO_IPX
   AF_IPX  MSAFD nwlnkspx[SPX]       SOCK_SEQPACKET NSPROTO_SPX
     MSAFD nwlnkspx[SPX][Pseudo-stream]   SOCK_STREAM NSPROTO_SPX
     MSAFD nwlnkspx[SPXII]       SOCK_STREAM NSPROTO_SPXII
     MSAFD nwlnkspx[SPXII][Pseudo-stream] SOCK_STREAM NSPROTO_SPXII
---------------------------------------------------------------------------------------------------------------------
NetBIOS   AF_NETBIOS Sequential Packets       SOCK_SEQPACKET LANA number
     Datagrams        SOCK_DGRAM  LANA number
---------------------------------------------------------------------------------------------------------------------
AppleTalk  AF_APPLETALK MSAFD AppleTalk[ADSP]       SOCK_RDM  ATPROTO_ADSP
     MSAFD AppleTalk[ADSP][Pseudo-stream] SOCK_STREAM ATPROTO_ADSP
     MSAFD AppleTalk[PAP]       SOCK_RDM  ATPROTO_PAP
     MSAFD AppleTalk[RTMP]       SOCK_DGRAM  DDPPROTO_RTMP
     MSAFD AppleTalk[ZIP]       SOCK_DGRAM  DDPPROTO_ZIP
---------------------------------------------------------------------------------------------------------------------
ATM   AF_ATM  MSAFD ATM AAL5        SOCK_RAW  ATMPROTO_AAL5
     Native ATM(AAL5)       SOCK_RAW  ATMPROTO_AAL5
---------------------------------------------------------------------------------------------------------------------
Infrared Sockets AF_IRDA  MSAFD Irda[IrDA]       SOCK_STREAM IRDA_PROTO_SOCK_STREAM
---------------------------------------------------------------------------------------------------------------------
如果protocol参数为0时,则WSAPROTOCOL_INFO结构中的dwProviderFlags条目就是即将使用的默认传输。如果在调用WSASocket前,已经
使用WSAEnumProtocols()列举了所有协议,就可以选定一个WSAPROTOCOL_INFO结构作为WSASocket()的lpProtocolInfo参数值,然后将前
三个参数(af、type和protocol)都设为常量FROM_PROTOCOL_INFO,这样这三个参数的作用就会由WSAPROTOCOL_INFO结构来提供了。

WSASocket()的第5个参数g表示套按字组,由于目前还没有支持套接字组的Winsock版本,所以该参数始终为0;最后一个参数dwFlags可以
指定下面一个或多个标志:WSA_FLAG_OVERLAPPED、WSA_FLAG_MULTIPOINT_C_ROOT、WSA_FLAG_MULTIPOINT_C_LEAF、WSA_FLAG_MULTIPOINT_D_ROOT、
WSA_FLAG_MULITPOINT_D_LEAF。第一个标志WSA_FLAG_OVERLAPPED,用于指定这个套接字具备重叠I/O(是适用于Winsock的可能实现的通信
模式之一)。调用socket建立一个套接字时,WSA_FLAG_OVERLAPPED便是默认设置。一般说来,在使用WSASocket时,最好始终保持设定该标
志。后面四个标志用于处理多播套接字。

注意:开发网络应用时,TCP/IP协议是最安全最好的选择。


第六章 地址家族和名字解析

网际协议(Internet Protocol, IP)是一种无连接的用于互联网的网络协议。
面向连接的通信是通过“传输控制协议”(Transmission Control Protocol, TCP)来完成的。连接一旦建立,两台计算机之间就可以把数据
当作一个双向字节流进行交换。
无连接通信是通过“用户数据报协议”(User Datagram Protocol, UDP)来完成的。
TCP和UDP两者都利用IP来进行数据传输,一般称为TCP/IP和UDP/IP。
Winsock通过AF_INET地址家族为IP通信定址,这个地址家族的定义在Winsock 1.h和Winsock 2.h中。

IP中,计算机都分配有一个IP地址,用一个32位数来表示,正式的称呼是“IPv4地址”。客户机需要通过TCP或UDP和服务器通信时,必须指
定服务器的IP地址和服务端口号。另外,服务器打算监听接入客户机请求时,也必须指定一个IP地址和一个端口号。Winsock中,应用通过
SOCKADDR_IN结构来指定IP地址和服务端口信息,该结构的格式如下:
struct sockaddr_in{
 short sin_family; //必须为AF_INET以告知Winsock要使用IP地址家族。
 u_short sin_port; //服务端口。
 struct in_addr sin_addr;  //IP地址,是一个4字节的无符号长整数类型,采用“互联网标准点分表示法”表示(例如:127.0.0.1)。
 char sin_zero[8]; //只充当填充项的作用,以使SOCKADDR_IN结构和SOCKADDR结构的长度一样。
};

注意:
端口号分为下面这三类:“已知”端口、已注册端口、动态和(或)私用端口。
■ 0 ~ 1023 由IANA(互联网编号分配认证)控制,是为固定服务保留的。
■ 1024 ~ 49151 是IANA列出来的、已注册的端口,供普通用户的进程或程序使用。
■ 49152 ~ 65535 是动态和(或)私用端口,可自由使用。
在使用bind API函数时,如果端口号已经被使用了,系统就会返回Winsock错误WSAEADDRINUSE。

一个有用的、名为inet_addr的支持函数,可把一个点式IP地址转换成一个32位的无符号长整数。它的定义如下:
unsigned long inet_addr(const char FAR *cp);

特殊地址INADDR_ANY允许网络应用监听系统中安装的所有网络接口(例如:系统中有多个IP地址);INADDR_BROADCAST用于在一个IP网络中
广播UDP数据通报。要使用INADDR_BROADCAST,应用程序需要设置套接字选项SO_BROADCAST。

在计算机中把IP地址和端口号指定成多字节数时,这个数就按“主机字节”(host-byte)顺序来表示的,即字节的排序是从最无意义的字
节到最有意义的字节。如果在网络上指定IP地址和端口号,“互联网联网标准”指定多字节值必须用““网络字节”(network-byte)顺序
来表示,即从最有意义的字节到最无意义的字节。有一系列的函数可用于多字节数的转换,下面4个函数用于从主机字节顺序转换成网络字
节顺序:
u_long htonl(u_long hostlong);
int WSAHtonl(SOCKET s, u_long hostlong, u_long FAR * lpnetlong);
u_short htons(u_short hostshort);
int WSAHtons(SOCKET s, u_short hostshort, u_short FAR * lpnetshort);
说明:htonl和WSAHtonl的hostlong参数是按主机字节顺序的一个4字节数。htonl函数返回的数顺序是网络字节顺序,而WSAHtonl函数通过
lpnetlong参数返回的数顺序是网络字节顺序。htons和WSAHtons的hostshort参数是按主机字节顺序的一个2字节数。htons函数把这个数当
作按网络字节顺序的一个2字节值返回,而WSAHtons函数通过lpnetshort参数把这个数返回。

下面4个函数用于从网络字节顺序转换成主机字节顺序:
u_long ntohl(u_long netlong);
int WSANtohl(SOCKET s, u_long netlong, u_long FAR * lphostlong);
u_short ntohs(u_short netshort);
int WSANtohs(SOCKET s, u_short netshort, u_short FAR * lphostshort);

下面是一个例子,描述如何使用inet_addr和htons函数来创建SOCKADDR_IN结构:

SOCKADDR_IN InternetAddr;
INT nPortId=5150;
InternetAddr.sin_family=AF_INET; //use IP address family
InternetAddr.sin_addr.s_addr=inet_addr("136.149.3.29");  //convert the proposed dotted internet address to a 4-byte integer.
InternetAddr.sin_port=htons(nPortId); //nPortId variable is stored in host-byte order, convert it to network-byte order.

创建一个IP套接字的好处是便于应用能够通过TCP、UDP和IP协议进行通信。如要用TCP协议打开一个IP套接字,需调用带有地址家族AF_INET和
套接字类型SOCK_STREAM的socket函数或WSASocket函数,并把协议字段设成0,方式如下:
s=socket(AF_INET, SOCK_STREAM, 0);
s=WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, SWA_FLAG_OVERLAPPED);
如果要用UDP协议打开IP套接字,只需要将上面两个调用中的SOCK_STREAM换成SOCK_DGRAM即可,如果要打开套接字通过IP直接通信,只需要
将上面两个调用中的SOCK_STREAM换成SOCK_RAW即可。

Windows提供了两个函数gethostbyname和WSAAsyncGetHostByName API函数,将主机名解析成IP地址,这两个函数都返回一个hostent结构,
它的定义如下:
struct hostent{
 char FAR * h_name; //主机名
 char FAR * FAR * h_aliases; //由主机备用名组成的字符串数组
 short h_addrtype;  //地址家族
 short h_length;  //h_addr_list数组中每个地址的字节长度
 char FAR * FAR * h_addr_list; //主机的IP地址组成的字符串数组(一个主机可以有多个IP地址)。这个数组中每个IP地址都是按网络字节顺序表示的。
};

gethostbyname的定义如下:
struct hostent FAR * gethostbyname( const char FAR * name);
说明:name是主机名,返回的hostent结构不需要手动释放。

WSAAsyncGetHostByName是gethostbyname的异步版本,它调用成功后,利用Windows消息向应用程序发出通知,定义如下:
HANDLE WSAAsyncGetHostByName(
 HWND hWnd, //要接收到调用成功的消息的窗口句柄。
 unsigned int wMsg, //调用成功后要发送的消息。
 const char FAR * name, //主机名
 char FAR * buf, //指向接收hostent结构的缓冲区,这个缓冲区必须大于hostent结构。
 int buflen //buf的大小,应该设为MAXGETHOSTSTRUCT
);

Windows也提供了两个函数gethostbyaddr和WSAAsyncGetHostByAddr,将IP地址解析成主机名,它们也是返回hostent结构,gethostbyaddr
的定义如下:
struct hostent FAR * gethostbyaddr(
 const char FAR * addr, //是网络字节顺序表示的IP地址
 int len, //addr的长度
 int type //应该设为AF_INET,表示是IP地址
);
SWAAsyncGetHostByAddr是gethostbyaddr的异步版本。

应用打算与运行于本地或远程计算机上的服务进行通信时,除了要知道远程计算机的IP地址外,还必须知道服务的端口号。调用
getservbyname或WSAAsyncGetServByName函数,可以返回一个servent结构,该结构中s_port字段表示了服务端口号。这两个函数
只从名为services的文件中获得静态信息。 Windows 95和Windows 98中,服务文件位于%WINDOWS%下面;在 Windows NT和
Windows  2000中,则位于%WINDOWS%\System32\Drivers\etc下面。getservbyname定义如下:
struct servent FAR * getservbyname(const char FAR * name, const char FAR * proto);
说明:name参数是服务名,例如FTP服务的服务名为"ftp"。proto参数是可选的,表示协议,如果该参数为NULL,则返回符合服务名为
name的servent结构,如果指定协议,则只返回在此协议下注册的服务名为name的servent结构。

Windows 2000中有一个新的注册和请求TCP和UDP服务信息的动态方法:服务器应用可以使用WSASetService函数注册服务名、IP地址和
服务端口号;客户机应用可以使用WSALookupServiceBegin、WSALookupServiceNext和WSALookupServiceEnd的组合来请求服务器注册的
服务信息。

红外线套接字:
红外线套接字称为IrSock,它允许两台计算机通过红外线串行端口进行通信,IrDA(Infrared Data Association,红外线数据联盟)是这样
设计的:无需在整个大型网络上,只需用一种特定的方式浏览范围内的资源,因此,IrDA没有使用标准Winsock命名服务函数和IP定址,相
反,命名服务被并入通信流中,另外还引入一个新的地址家族,以支持与红外线串行端口绑定在一起的服务。IrSock地址结构中包含一个
服务名和一个设备标识符,这里的服务名和设备标识符类似于TCP/IP套接字所用的IP地址和端口号。IrSock地址结构的定义如下:
typedef struct sockaddr_irda{
 u_short irdaAddressFamily; //其值一直都是AF_IRDA。
 u_char irdaDeviceID[4]; //唯一标识运行服务的设备。在建立IrSock服务器时,这个字段是忽略掉的。但对于客户机而言,它指定了要连接的IrDA设备。
 char irdaServiceName[25]; //服务名。
} SOCKADDR_IRDA;

IrSock定址是以“IrDA逻辑服务访问点选择符(LSAP-SEL)”或“信息访问服务”(IAS)为基础。IAS从一个LSAP-SEL中摘出一项服务,并
把它转换成用户友好的文本服务名,方式和互联网域名服务器把主机名映射到数字化IP地址差不多。要成功建立一个连接,既可以用
LSAP-SEL,又可以用文本服务名。不过,文本服务名需要名字解析。大多数时候,都不要使用直接的LSAP-SEL“地址”,因为IrDA服务的地
址空间是限制了的。 Win32实施方案允许LSAP-SEL整数标识符,标识符的范围是1到127。从本质上说,我们可把IAS服务器当作一个WINS服
务器,因为它把LSAP-SEL和一个文本服务名关联在一起。

IAS条目有三个重要字段:类名、属性和属性值。例如:一个服务器想要注册一个名为MyServer的服务,这是通过SOCKADDR_IRDA结构执行
绑定调用时完成的,之后,就会增加一个IAS条目,该条目包括类名MyServer、属性IrDA:TinyTP:Lsap-SEL和属性值3。属性值就是下一个
未用过的LSAP-SEL,是系统根据注册来分配的。另一方面,客户机向连接调用投递一个SOCKADDR_IRDA结构。随后便开始IAS查找,查找带
有类名MyServer和属性IrDA:TinyTP:Lsap-SEL的那项服务。IAS查询会返回3这个值。用户也可以利用getsocketopt调用中的套接字选项
IRLMP_IAS_QUERY来定制自己的IAS查询。

如果打算完全忽略IAS(一般不建议这样做),则可为客户机准备连接的服务名或终端直接指定一个LASP-SEL地址。忽略IAS后,就只能和
不提供任何IAS注册的老IrDA设备(比如红外线打印机)进行通信。把SOCKADDR_IRDA结构中的服务名指定为LSAP-SEL-xxx,就可忽略IAS注
册和查找。其中,xxx处是属性值,其范围在1到127之间。对服务器而言,这样会直接为该服务器分配特定的LSAP-SEL地址(假定这个
LSAP-SEL地址尚未使用)。对客户机而言,这样会忽略IAS查找,并试图马上与运行于指定的LSAP-SEL上的任何一项服务建立连接。

IrSock编程需要使用Af_irda.h头文件。

列举红外线设备的方法是采用getsockopt的IRLMP_ENUMDEVICES命令,DEVICELIST结构被当作optval参数传递。有两个DEVICELIST结构,定
义如下:
typedef struct _WINDOWS_DEVICELIST{
 ULONG numDevice;
 WINDOWS_IRDA_DEVICE_INFO Device[1];
}WINDOWS_DEVICELIST, *PWINDOWS_DEVICELIST, FAR *LPWINDOWS_DEVICELIST;
typedef struct _WCE_DEVICELIST{
 ULONG numDevice;
 WCE_IRDA_DEVICE_INFO Device[1];
}WCE_DEVICELIST, *PWCE_DEVICELIST;
_WINDOWS_DEVICELIST是用在Windows 98和Windows 2000上的,而_WCE_DEVICELIST是用在Windows CE上的,它们的区别在于包含有不同的
IRDA_DEVICE_INFO结构,#define指令会根据目标平台声明正确的DEVICELIST和IRDA_DEVICE_INFO结构。
typedef struct _WINDOWS_IRDA_DEVICE_INFO{
 u_char irdaDeviceID[4];
 char irdaDeviceName[22];
 u_char irdaDeviceHints1;
 u_char irdaDeviceHints2;
 u_char irdaCharSet;
}WINDOWS_IRDA_DEVICE_INFO, *PWINDOWS_IRDA_DEVICE_INFO, FAR *LPWINDOWS_IRDA_DEVICE_INFO;
typedef struct _WCE_IRDA_DEVICE_INFO{
 u_char irdaDeviceID[4];
 char irdaDeviceName[22];
 u_char Reserved[2];
}WCE_IRDA_DEVICE_INFO, *PWCE_IRDA_DEVICE_INFO;

下面的代码可把邻近的所有红外线设备ID列举出来:
///
SOCKET sock;
DEVICELIST devList;
DWORD dwListLen=sizeof(DEVICELIST);
sock=WSASocket(AF_IRDA, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
...
devList.numDevice=0;
dwRet=getsockopt(sock,SOL_IRLMP, IRLMP_ENUMDEVICES, (char *)&devList, *dwListLen);
///
注意:由于红外线设备位置不固定,所以一次查询未必一定可以找得到红外线设备,所以,在实际应用中,可能会多次执行查找,一般是,
在查找不成功之后,调用Sleep进入短期等待,再调用查询代码,在每次调用之前,必须将DEVICELIST结构的numDevice字段设为0,这样就
可以在调用查询代码之后,通过判断该字段的值来确定是否查找到红外线设备了。

创建IrSock服务器的常见步骤如下:
1)建立一个地址家族为AF_IRDA套接字类型为SOCK_STREAM的套接字。
2)用服务器的服务名填写一个SOCKADDR_IRDA结构。
3)利用创建的套接字句柄和SOCKADDR_IRDA结构调用bind()函数。
4)利用创建的套接字句柄和backlog边限调用listen()函数。
5)为接入的客户机锁定一个accept调用。
下面是一个Winsock服务器的例子:
/
#include <stdio.h>
#include "winsock2.h"

void main() {

  //---------------------------------------
  // Declare variables
  WSADATA wsaData;
  SOCKET ListenSocket;
  sockaddr_in service;

  //---------------------------------------
  // Initialize Winsock
  int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
  if( iResult != NO_ERROR )
    printf("Error at WSAStartup\n");

  //---------------------------------------
  // Create a listening socket
  ListenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
  if (ListenSocket == INVALID_SOCKET) {
    printf("Error at socket()\n");
    WSACleanup();
    return;
  }

  //-------------------------------------------------------
  // Bind the socket to the local IP address and port 27015
  hostent* thisHost;
  char* ip;
  u_short port;
  port = 27015;
  thisHost = gethostbyname("");
  ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list); //This function converts an (Ipv4) Internet network address into a string in Internet standard dotted format.

  service.sin_family = AF_INET;
  service.sin_addr.s_addr = inet_addr(ip);
  service.sin_port = htons(port);
 
  if ( bind( ListenSocket,(SOCKADDR*) &service, sizeof(service) )  == SOCKET_ERROR ) {
    printf("bind failed\n");
    closesocket(ListenSocket);
    return;
  }

  //---------------------------------------
  // Initialize variables and call getsockopt.
  // The SO_ACCEPTCONN parameter is a socket option
  // that tells the function to check whether the
  // socket has been put in listening mode or not.
  // The various socket options return different
  // information about the socket. This call should
  // return 0 to the optVal parameter, since the socket
  // is not in listening mode.
  int optVal;
  int optLen = sizeof(int);

  if (getsockopt(ListenSocket,
    SOL_SOCKET,
    SO_ACCEPTCONN,
    (char*)&optVal,
    &optLen) != SOCKET_ERROR)
    printf("SockOpt Value: %ld\n", optVal);

  //---------------------------------------
  // Put the listening socket in listening mode.
  if (listen( ListenSocket, 100 ) == SOCKET_ERROR) {
    printf("error listening\n");
  }

  //---------------------------------------
  // Call getsockopt again to verify that
  // the socket is in listening mode.
  if (getsockopt(ListenSocket,
    SOL_SOCKET,
    SO_ACCEPTCONN,
    (char*)&optVal,
    &optLen) != SOCKET_ERROR)
    printf("SockOpt Value: %ld\n", optVal);

  WSACleanup();
  return;
}

建立IrSock客户机的常见步骤如下:
1)建立一个地址家族为AF_IRDA套接字类型为SOCK_STREAM的套接字。
2)调用有IRLMP_ENUMDEVICES选项的getsockopt函数,列举出所有可用的经外线设备。
3)针对返回的每个设备,利用设备ID和准备连接的服务名填写一个SOCKADDR_IRDA结构。
4)利用套接字句柄和SOCKADDR_IRDA结构,调用connect函数。针对步骤3)中所填写的结构,重复步骤4),直到连接成功。
下面是一个客户机例子:
/
#define DEVICE_LIST_LEN    10

SOCKADDR_IRDA     DestSockAddr = { AF_IRDA, 0, 0, 0, 0, "SampleIrDAService" };
unsigned char    DevListBuff[sizeof(DEVICELIST) &#8211;
                 sizeof(IRDA_DEVICE_INFO) +
                 (sizeof(IRDA_DEVICE_INFO) * DEVICE_LIST_LEN)];
int              DevListLen    = sizeof(DevListBuff);
PDEVICELIST      pDevList      = (PDEVICELIST) &DevListBuff;

pDevList->numDevice = 0;

// Sock is not in connected state
if (getsockopt(Sock, SOL_IRLMP, IRLMP_ENUMDEVICES,
               (char *) pDevList, &DevListLen) == SOCKET_ERROR)
{
        // WSAGetLastError
}

if (pDevList->numDevice == 0)
{
        // no devices discovered or cached
        // not a bad idea to run a couple of times
}
else
{
       // one per discovered device
       for (i = 0; i < (int) pDevList->numDevice; i++)
       {
            // typedef struct _IRDA_DEVICE_INFO
            // {
            //     u_char    irdaDeviceID[4];
            //     char      irdaDeviceName[22];
            //     u_char    irdaDeviceHints1;
            //     u_char    irdaDeviceHints2;
            //     u_char    irdaCharSet;
            // } _IRDA_DEVICE_INFO;

            // pDevList->Device[i]. see _IRDA_DEVICE_INFO for fields
            // display the device names and let the user select one
       }
}

// assume the user selected the first device [0]
memcpy(&DestSockAddr.irdaDeviceID[0], &pDevList->Device[0].irdaDeviceID[0], 4);

if (connect(Sock, (const struct sockaddr *) &DestSockAddr,
            sizeof(SOCKADDR_IRDA)) == SOCKET_ERROR)
{
       // WSAGetLastError
}
/

要知道特定服务是否在特定的设备上运行,有两种方法。第一种是真正与特定服务连接;另一种是向I A S查询特定的服务名。两种方法
都要求列举红外线设备,然后对每一个设备进行查询直到达到目的或所有的设备都查完。执行查找是调用带有IRLMP_IAS_QUERY选项的
getsockopt函数来完成的。IAS_QUERY结构定义如下:
typedef struct _WINDOWS_IAS_QUERY{
 u_char irdaDeviceID[4];
 char irdaClassName[IAS_MAX_CLASSNAME];
 char irdaAttribName[IAS_MAX_ATTRIBNAME];
 u_long irdaAttribType;
 union{
  LONG irdaAttribInt;
  struct{
   u_long Len;
   u_char OctetSeq[IAS_MAX_OCTET_STRING];
  }irdaAttribOctetSeq;
  struct{
   u_long Len;
   u_long CharSet;
   u_char UsrStr[IAS_MAX_USER_STRING];
  }irdaAttribUsrStr;
 }irdaSttribute;
}WINDOWS_IAS_QUERY, *PWINDOWS_IAS_QUERY, FAR *LPWINDOWS_IAS_QUERY;
typedef struct _WCE_IAS_QUERY{
 u_char irdaDeviceID[4];
 char irdaClassName[61];
 char irdaAttribName[61];
 u_short irdaAttibType;
 union{
  int irdaAttibInt;
  struct{
   int Len;
   u_char OctetSeq[1];
   u_char Reserved[3];
  }irdaAttribOctetSeq;
  struct{
   int Len;
   u_char CharSet;
   u_char UsrStr[1];
   u_char Reserved[2];
  }irdaAttribUsrStr;
 }irdaAttribute;
}WCE_IAS_QUERY, &PWCE_IAS_QUERY;
要对特定服务的LSAP-SEL数有多少进行查询,很简单:把irdaClassName字段设为LSAP-SEL的属性字串,即IrDA:IrLMP:LsapSel,然后,
把irdaAttributeName字段设成准备查询的那个服务名。除此以外,还必须用范围内的有效设备来设置irdaDeviceID字段。

创建套接字:
s=socket(AF_IRDA, SOCK_STREAM, 0);
s=WSASocket(AF_IRDA, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
注意:对IrDA来说,大多数SO_socket选项都是没有意义的,只有SO_LINGER得到了特别的支持。IrSock特有的套接字选项当然也得到了支持,
不过只限于地址家族AF_IRDA套接字上。


“互联网包交换”(IPX)协议提供两个进程间的无连接通信,如果应用程序需要数据投递保证,但仍坚持使用IPX,它就会选用一个比IPX高
级的协议,比如“顺序分组交换”(SPX)和SPX II协议,这两个协议中, SPX包通过IPX发送。Winsock为应用程序提供了在Windows平台上
通过IPX进行通信的能力(Windows CE除外)。

要用IPX进行Winsock客户机或服务器通信,必须建立SOCKADDR_IPX结构。该结构在Wsipx.h头文件中定义,应用程序在包括Winsock2.h文件之
后还必须包括该文件。SOCKADDR_IPX结构如下定义:
typedef struct sockaddr_ipx{
 short sa_family; //必须为AF_IPX
 char sa_netnum[4]; //IPX网络上4字节的网段号
 char sa_nodenum[6]; //6字节的节点(计算机)物理地址。
 unsigned short sa_socket; //一个节点区分IPX通信的套接字或接口
}SOCKADDR_IPX, *PSOCKADDR_IPX, FAR *LPSOCKADDR_IPX;

创建IPX套接字:
调用socket或WSASocket时,传递AF_IPX地址家族、套接字类型和NSPROTO_IPX协议,例如:
s=socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX);
s=WSASocket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX, NULL, 0, WSA_FLAG_OVERLAPPED);
IPX利用数据报提供不可靠的无连接通信,如果一个应用需要可靠的无连接通信,可采用比IPX高级的协议,比如SPX和SPX II。要做到这一
点,把socket和WSASocket调用的类型和协议字段分别设置成套接字类型SOCK_SEQPACKET或SOCK_STREAM和协议字段NSPROTO_SPX或
NSPROTO_SPX II即可。

如果指定了SOCK_STREAM,系统就会把数据当作连续不断的字节流发送出去,没有消息边界,这类似于TCP/IP中套接字的行为。另一方面,
如果发送端发出2000个字节,在这2000个字节全部到达接收端之前,接收端是不会返回任何消息的。对SPX和SPX II来说,这是通过设置
SPX头中的消息结束位来完成的。指定SOCK_STREAM时,要注意这个位,Winsock recv和WSARecv调用在其收到这个位之前不会中止。如果
指定SOCK_STREAM时没有注意到这个消息结束位,一旦接收端收到数据,recv就会中止,不管消息结束位设在哪里。从发送端这一方来说
(使用SOCK_SEQPACKET类型),如果发送的数据包小于一个完整的数据包,消息结束位就会随这个包一起发送。如果发送的包大于一个完
整的数据包,消息结束位就只设在最后发送的那个包中,而不是每个包都有。

006 P12

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值