结合socket详解TCP三次握手四次挥手
结合socket详解TCP三次握手四次挥手
TCP协议中的三次握手和四次挥手大家应该都至少听说过了,本人一直觉得理论学习要结合代码才能学习的更深刻,当知道东西是这样,然后再知道为什么是这样的时候,领悟往往更加深刻,今天本人就结合socket编程中的API来解析一下TCP协议的三次握手和四次挥手过程。
那么TCP协议中的三次握手和四次挥手实际在网络编程中是怎么对应的呢?先贴一个简单的echo服务器端和客户端的代码:
Server:
#include <iostream>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<errno.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<unistd.h>
#define DEFAULT_PORT 8000
using namespace std;
const int buffer_size = 2048;
int main()
{
int listenfd,connfd,n;
struct sockaddr_in servaddr;
char rec_buff[buffer_size];
//char send_buff[buffer_size];
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
{
cout<< " create socket error";
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(DEFAULT_PORT);
if(bind(listenfd,(sockaddr*)&servaddr,sizeof(servaddr)) == -1)
{
cout<<"bind error";
return -1;
}
if(listen(listenfd,5) == -1)
{
cout<< "listen error";
return -1;
}
while(true)
{
if((connfd = accept(listenfd,(sockaddr*)NULL,NULL))== -1)
{
cout<<"accept error";
continue;
}
if(send(connfd,"connect successful",19,0) == -1)
{
cout<< "send error"<<endl;
}
while(true)
{
if((n = recv(connfd,rec_buff,buffer_size,0)) > 0)
{
cout << "receive message is "<<rec_buff<<endl;
if(send(connfd,"receive successful",19,0) == -1)
{
cout<< "send error"<<endl;
}
}
else
{
cout << "recv error"<<endl;
}
rec_buff[n] = '\0';
if(strcmp(rec_buff,"quit") == 0)
{
break;
}
}
close(connfd);
}
close(listenfd);
return 0;
}
Clinet
#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
using namespace std;
int main(int argc,char** argv)
{
int sockfd,n,recv_len;
char rec_buffer[2048],send_buffer[2048];
struct sockaddr_in servaddr;
if(argc != 2)
{
cout<<"input IP address"<<endl;
return -1;
}
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
cout<<"socket error"<<endl;
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8000);
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0)
{
cout<<"inet_pton error"<<endl;
return -1;
}
if(connect(sockfd,(sockaddr*)&servaddr,sizeof(servaddr)) < 0)
{
cout<< "connect error"<<endl;
return -1;
}
while(true)
{
cout<<"send message to server:";
cin >> send_buffer;
if(strcmp(send_buffer,"exit") == 0)
{
break;
}
if(send(sockfd,send_buffer,strlen(send_buffer),0) < 0)
{
cout<<"send error"<<endl;
return -1;
}
if((recv_len = recv(sockfd,rec_buffer,2048,0)) < 0)
{
cout<<"receive error"<<endl;
return -1;
}
cout << rec_buffer <<endl;
}
close(sockfd);
return 0;
}
功能很简单,就是客户端发送字符串,然后服务器端收到打印出来,结合这个简单的模型来看看通信过程中的三次握手和四次挥手
client端主要做了以下几件事:
socket() ->connect() -> send() ->recv() ->close()
sever端主要做了以下几件事:
首先,socket(),bind()这两个函数并不算实际参与到了三次握手和四次挥手的过程中,它们算是通信开始前的预备动作,socket()负责产生一个套接字的描述符,bind()负责将一个本地协议地址赋予一个套接字,同时也将通信端口进行绑定,如果bind绑定的是INADDR_ANY,即表示所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都由这个服务端进程进行处理。一般情况下,如果你要建立网络服务器应用程序,则你要通知服务器操作系统:请在某地址 xxx.xxx.xxx.xxx上的某端口 yyyy上进行侦听,并且把侦听到的数据包发送给我。这个过程,你是通过bind()系统调用完成的。——也就是说,你的程序要绑定服务器的某地址,或者说:把服务器的某地址上的某端口占为已用。服务器操作系统可以给你这个指定的地址,也可以不给你。如果你的服务器有多个网卡(每个网卡上有不同的IP地址),而你的服务(不管是在udp端口上侦听,还是在tcp端口上侦听),出于某种原因:可能是你的服务器操作系统可能随时增减IP地址,也有可能是为了省去确定服务器上有什么网络端口(网卡)的麻烦 —— 可以要在调用bind()的时候,告诉操作系统:“我需要在 yyyy 端口上侦听,所有发送到服务器的这个端口,不管是哪个网卡/哪个IP地址接收到的数据,都是我处理的。”这时候,服务器程序则在0.0.0.0这个地址上进行侦听。
listen()函数则完成主要的三次握手的过程,listen负责将主动连接套接字变为被动连接套接字(当一个socket()创建套接字后,它被假设为一个主动套接字),然后指定内核为相应套接字排队的最大连接个数。然后维护一个半连接队列和一个全连接队列,什么是半连接队列呢?就是只进行了两次握手,还未收到client确认ACK的连接,全连接队列就是3次握手成功的连接的队列,这个连接的总长度是由listen(int sockfd,int backlog)中的backlog来决定,但是这个backlog不好确定,Berkeley的实现为这个backlog增设了一个模糊因子(将其乘以1.5得到队列最大长度),现在的http的服务器一般都将backlog设定一个大值来保证连接都可以得到处理,在《Unix网络编程》中给出了一个利用环境变量LISTENQ来设定backlog值得做法,方式如下
然后就是accept(),accept()也不算参与3次握手的过程中了,accept()的任务就是从listen()的全连接队列里取出第一个就绪的连接,然后返回一个新的connection fd。
如果accept成功,那么其返回值是由内核自动生成的一个全新描述符,代表与所返回client的TCP连接,要注意区分监听套接字(listenfd)和已连接套接字(connfd),一个服务器通常仅创建一个listenfd,它在服务器的生命期内一直存在,内核为每个连接成功的client创建一个connfd,当server完成对某个client的服务时,就会关闭这个connfd。
为什么要3次握手而不是两次呢?在谢希仁的《计算机网络》中是这样说的:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。比如:
“已失效的连接请求报文段”的产生在这样一种情况下:client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。”
这就很明白了,防止了服务器端的一直等待而浪费资源。
参与到四次挥手过程主要就是close()函数了:
那为什么要4次挥手呢?TCP协议是一种面向连接的、可靠的、基于字节流的运输层通信协议。TCP是全双工模式,这就意味着,当主机1发出FIN报文段时,只是表示主机1已经没有数据要发送了,主机1告诉主机2,它的数据已经全部发送完毕了;但是,这个时候主机1还是可以接受来自主机2的数据;当主机2返回ACK报文段时,表示它已经知道主机1没有数据发送了,但是主机2还是可以发送数据到主机1的;当主机2也发送了FIN报文段时,这个时候就表示主机2也没有数据要发送了,就会告诉主机1,我也没有数据要发送了,之后彼此就会愉快的中断这次TCP连接。
上一篇: Flume原理与应用
下一篇: 莴苣烧泥鳅的好处