在另外一边的客户端,我们分析一下TCPClientSock的建立过程。
class TCPClientSock: public BaseSock{ private : sockaddr_in serverSockAddr; protected : char * preBuffer; int preBufferSize; mutable int preReceivedLength; public : TCPClientSock( const char * server_IP, unsigned short server_port, int pre_buffer_size = 32 ); virtual ~ TCPClientSock(); int TCPReceive() const ; int TCPSend( const char * send_data, const int & data_length) const ;}; 我们看到TCPClientSock的类与TCPServerSock很类似,构造函数的差别是,TCPClientSock需要提供server端的IP地址和端口号。
TCPClientSock::TCPClientSock( const char * server_IP, unsigned short server_port, int pre_buffer_size):preBufferSize(pre_buffer_size),preReceivedLength( 0 ){ preBuffer = new char [preBufferSize]; sockFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockFD < 0 ) { sockClass::error_info( " sock() failed. " ); } memset( & serverSockAddr, 0 , sizeof (serverSockAddr)); serverSockAddr.sin_family = AF_INET; serverSockAddr.sin_addr.s_addr = inet_addr(server_IP); serverSockAddr.sin_port = htons(server_port); if (connect(sockFD, ( struct sockaddr * ) & serverSockAddr, sizeof (serverSockAddr)) < 0 ) { sockClass::error_info( " connect() failed. " ); }}TCPClientSock:: ~ TCPClientSock(){ delete [] preBuffer; close(sockFD);} TCPClientSock通过socket()建立起sockFD,然后指定服务器的serverSockAddr,然后通过connect()向serverSockAddr指定的服务器发出握手请求。需要说明的是,调用connect()的时候,系统会检查TCPClientSock的sockFD是否已经绑定了本机的SockAddr,事实上我们也可以通过bind()将本机的IP和指定的端口号绑定在这个sockFD上,但是我们并不关心这个IP地址和端口号(况且很多主机并没有公网IP,特别在中国),所以通常我们不自己去绑定,这样系统就会帮我们完成绑定工作,分配一个空闲的端口号作为本机地址的端口号。
这样TCPClientSock具有来向(本机地址,通常由系统自动完成绑定,也可以指定)和去向(指定的server端地址)的地址信息,所以可以收发信息。于是,TCPClientSock发出的第一个数据报是发给server监听socket的握手请求数据报,TCPListenSock接收这个数据报后,将相关信息传递给TCPServerSock建立新的sockFD,我们上一节讲到,这个新的sockFD建立起来之后马上就向client端返回一个数据报:一方面表示接受第一次握手请求,另外一方面发出第二次握手请求。
收到第二次握手请求后,connect()才会返回,不然就会阻塞,非常“尽力”的去连接server。这个“尽力”的程度跟系统有关,在我的试验中,windows下很快,就几秒;而Debian则接近6分钟!
connect()返回的同时,向server发出了第三次握手的信息,这个信息是对第二次握手请求的认可。所以,第一次和第二次握手包含着连接的请求;而第二次和第三次握手则包含着对握手请求的认可,他们都是在告诉对方:我知道并同意你连接上我了。
至此,TCP三次握手的概念在socket中完整的实现,建立起数据流的TCP通信通道。