本章重点讲解WindowsSockets的基本知识。协议特征,IP定址,TCP和UDP Socket应用程序所调用的windows Sockets API,套接字选项。
应用程序至少可以使用两种套接字,即流套接字和数据报套接字。
流套接字:提供双向有序,无重复并且没有记录边界的数据流服务。
数据包套接字:虽然支持双向的数据流,但不保证数据是可靠的有序的和无重复的。也就是说,从数据报套接字接收数据有可能是重复的,或者和数据发出时的顺序不同。
type unsigned int u_int;
type u_int SOCKET;
WindowsSockets中除了INVALID_SOCKET不是一个有效的套接字外,在0~INVALID_SOCKET-1之间的数值都是有效的套接字。也就意味着在判断创建套接字是否有效,则:SOCKET s=socket(_);
if(INVALID_SOCKET == s)
{ //失败}
INVALID_SOCKET的声明:#define INVALID_SOCKET (SOCKET)(~0)
网络服务分面向连接和面向无连接两种。对于面向连接服务,发送数据的源主机必须首先与目的主机连接。这种连接是通过三次握手方式建立起来的。建立连接的过程需要很多开支,花费一定的时间。当源主机与目的主机建立连接后,就可以进行数据传输了,当完成数据传输后释放连接。因为在数据传输过程中,会进行数据的验证等工作,所以大部分面向连接的服务能够保证传输数据的正确性。
在面向无连接服务中,每个数据分组是在网络上传输的独立单元,称作数据报。发送数据的源主机和目的主机之间没有建立连接的过程。源主机将包含目标地址和源地址的数据报之间发送到网络上,该数据报能否正确到达目的主机不得而知。无连接服务因其省去许多数据保证机制,因此通信协议相对简单,通信效率较高。
通常情况下,可靠性、次序性与协议时面向连接还是面向无连接的性质有密切相关。可靠性是指源主机发送的数据都能到达目的主机。不具备可靠性的协议则不能保证数据完整无误的到达目的主机。大多数情况下,面向连接的协议能够保证数据的可靠性。
次序性是指能够对数据到达目的主机的顺序进行处理。具备次序性的协议保证接收到的数据顺序就是源主机发送的数据顺序。
面向无连接的协议一般不能保证数据的完整性和次序性,而面向连接的协议能够做到保证数据的完整性和次序性。
对每个离散命令来说,如果传输协议包每条命令作为独立的消息进行传输,则称该协议是面向消息的。对接收端来说,在接收数据时,每次接收的数据正是对应发送端发送的每条离散消息。
如上图:源主机向目的主机发送64字节和32字节两条消息。目的主机会对应发送两条读取命令,虽然接收数据的缓冲区大小是128个字节,但是这两个读取命令分别返回64字节和32字节数据。目的主机的第一个读取调用,不会将这两个包都接收,这被称为”保护消息边界”,也就是美国数据都被作为一个独立的消息。
无保护消息边界的协议通常被称为“基于流的协议”。流服务的含义是指连续的数据传输。对发送端来说,这意味着允许系统将原始数据分解成小的数据,或者将小的数据累积成一个较大的数据包,然后发送出去。是否将几个数据包积累在一起,受到很多因素影响,例如最大传输单元,Nagle算法等。对于接收端来说,数据一旦到达,网络堆栈就开始读取它,并将数据缓存起来,等待应用程序处理。在应用程序请求最大数据时,系统会在不溢出应用程序缓冲区的前提下,尽可能返回更多的数据。
如上图:源主机向目的主机发送64字节和32字节两条消息。但是,系统堆栈将这些数据聚合成一个大的数据包,发送给目的主机。在接收端,网络堆栈将所有进来的数据包聚合在一起,保存在既定进程中。无关目的主机执行一次128字节缓冲区的读取,则系统立即返回96字节数据,如果目的主机执行一次30字节数据读取,则系统返回30字节数据。
大多数面向连接协议都支持从容关闭。子啊这种关闭过程中,一方即使关闭连接,但对方仍然可以读取网络堆栈中的数据。如果面向连接的协议不支持从容关闭,则只有一方关闭了通信,都会导致连接立即中断和数据丢失,接收端不能读取这些数据。对于TCP协议来说,只有连接双方都执行一次关闭,才能完全中断连接。
在WindowsSockets中,关闭套接字的连接和关闭套接字是不同的。关闭套接字的连接时双方要交换协议消息,按照一定顺序关闭连接。而关闭套接字是指关闭套接字句柄,释放占用资源。
WindowsSockets定义了从容关闭和“硬”关闭两种关闭连接的方式。在从容关闭方式下,先把等待发送的数据发送出去,然后才关闭连接。在“硬”关闭方式下,任何还没有发送的数据都将被丢失。
WindowsSocketsAPI提供了shutdown()和WSASendDisconnect()实现关闭连接的功能。closesocket()实现关闭套接字的功能,同时也隐含执行shutdown()。在应用程序中,虽然可以通过设置套接字选项来控制closesocket()的行为,但是应用程序可以使用下面方法,执行从容关闭,从而保证在关闭连接之前,数据都被接收。
如上图,客户端执行从容关闭的过程:
Windows系统一个有用的特征是能够同步支持多种不同的网络协议。由于WindowsSockets提供了与协议无关的编程接口,开发人员就可以开发直接使用任何一种协议的网络应用程序。尽管如此,要实现网络通信定位和网络连接,仍然必须了解如果为主机定址。每种协议都有一套不同的定址方案。本节重点讲解IP协议的定址方法。
TCP协议和UDP协议通过IP协议传输数据。WindowsSockets通过AF_INET地址家族为IP通信定址。A:address,F:family。AF_INET的声明:#define AF_INET 2
在WindowsSocket中,SOCKADDR_IN结构来指定IP地址和端口号,声明如下
struct sockaddr_in
{
short sin_family;地址家族;必须为AF_INET,以告知WindowsSockets应用程序使用IP地址家族
u_short sin_port;服务端口号;根据第二章介绍的端口的范围划分,应取 1024~49151
struct in_addr sin_addr;in_addr类型的IP地址;sin_addr把一个IP地址保存为一个4字节的数值。
char sin_zero[8];填充该结构的大小,使之与SOCKADDR结构大小相同。
}
针对“大头”(big-endian)和“小头”(little-endian)形式的编号,不同计算机处理器的表示方法不同。在计算机中把IP地址和端口号指定为多字节数字时,这个数就按照“主机字节”(host-byte)顺序表示。但是在网络上指定IP地址和端口号,这个数必须按照”大头”形式来表示,也就是按照从最有意义的字节到最无意义的字节来表示数据,这里称为“网络字节”(network-byte)顺序。
htonl(),htons()实现从主机字节顺序转换为网络字节顺序的功能。
ntonl(),ntohs()实现从网络字节顺序转换为主机字节顺序的功能。
u_long htonl(u_long hostlong);u_short htons(u_short hostshort);
u_long ntohl(u_long netlong); u_short ntohs(u_short netshort);
h:host主机;n:net网络;l:long;s:short;to:转换
开发套接字应用程序时,首先应该进行WindowsSockets的初始化,加载WindowsSockets的实现,然后创建套接字,对TCP套接字来说,需要在socket()或者WSASocket()中指明SOCK_STREAM套接字类型。当服务器和客户端通讯结束时,关闭套接字,释放WindowsSockets的实现。
与面向连接的协议比较,面向无连接协议极为不同,其中一个重要的不同点就是客户端与服务器之间不必建立连接。
创建套接字后,通过套接字选项对其各种属性进行设置,便可对套接字的行为产生影响。有的套接字选项只能用于返回套接字信息;有的选项不仅可以返回套接字信息,而且可以通过设置该选项影响套接字的行为。Getsockopt()返回套接字选项信息;setsockopt()设置套接字选项。
设置SOL_SOCKET选项级别时,调用setsockopt()和getsockopt()所设置或者获取的信息为套接字本身的特征,这些信息与基层协议无关。
SOL_SOCKET选项类型 |
||
选择 |
数据类型 |
说明 |
SO_ACCEPTCONN |
BOOL |
如果为真,表示套接字处于监听模式。SOCK_DGRAM类型的套接字不支持该选项 |
SO_BROADCAST |
BOOL |
如果为真,表明套接字已设置成为广播消息发送 |
SO_DEBUG |
BOOL |
如果为真,则允许输出调试信息 |
SO_DONTLINGER |
BOOL |
如果为真,则禁止SO_LINGER |
SO_DONTROUTE |
BOOL |
如果为真,则不会做出路由选择 |
SO_ERROR |
int |
返回和设置以具体套接字为基础的错误代码 |
SO_KEEPALIVE |
BOOL |
如果为真,则套接字在会话过程中发送”保持活动”消息。“保持活动”消息的发送要以少于2小时为时间间隔 |
SO_LINGER |
Struct linger FAR* |
设置或者获取当前的拖延值 |
SO_OOBINLINE |
BOOL |
如果为真,则带外数据会在普通数据流中返回 |
SO_RCVBUF |
int |
设置或获取接收发送数据的缓冲区长度 |
SO_REUSEADDR |
BOOL |
如果为真,套接字可与一个正在被其他套接字使用的地址绑定在一起 |
SO_SNDBUF |
int |
设置或获取发送数据缓冲区的大小 |
SO_TYPE |
int |
返回套接字的类型,如SOCK_DGRAM,SOCK_STREAM |
SO_SNDTIMEO |
int |
设置或获取套接字在发送数据的超时时间 |
SO_RCVTIMEO |
int |
设置或获取套接字在接收数据的超时时间 |