Winsock API 选项配置

(整期优先)网络出版时间:2019-11-10
/ 4
摘 要 介绍了Winsock 下的网络编程API函数的网络参数以及一些基本的环境配置。同时也可以了解到一些基本的网络特性,给出了网络配置的基本形式,从而更好地对网络通讯进行有效地配置管理。在面对大量烦琐的网络函数与参数的同时,总结出了一些配置它们的方法。

关键词 套接字;Winsock;地址族;协议族;


1 网络编程接口

为了方便网络编程,20世纪90年代初,由Microsoft联合了其他几家公司共同制定了一套Windows下的网络编程接口,即Windows Sockets规范,它不是一种网络协议,而是一套开放的、支持多种协议的Windows下的网络编程接口。现在的Winsock已经基本上实现了与协议无关,可以使用Winsock来调用多种协议的功能,但较常使用的是TCP/IP协议。Socket实际在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有Socket接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个Socket接口来实现。

通讯的基石是套接字,一个套接字是通讯的一端。在这一端上可以找到与其对应的一个地址名。一个正在被使用的套接字都有它的类型和与其相关的进程。套接字存在于通讯域中。通讯域可以是抽象的概念,它是套接字的一套完备的系列,最后可以通过程序来实现。套接字通常和同一个域中的套接字交换数据。Windows Sockets规范支持单一的通讯域,即Internet域。

944563164.jpg

944568447.jpg

套接字可以根据通讯性质分类,程序员可以根据具体的情况进行设置。一般我们仅在同一类的套接字间通讯。可以通过配置底层文件,让不同类型的套接字间也照样可以通讯。

目前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()关闭套接字。

3.1 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()将其置零。

servaddr.sin_family = AF_INET;

表示套接字使用TCP/IP协议族。

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

设置服务器套接字的IP地址为特殊值INADDR_ANY,表示服务器愿意接收来自任何网络设备接口的客户机连接。htonl()函数将主机顺序的字节转换成网络顺序的字节。

servaddr.sin_port = htons(PORT);

设置通信端口号,PORT 即用于通讯的端口号。

3.2 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()只有在套接字连接成功后才会正确的得到分配的地址。

3.3 listen()

int listen(int sockfd, int backlog);

参数sockfd指定要求转换的套接字描述符,参数backlog设置请求队列的最大长度。函数listen()主要完成以下操作:

  首先是将套接字转换成监听套接字。客户机可以通过调用函数connect()来使用这样的套接字主动和服务器建立连接。而listen()可将一个服务端尚未连接的主动套接字转换成为这样的"被动"套接字,也就是监听套接字。在执行了listen()函数之后,服务器的TCP就由CLOSED变成LISTEN状态了。另外listen()可以设置连接请求队列的最大长度。TCP协议为每个倾听套接字实际上维护两个队列,一个是未完成连接队列,这个队列中的成员都是未完成3次握手的连接;另一个是完成连接队列,这个队列中的成员都是虽然已经完成了3次握手,但是它是还未被服务器调用accept()接收的连接。参数backlog指定的是这个倾听套接字完成连接队列的最大长度。

3.4 accept()

int accept(int sockfd, struct sockaddr *addr, int *addrlen);

参数sockfd是转换成功并且得到的倾听套接字;参数addr是一个指向套接字地址结构的指针,参数addrlen为一个整型指针。当函数成功执行时,返回3个结果,函数返回一个新的套接字,即接收套接字,服务器可以通过这个新的套接字描述符和客户机进行通信。参数addr所指向的套接字地址结构中将存放客户机的相关信息,addrlen指针将描述前述套接字地址结构的长度。在通常情况下程序服务端对后面两个参数信息不是很感兴趣,我们可以忽略他们,因此通常我们将accept()函数的后两个参数都设置为NULL。

3.5 connect()

int connect(int sockfd, struct sockaddr *servaddr, int addrlen);

参数sockfd是调用函数socket()返回的套接字描述符,参数servaddr指向远程服务器的套接字地址结构,参数addrlen指定这个套接字地址结构的长度。函数connect()执行成功时返回"0",如果执行失败则返回"-1",并将全局变量errno设置为相应的错误类型。

4 数据传输API

4.1 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,它发生在连接由于网络故障或远程连接系统异常死机而引起的连接中断时。

4.2 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 中断连接API

5.1 shutdown()

int shutdown(

SOCKET s;

int how;

);

how参数可以是下面的任何一个值:SD_RECEIVE、SD_SEND或SD_BOTH。如果是SD_RECEIVE,就表示不允许再调用接收函数。对TCP套接字来说,不管数据在等候接收,还是数据接连到达,都要重设连接,这一点一定要注意。

如果换一种情况,UDP套接字上,仍然可以接受并排列数据。如果选择SE_SEND,表示不允许再调用发送函数。如果指定SD_BOTH,则表示取消连接两端的收发操作。

5.2 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,2000.3

[3]MSDN Library for Visual Studio 2005,MSDN Library for Visual Studio 2005