7.3.2 客户机API函数
客户机要简单得多,建立成功连接所需的步骤也要少得多。客户机只需三步操作:
1) 用s o c k e t或W S A S o c k e t创建一个套接字。
2) 解析服务器名(以基层协议为准)。
3) 用c o n n e c t或W S A C o n n e c t初始化一个连接。
TCP状态
作为一名Wi n s o c k程序员,通常没必要了解实际的T C P状态。但了解T C P状态,就能更好地理解Winsock API调用如何对基层协议中的改变产生影响。此外,许多程序员在关闭套接字时,会碰到一个常见问题;围绕套接字关闭的T C P状态是我们目前最感兴趣的问题。
对每个套接字来说,它的初始状态都是C L O S E D。若客户机初始化了一个连接,就会向服务器发送一个S Y N包,同时将客户机套接字状态置为S Y N _ S E N T。服务器收到S Y N包后,会发出一个“ S Y N - A C K”包。作为客户机,需要用一个A C K包对它做出反应。此时,客户机的套接字会变成E S TA B L I S H E D状态。如果服务器一直不发送“ S Y N - A C K”包,客户机就会超时,并返回C L O S E D状态。
若一个服务器的套接字同一个本地接口和端口绑定起来,并在它上面进行监听,那么套接字的状态便是L I S T E N。客户机试图与之连接时,服务器就会收到一个S Y N包,并用一个S Y N - A C K包做出响应。服务器套接字的状态就变成S Y N _ R C V D。最后,客户机发出一个A C K包,令服务器套接字的状态变成E S TA B L I S H E D。
一旦应用处于E S TA B L I S H E D状态,可通过两种方法来关闭它。如果由应用程序来关闭,便叫作“主动套接字关闭”;否则,套接字的关闭便是被动的。图7 - 2对两种关闭方法进行了解释。如主动关闭,应用程序便会发出一个F I N包。应用程序调用c l o s e s o c k e t或s h u t d o w n时(把S D _ S E N D当作第二个参数),会向对方发出一个F I N包,而且套接字的状态则变成F I N _ WA I T _ 1。正常情况下,通信对方会回应一个A C K包,我们的套接字的状态
随之变成F I N _ WA I T _ 2。如对方也关闭了连接,便会发出一个F I N包,我们的机器则会响应一个A C K包,并将己方套接字的状态置为T I M E _ WA I T。
T I M E _ WA I T状态也叫作2 M S L等待状态。其中, M S L代表“分段最长生存时间”(Maximum Segment Lifetime),表示一个数据包在丢弃之前,可在网络上存在多长时间。
每个I P包都含有一个“生存时间”(T T L)字段,若它递减为0,包便会被丢弃。一个包经过网络上的每个路由器时, T T L 值都会减1 ,然后继续传递。一旦应用程序进入T I M E _ WA I T状态,那么就会一直持续M S L时间的两倍之久。这样一来, T C P就可以在最后一个A C K丢失的前提下,重新发送它,也就是说, F I N会被重新传送出去。M S L时间两倍之久的等待状态结束之后,套接字便进入C L O S E D状态。
图7-2 TCP套接字的关闭状态
套接字主动关闭
关闭套接字
发送: FIN----->FIN_WAIT_1--接收:ACK-->FIN_WAIT_2----接收: FIN发送: ACK--->TIME_WAIT(2MSL超时)----->CLOSED
套接字主动关闭
关闭套接字
发送: FIN----->接收: FIN发送: ACK---->CLOSING--接收: ACK-->TIME_WAIT(2MSL超时)----->CLOSED
套接字主动关闭
关闭套接字
发送: FIN----->接收: FIN_ACK发送: ACK----->TIME_WAIT(2MSL超时)----->CLOSED
套接字被动关闭
接收: FIN
发送: ACK------>CLOSE_WAIT--关闭套接字发送: FIN-->LAST_ACK------->CLOSED
T I M E _ WA I T状态也叫作2 M S L等待状态。其中, M S L代表“分段最长生存时间”(Maximum Segment Lifetime),表示一个数据包在丢弃之前,可在网络上存在多长时间。
每个I P包都含有一个“生存时间”(T T L)字段,若它递减为0,包便会被丢弃。一个包经过网络上的每个路由器时, T T L 值都会减1 ,然后继续传递。一旦应用程序进入T I M E _ WA I T状态,那么就会一直持续M S L时间的两倍之久。这样一来, T C P就可以在最后一个A C K丢失的前提下,重新发送它,也就是说, F I N会被重新传送出去。M S L时间两倍之久的等待状态结束之后,套接字便进入C L O S E D状态
采取主动关闭措施时,有两个路径会进入T I M E _ WA I T状态。在我们以前的讨论中,
只有一方发出一个F I N,并接收一个A C K响应。然而,另一方仍然可以自由地发送数据,
直到它也被关闭为止。因此,需要两个路径发挥作用。在一个路径中(即同步关闭),一台计算机和它的通信对方会同时要求关闭;计算机向对方送出一个F I N数据包,并从它那里接收一个F I N数据包。随后,计算机会发出一个A C K数据包,对对方的F I N包做出响应,并将自己的套接字置为C L O S I N G状态。计算机从对方那里接收到最后一个A C K包之后,它的套接字状态会变成T I M E _ WA I T。
主动关闭时,另一个路径其实就是同步关闭的变体:套接字从F I N _ WA I T _ 1状态直接变成T I M E _ WA I T。若应用程序发出一个F I N数据包,但几乎同时便从对方那里接收到一个F I N - A C K包,这种情况就会发生。在这种情况下,对方会确认收到应用程序的F I N包,并送出自己的F I N包。对于这个包,应用程序会用一个A C K包做出响应。
T I M E _ WA I T状态的主要作用是在T C P连接处于2 M S L等待状态的时候,规定用于建立那个连接的一对套接字不可被拒绝。这对套接字由本地I P端口以及远程I P端口组成。对某些T C P实施方案来说,它们不允许拒绝处于T I M E _ WA I T状态下的套接字对中的任何端口号。在微软的方案中,不会存在这个问题。然而,若试图通过一对已处于T I M E _ WA I T状态的套接字建立连接,就会失败,并返回W S A E A D D R I N U S E错误。要解决这一问题(除了等待使用那个本地端口来脱离T I M E _ WA I T状态的套接字对),一个办法是使用套接字选
项S O _ R E F U S E A D D R,我们将在第9章对这个选项进行详细讨论。
被动关闭情况下,应用程序会从对方那里接收一个F I N包,并用一个A C K包做出响应。此时,应用程序的套接字会变成C L O S E _ WA I T状态。由于对方已关闭自己的套接字,所以不能再发送数据了。但应用程序却不同,它能一直发送数据,直到对方的套接字已关闭为止。要想关闭对方的连接,应用程序需要发出自己的F I N,令应用程序的套接字状态变成L A S T _ A C K。应用程序从对方收到一个A C K包后,它的套接字就会逆转成C L O S E D状态。
要想了解T C P / I P协议的有关详情,请参阅RFC 793 文件。可在h t t p : / / w w w. r f c -e d i t o r. o rg那里找到这份文件。
c o n n e c t函数和W S A C o n n e c t函数
最后一步就是连接。这是通过调用c o n n e c t函数或W S A C o n n e c t函数来完成的。我们先来看看该函数的Winsock 1版本,其定义如下:
int connect(
SOCKET s,
const struct sockaddr FAR * addr,
int namelen
);
该函数的参数是相当清楚的: s是即将在其上面建立连接的那个有效T C P套接字; n a m e是针对T C P(说明连接的服务器)的套接字地址结构( S O C K A D D R _ I N);n a m e l e n则是名字参数的长度。Winsock 2版本中,它的定义是这样的:
int WSAConnect(
SOCKET s,
const struct sockaddr FAR * addr,
int namelen,
LPWSABUF lpCallerData,
LPWSABUF lpCalleeData,
LPQOS lpSQOS,
LPQOS lpGQOS
);
前三个参数和connect API函数的参数是完全一样的。另外两个参数—l p C a l l e r D a t a和l p C a l l e e D a t a,是字串缓冲区,用于收发请求连接时的数据。l p C a l l e r D a t a参数是指向缓冲区的指针,缓冲区内包含客户机向服务器发出的请求连接的数据。l p C a l l e e D a t a参数则指向另一个缓冲区,区内包含服务器向客户机返回的建立连接时的数据。这两个参数都是W S A B U F结构,
因此,若是l p C a l l e r D a t a,l e n字段应该设为b u f字段中准备传输的数据长度。若是l p C a l l e e D a t a,l e n字段则代表b u f中的缓冲区长度,设为从服务器返回的数据长度。最后两个参数—l p S Q O S和l p G Q O S,表示Q O S结构,该结构对即将建立的连接上收发数据所需要的带宽进行了定义。
l p Q O S参数用于指定套接字s需要的服务质量,而l p G Q O S则用于指定套接字组所需要的服务质量。目前,尚未提供对套接字组的支持。若l p Q O S是空值,则表明没有某应用专用的Q O S。
如果你想连接的计算机没有监听指定端口这一进程, c o n n e c t调用就会失败,并发生错误W S A E C O N N R E F U S E D。另一个错误可能是W S A E T I M E D O U T,这种情况一般发生在试图连接的计算机不能用时(亦可能因为到主机之间的路由上出现硬件故障或主机目前不在网上)。