Winsock API 选项配置
摘 要 介绍了 Winsock 下的 网络 编程 API 函数的网络参数以及一些基本的环境配置。同
时也可以了解到一些基本的网络特性,给出了网络配置的基本形式,从而更好地对网络通
讯进行有效地配置管理。在面对大量烦琐的网络函数与参数的同时, 总结 出了一些配置
它们的 方法 。 关键词 套接字;Winsock;地址族;协议族; 1 网络编程接口 为
了方便网络编程,20 世纪 90 年代初,由 Microsoft 联合了其他几家公司共同制定了一套
Windows 下的网络编程接口,即 Windows Sockets 规范,它不是一种网络协议,而是一
套开放的、支持多种协议的 Windows 下的网络编程接口。现在的 Winsock 已经基本上实
现了与协议无关,可以使用 Winsock 来调用多种协议的功能,但较常使用的是 TCP/IP 协
议。Socket 实际在 计算 机中提供了一个通信端口,可以通过这个端口与任何一个具有
Socket 接口的计算机通信。 应用 程序在网络上传输,接收的信息都通过这个 Socket 接
口来实现。 通讯的基石是套接字,一个套接字是通讯的一端。在这一端上可以找到与其
对应的一个地址名。一个正在被使用的套接字都有它的类型和与其相关的进程。套接字存
在于通讯域中。通讯域可以是抽象的概念,它是套接字的一套完备的系列,最后可以通过
程序来实现。套接字通常和同一个域中的套接字交换数据。Windows Sockets 规范支持单
一的通讯域,即 Internet 域。 套接字可以根据通讯性质分类,程序员可以根据具体的情
况进行设置。一般我们仅在同一类的套接字间通讯。可以通过配置底层文件,让不同类型
的套接字间也照样可以通讯。 目前 socket 提供可以使用两种套接字类型,即流套接字
(TCP)和数据报套接字(UDP)。流套接字提供了双向的,有序的,无重复并且无记录边界的
数据流服务。他在数据传输当中保证数据的完整性和可靠性等。数据报即 UDP 套接字支
持双向的数据流,但并不保证是可靠,有序,无重复。2 用于连接套接字 API Windows
Sockets 规范支持的套接字属性选项都可以在对 setsockopt()函数和 getsockopt()函数的配
置中实现。任何关于 Sockets 实现必须能够识别所有这些属性选项,并且对每一个属性选
项都返回合理的数值。int setsockopt( SOCKET s, int level, int optname, const
char* optval, int optlen); 一般来讲,我们可以手动地对 socket 进行配置,但是在大
多数情况下,socket 的默认配置可以解决绝大多数的网络 问题 ,只有我们需要一些特殊
的用途时才会用到它。3 套接字函数 让套接字工作,其过程如下: 服务器首先启动
,通过调用 socket()建立一个套接字,然后调用 bind()将该套接字和本地网络地址联系在
一起,再调用 listen()使套接字做好侦听的准备,并规定它的请求队列的长度(这个长度通
常有最大的限制),之后就调用 accept()来接收连接.客户在建立套接字后就可调用
connect()和服务器建立连接.一旦建立连接,客户机和服务器之间就可以通过调用 read()
和 write()来发送和接收数据。数据收发完毕之后,调用 close()关闭套接字。
socket() int socket(int domain, int type, int protocol); 参数 domain 指定套接
字使用的协议族,AF_INET 表示使用 TCP/IP 协议族,AF_UNIX 表示使用 Unix 协议族,
AF_ISO 表示套接字使用 ISO 协议族,一般只要使用 TCP/IP 就可以了。type 指定套接字类
型,一般的面向连接通信类型(如 TCP)设置为 SOCK_STREAM,当套接字为数据报类型时
,type 应设置为 SOCK_DGRAM,如果是可以直接访问 IP 协议的原始套接字则 type 应设
置为 SOCK_RAW。参数 protocol 一般设置为"0",表示使用默认协议。当 socket()函数成
功执行时,返回一个对这个套接字的描述符,如果出错则返回"-1",并设置 errno 为相应
的错误类型。 在通常情况下,首先要将描述服务器信息的套接字地址结构清零,这个地
址结构将常用的地址信息储存在一个结构里面以供调用,我们可以直接在地址结构中填入
相应的 内容 ,准备接受客户机送来的连接建立请求。Winsock 定义了一种通用的套接字
地址结构:struct sockaddr{unsigned short sa_family; /* address type */char
sa_data[14]; /* protocol address */} 其中 sa_family 意指套接字使用的协议族地址类
型,对于一般的 TCP/IP 网络,其值应该是 AF_INET,sa_data 是存储地址协议信息的数组
,不同的协议族有不同的地址,但是都可以存在这个通用的结构体里面,通常的做法是将
不同的协议转换成相同的协议地址,然后再进行通讯。用于 TCP/IP 协议族的套接字地址
结构是 sockaddr_in,其定义为:struct sockaddr_in{ short sin_family; unsigned short
sin_port; IN_ADDR sin_addr; char sin_zero[8];}; 其中 sin_zero 成员并未使用,它
是为了和通用套接字地址 struct sockaddr 兼容性而设定的。在编程中,一般都通过
bzero()或是 memset()将其置零。 _family = AF_INET; 表示套接字使用
TCP/IP 协议族。 _addr = htonl(INADDR_ANY); 设置服务器套接
字的 IP 地址为特殊值 INADDR_ANY,表示服务器愿意接收来自任何网络设备接口的客户机
连接。htonl()函数将主机顺序的字节转换成网络顺序的字节。 _port =
htons(PORT); 设置通信端口号,PORT 即用于通讯的端口号。 bind()将一本地地址
与一套接字捆绑。int bind( SOCKET s, const struct sockaddr* name, int namelen);
s:未绑定的套接字。name:套接字地址。sockaddr 结构定义如下:struct
sockaddr{u_short sa_family;char sa_data[14];};namelen:name 名字的长度。 本
函数适用于未连接的数据报或流类套接字,在 listen()调用前使用。当用 socket()创建套接
字后,它便存在于一个名字空间(地址族)中,但并未赋名。bind()函数通过给一个未命名套
接字分配一个本地名字来为套接字建立本地捆绑。 如果端口号置为 0,则 Winsock 将给
应用程序分配一个值在 1024 到 5000 之间的端口号。应用程序可在 bind()后用
getsockname()来获知所分配的地址,但 getsockname()只有在套接字连接成功后才会正确
的得到分配的地址。 listen() int listen(int sockfd, int backlog); 参数 sockfd 指
定要求转换的套接字描述符,参数 backlog 设置请求队列的最大长度。函数 listen()主要完
成以下操作:
首先是将套接字转换成监听套接字。客户机可以通过调用函数 connect()来使用这样
的套接字主动和服务器建立连接。而 listen()可将一个服务端尚未连接的主动套接字转换成
为这样的"被动"套接字,也就是监听套接字。在执行了 listen()函数之后,服务器的 TCP
就由 CLOSED 变成 LISTEN 状态了。另外 listen()可以设置连接请求队列的最大长度。TCP
协议为每个倾听套接字实际上维护两个队列,一个是未完成连接队列,这个队列中的成员
都是未完成 3 次握手的连接;另一个是完成连接队列,这个队列中的成员都是虽然已经完
成了 3 次握手,但是它是还未被服务器调用 accept()接收的连接。参数 backlog 指定的是
这个倾听套接字完成连接队列的最大长度。
accept() int accept(int sockfd, struct sockaddr *addr, int *addrlen); 参数
sockfd 是转换成功并且得到的倾听套接字;参数 addr 是一个指向套接字地址结构的指针
,参数 addrlen 为一个整型指针。当函数成功执行时,返回 3 个结果,函数返回一个新的
套接字,即接收套接字,服务器可以通过这个新的套接字描述符和客户机进行通信。参数
addr 所指向的套接字地址结构中将存放客户机的相关信息,addrlen 指针将描述前述套接
字地址结构的长度。在通常情况下程序服务端对后面两个参数信息不是很感兴趣,我们可
以忽略他们,因此通常我们将 accept()函数的后两个参数都设置为 NULL。
connect() int connect(int sockfd, struct sockaddr *servaddr, int addrlen); 参
数 sockfd 是调用函数 socket()返回的套接字描述符,参数 servaddr 指向远程服务器的套
接字地址结构,参数 addrlen 指定这个套接字地址结构的长度。函数 connect()执行成功时
返回"0",如果执行失败则返回"-1",并将全局变量 errno 设置为相应的错误类型。4 数
据传输 send() int PASCAL FAR send( SOCKET s, const char FAR* buf, int
len, int flags); s:一个用于标识已连接套接字的描述字。 buf:包含待发送数据的
缓冲区。 len:缓冲区中数据的长度。 flags:调用执行方式。 前面已经在 socket
的基础上建立起了一个完整的套接字,现在将在这个套接字上发送数据。第二个参数 buf
,则是字符缓冲区,区内包含即将发送的数据。第三个参数 len,指定即将发送的缓冲区
内的字符数。最后,flags 可为 0、MSG_DONTROUTE 或 MSG_OOB。另外,flags 还可以
是对那些标志进行按位“或运算”的一个结果。MSG_DONTROUTE 标志要求传送层不要将它
发出的包路由出去。由基层的传送决定是否实现这一请求(例如,若传送协议不支持该选
项,这一请求就会被忽略)。MSG_OOB 标志预示数据应该被带外发送。 对返回数据而
言,send 返回发送的字节数,通常可以得到这些数据进行处理。若发生错误,就返回
SOCKET_ ERROR。常见的错误是 WSAECONNABORTED,这一错误一般发生在虚拟回路由
于超时或协议有错而中断的时候。发生这种情况时,应该关闭这个套接字,因为他已经无
效了。远程主机上的 应用 通过执行强行关闭或意外中断操作重新设置虚拟回路时,或远
程主机重新启动时,发生的则是 WSAECONNRESET 错误。发生这一错误时,也应该关闭
这个套接字。最后一个常见错误是 WSAETIMEOUT,它发生在连接由于 网络 故障或远程
连接系统异常死机而引起的连接中断时。 recv()Int recv( SOCKET S; char FAR*
buf; int len; int flags;); 第一个参数 s,是准备接收数据的套接字描述符。第二
个参数 buf,是即将收到数据的字符缓冲,而 len 则是准备接收的字节数或 buf 缓冲的长
度。最后,flags 参数可以是下面的值:0、MSG_PEEK 或 MSG_OOB。0 表示无特殊行为
。MSG_PEEK 会使有用的数据复制到所提供的接收端缓冲内,但是没有从系统缓冲中将它
删除。另外,还返回了待发字节数。消息取数不太好。不仅导致性能下降(因为需要进行
两次系统调用,一次是取数,另一次是无 MSG_PEEK 标志的真正删除数据的调用),在某
些情况下还可能不可靠。返回的数据可能没有反射出真正有用的数量。与此同时,把数据
留在系统缓冲,可容纳接入数据的系统空间就会很少。其结果便是,系统减少各发送端的
TCP 窗口容量。因此就不能获得最大的流通。最好是把所有数据都复制到自己的缓冲中,
并在那里 计算 数据。在待发数据大于所提供的缓冲这一事件中,缓冲内会尽量地填充数
据。这时,recv 调用就会产生 WSAEMSGSIZE 错误。注意,消息长错误是在使用面向消息
的协议时发生的。流协议把接入的数据缓存下来,并尽量地返回应用所要求的数据,即使
待发数据的数量比缓冲大。因此,对流式传输协议来说,就不会碰到 WSAEMSGSIZE 这个
错误。5 中断连接 shutdown()int shutdown( SOCKET s; int how;); how
参数可以是下面的任何一个值:SD_RECEiVE、SD_SEND 或 SD_BOTH。如果是
SD_RECEIVE,就表示不允许再调用接收函数。对 TCP 套接字来说,不管数据在等候接收
,还是数据接连到达,都要重设连接,这一点一定要注意。 如果换一种情况,UDP 套
接字上,仍然可以接受并排列数据。如果选择 SE_SEND,表示不允许再调用发送函数。
如果指定 SD_BOTH,则表示取消连接两端的收发操作。 closesocket()closesocket 函
数用于关闭套接字,它的定义如下:int closesocket( SOCKET s); closesocket 的调用
会释放套接字描述符,并且也释放套接字所占用的空间,再利用套接字执行调用就会失败
,并出现 WSAENOTSOCK 错误。所有与其描述符关联的资源都会被释放,其中包括丢弃
所有等候处理的数据。 在 socket 关于这个套接字连接的线程也将会被终止,待决的重
叠操作也被删除。完成例程或完成端口能执行,但最后会失败,出现
WSA_OPERATION_ABORTED 错误。除此之外,在我们的程序当中应该为每一个完成了的
socket 套接字调用 closesocket 函数,这样,将及时的把 socket 资源交还给系统。6 讨
论 这里主要是对最常用的 socket 函数进行参数的讲解,并且这也是构建一个 socket 基
本体系的最基础部分。 在此基础上,我们可以对网络通讯进行较为复杂的配置,对
winsock 最底层的通讯进行有效的管理。同时也可以根据程序的不同,网络环境的不同进
行网络优化,从而更好的利用 winsock 所提供给我们的 API 函数。 参考 文献 [1]施炜,李
铮,秦颖.Windows Socket 规范以及应用.上海:上海 交通 大学[2]Network
Programming for Microsoft Windows,Anthony Jones,[3]MSDN Library for Visual
Studio 2005,MSDN Library for Visual Studio 2005