最近在看网络编程方面的东西,原来对TCP的状态的认识都不是很清楚,这次仔细地看了看,下面先对TCP的状态和其转换进行简要说明,然后给出FSM对其进行模拟的代码。
首先,看看TCP的状态转移图,如下:
图片来自:
我们规定发起链接的主机叫做S,被动接受链接的主机叫做D,下面简要说明状态转移过程:
首先说说粗线,也就是主机S的状态转移过程,在CLOSED状态下,主机S向目的主机D发起链接,即:向D发送SYN k消息,也就是主机S调用了connect函数,然后进入SYN_SENT状态,此时若等待主机D返回对SYN k的ACK消息超时,则主机重新进入CLOSED状态,若主机S准时收到了主机D返回来的ACK以及自己的同步消息SYN j,主机S先向主机D发送SYN J的确认ACK消息,然后进入ESTABLISHED状态,也就是说主机connect成功。在此状态下,主机S与主机D进行正常的通信。若通信结束,主机S向目的主机发送FIN消息,也就是主机S调用了close函数,然后主机S 进入FIN_WAIT_1状态等待着主机D返回来FIN消息的ACK,当接收到主机D返回来的ACK后,主机S进入FIN_WAIT_2状态,因为通信是双端的,所以主机D也会向主机S发送FIN消息(也就是目的主机D也调用了close函数),这时主机S向目的主机发送确认ACK消息,同时进行入TIME_WAIT状态。
TIME_WAIT状态持续2MSL(MSL最长分节生命期)后,进入CLOSED状态,也就socket正式关闭。之所以在WAIT_2和CLOSED之间加入一个TIME_WAIT状态且维持2MSL,是出于两个目的,1)确保TCP全双工的终止,例如:在FIN_WAIT_2状态时,主机S发完ACK后就关闭啦,而此时这个ACK又发丢失啦,这将导致主机D收不到其FIN的确认ACK而无法关闭。2)确保上一次链接产生的消息 ,在下一次再次连接之前就全部消失,不对下一次链接产生影响。
其次说说虚线,也就是主机D的状态转移过程,主机D处在LISTEN状态时,也就是主机D调用了listen和accept函数,此时主机D收到了主机S发送来了连接请求,也就是SYN K,然后返回给主机S自己的同步消息和S的确认消息SYN J ACK K+1。此时主机D进入SYS_RCVD状态,等待着主机S返回ACK J+1这个ACK,若收到了此确认消息,主机D进入ESTABLISHED状态,若没有收到还会重复发送(上图没有标出若主机D发送完SYN J ACK K+1消息后主机 S宕机了会怎样,猜测会有一个重发机制)。主机D的socket关闭过程与主机S的 关闭过程有些不一样,因为主机D是被迫关闭,此时主机D收到主机S发来的FIN消息,然后祥主机S返回对FIN的ACK,并进入CLOSE_WAIT状态,在此状态下,主机D将自己socket 内的数据处理完毕之后,同样向主机S发送FIN消息也就是调用close函数,此时主机D进入LAST_ACK状态,在收到来自主机S的ACK后进入最终的CLOSED状态。
最后简单说说图中的细线,图中的细线代表主机S和D同时打开和同时关闭时,TCP的状态转变,不过这两种情况是很少发生的。
下面先对FSM坐下简要说明,最后给出相应的模拟代码。
FSM,也就是有限状态机是一种数学上的概念,它是一种协议,用于有限数量的子程序,也就是状态,每一个子程序进行一些处理后进入下一个状态。在C语言中实现FSM的方法有很多,但大多数使用函数指针数组的方式实现,也可以使用显示的分支指令switch实现。下面为了实现上的简便,采用switch来实现 TCP状态 的FSM,如下:
-
enum status{CLOSED,LISTEN,ESTABLISED,SYN_RCVD,SYN_SENT,FIN_WAIT_1,FIN_WAIT_2,TIME_WAIT,CLOSE_WAIT,LAST_ACK};
-
//链接建成后,接收一些数据
-
int receive(void* buff,size_t len){
-
int len = 0;
-
//len为实际接收到的数据
-
//进行一些关于接收到的数据操作
-
return len;
-
}
-
int sent(void* buff,size_t len){
-
int len = 0;
-
//len为实际发送的数据
-
//进行一些关于发送数据的操作
-
return len;
-
}
-
//链接建成后发送一些数据
-
void
-
void status_operator(status s){
-
switch (s){
-
case LISTEN :
-
f_Listen();
-
break;
-
case ESTABLISED:
-
f_established();
-
break;
-
case CLOSED:
-
f_close();
-
break;
-
case SYN_RCVD:
-
f_syn_rcvd();
-
break;
-
case SYN_SENT:
-
f_syn_sent();
-
break;
-
case FIN_WAIT_1:
-
f_fin_wait_1();
-
break;
-
case FIN_WAIT_2:
-
f_fin_wait_2();
-
break;
-
case TIME_WAIT:
-
f_time_wait();
-
break;
-
case CLOSE_WAIT:
-
f_close_wait();
-
break;
-
case LAST_ACK:
-
f_last_ack();
-
break;
-
default:
-
break;
-
}
-
}
-
status f_Listen(void){
-
status rst;
-
//接收来自主机S发来的SYN K,并对其进行操作:K++
-
//向主机D发送确认和自己的同步消息 SYN J
-
rst = SYN_RCVD;
-
status_operator(rst);
-
}
-
status f_established(void){
-
status rst;
-
//此时链接已建立,也就是connect成功
-
//进行一些操作,
-
char* rbuf[1024],*wbuf[1024];
-
receive(rbuf,1024);
-
//一些操作
-
sent(wbuf,1024);
-
//一些操作
-
-
//数据接收或发送完成之后
-
if(/*主机主动关闭链接*/){
-
rst = FIN_WAIT_1;
-
}
-
else
-
rst = CLOS_WAIT;
-
status_operator(rst);
-
}
-
status f_close(void){
-
status rst;
-
//进行一些清理工作
-
}
-
void f_syn_rcvd(void){
-
status rst;
-
//接收主机S发来的SYN K消息,并进行++K操作
-
//设置自己的SYN J消息,并和SYN K的确认消息一起发给主机D
-
rst = ESTABLISHED;
-
status_operator(rst);
-
}
-
void f_syn_sent(void){
-
status rst;
-
//其操作与f_Listen处理一样
-
rst = ESTABLISHED;
-
status_operator(rst);
-
}
-
void f_fin_wait_1(void){
-
status rst;
-
//应用主动关闭并发送 FIN消息
-
rst = FIN_WAIT_2;
-
status_operator(rst);
-
}
-
void f_fin_wait_2(void){
-
status rst;
-
//应用收到对在FIN_WAIT_1状态下发送的FIN的ACK
-
rst = TIME_WAIT;
-
status_operator(rst);
-
}
-
void f_time_wait(void){
-
status rst;
-
//在此状态下等待2MSL
-
rst = CLOSED;
-
status_operator(rst);
-
}
-
void f_close_wait(void){
-
status rst;
-
//此状态下,应用收到FIN并发送ACK
-
//应用进行一些关闭前的操作,比如吧socket中的数据读完
-
//并想主机S发送FIN消息
-
rst = LAST_ACK;
-
status_operator(rst);
-
-
}
-
void f_last_ack(void){
-
status rst;
-
//当收到CLOSE_WAIT发送的FIN的ACK后
-
rst = CLOSED;
-
status_operator(rst);
-
}
参考文献:
UNIX网络编程34~38
C专家编程183~185
此文出处:
http://blog.chinaunix.net/uid-28311809-id-3853883.html
阅读(707) | 评论(0) | 转发(0) |