zoukankan      html  css  js  c++  java
  • TCP/IP详解V2(六)之TCP协议

    TCP输入

    tcp_input

    void
    tcp_input(m, iphlen)
    	register struct mbuf *m;
    	int iphlen;
    {
    	register struct tcpiphdr *ti;
    	register struct inpcb *inp;
    	caddr_t optp = NULL;
    	int optlen;
    	int len, tlen, off;
    	register struct tcpcb *tp = 0;
    	register int tiflags;
    	struct socket *so;
    	int todrop, acked, ourfinisacked, needoutput = 0;
    	short ostate;
    	struct in_addr laddr;
    	int dropsocket = 0;
    	int iss = 0;
    	u_long tiwin, ts_val, ts_ecr;
    	int ts_present = 0;
    
    	tcpstat.tcps_rcvtotal++;        //记录全局变量
    	ti = mtod(m, struct tcpiphdr *);        //将mbuf中的信息转化为TCP/IP Header
    	if (iphlen > sizeof (struct ip))        //如果存在IP选型,丢弃IP选项
    		ip_stripoptions(m, (struct mbuf *)0);
    	if (m->m_len < sizeof (struct tcpiphdr)) {        //如果首部mbuf中数据小于40字节,将外部簇中的数据调整到首部mbuf中
    		if ((m = m_pullup(m, sizeof (struct tcpiphdr))) == 0) {
    			tcpstat.tcps_rcvshort++;
    			return;
    		}
    		ti = mtod(m, struct tcpiphdr *);        //将mbuf中的数据转化为ti
    	}
    
    	tlen = ((struct ip *)ti)->ip_len;        //除去IP首部外的数据
    	len = sizeof (struct ip) + tlen;    //算上IP首部的数据
    	ti->ti_next = ti->ti_prev = 0;        //已经将数据报提交给了协议层,所以TCP头部的信息基本上没用了
    	ti->ti_x1 = 0;
    	ti->ti_len = (u_short)tlen;
    	HTONS(ti->ti_len);
    	if (ti->ti_sum = in_cksum(m, len)) {        //计算校验和
    		tcpstat.tcps_rcvbadsum++;
    		goto drop;
    	}
    #endif /* TUBA_INCLUDE */
    
    	off = ti->ti_off << 2;        //获取TCP首部的长度
    	if (off < sizeof (struct tcphdr) || off > tlen) {
    		tcpstat.tcps_rcvbadoff++;
    		goto drop;
    	}
    	tlen -= off;        //减去TCP首部的信息,获取数据长度
    	ti->ti_len = tlen;
    	if (off > sizeof (struct tcphdr)) {        //如果TCP首部长度 > 20字节,意味着存在TCP选项
    		if (m->m_len < sizeof(struct ip) + off) {        //如果首部mbuf中的长度小于TCP首部长度+TCP首部选项长度+IP首部长度
    			if ((m = m_pullup(m, sizeof (struct ip) + off)) == 0) {        //调用函数将数据完整的copy进mbuf中
    				tcpstat.tcps_rcvshort++;
    				return;
    			}
    			ti = mtod(m, struct tcpiphdr *);        
    		}
    		optlen = off - sizeof (struct tcphdr);        //获取TCP选项长度
    		optp = mtod(m, caddr_t) + sizeof (struct tcpiphdr);        //optp是指向第一个选项的指针
    		
    		if ((optlen == TCPOLEN_TSTAMP_APPA ||
    		     (optlen > TCPOLEN_TSTAMP_APPA &&
    			optp[TCPOLEN_TSTAMP_APPA] == TCPOPT_EOL)) &&
    		     *(u_long *)optp == htonl(TCPOPT_TSTAMP_HDR) &&
    		     (ti->ti_flags & TH_SYN) == 0) {        //快速的处理时间戳选项
    			ts_present = 1;
    			ts_val = ntohl(*(u_long *)(optp + 4));
    			ts_ecr = ntohl(*(u_long *)(optp + 8));
    			optp = NULL;	/* we've parsed the options */
    		}
    	}
    	tiflags = ti->ti_flags;
    
    	/*
    	 * Convert TCP protocol specific fields to host format.
    	 */
    	NTOHL(ti->ti_seq);        //将seq,ack,win以及urp转换为主机字节序
    	NTOHL(ti->ti_ack);
    	NTOHS(ti->ti_win);
    	NTOHS(ti->ti_urp);
    
    	/*
    	 * Locate pcb for segment.
    	 */
    findpcb:
    	inp = tcp_last_inpcb;        //获取上一次的Internet PCB
    	if (inp->inp_lport != ti->ti_dport ||
    	    inp->inp_fport != ti->ti_sport ||
    	    inp->inp_faddr.s_addr != ti->ti_src.s_addr ||
    	    inp->inp_laddr.s_addr != ti->ti_dst.s_addr) {        //寻找合适的四元组
    		inp = in_pcblookup(&tcb, ti->ti_src, ti->ti_sport,
    		    ti->ti_dst, ti->ti_dport, INPLOOKUP_WILDCARD);
    		if (inp)        //找到的话,调整last PCB
    			tcp_last_inpcb = inp;
    		++tcpstat.tcps_pcbcachemiss;
    	}
    
    	if (inp == 0)        //如果没有找到话,丢弃报文段,并发送RST
    		goto dropwithreset;
    	tp = intotcpcb(inp);        //获取TCP PCB
    	if (tp == 0)        //如果没有找到TCP PCB,说明插口已经关闭,丢弃数据报并发送RST
    		goto dropwithreset;
    	if (tp->t_state == TCPS_CLOSED)        //如果目前的状态为CLOSED,直接丢弃数据报。
    		goto drop;
    	
    	if ((tiflags & TH_SYN) == 0)        //如果置位了窗口大小改变选项,调整窗口大小,只有在SYN报文段中才能支持窗口大小改变选项
    		tiwin = ti->ti_win << tp->snd_scale;
    	else
    		tiwin = ti->ti_win;
    
    	so = inp->inp_socket;        //获取插口
    	if (so->so_options & (SO_DEBUG|SO_ACCEPTCONN)) {        //如果插口正处于调试状态,保存TCP与IP的首部
    		if (so->so_options & SO_DEBUG) {
    			ostate = tp->t_state;
    			tcp_saveti = *ti;
    		}
    		if (so->so_options & SO_ACCEPTCONN) {        //如果目前处于ACCEPTION状态,调用函数分配新的插口,Internet PCB和TCP PCB
    			so = sonewconn(so, 0);
    			if (so == 0)
    				goto drop;
    
    			dropsocket++;
    			inp = (struct inpcb *)so->so_pcb;        //填充本地地址与端口
    			inp->inp_laddr = ti->ti_dst;
    			inp->inp_lport = ti->ti_dport;
    #if BSD>=43
    			inp->inp_options = ip_srcroute();        //获取数据报源路由选项
    #endif
    			tp = intotcpcb(inp);
    			tp->t_state = TCPS_LISTEN;        //将新创建的SOCKET状态调整为LISTEN
    
    			while (tp->request_r_scale < TCP_MAX_WINSHIFT &&
    			   TCP_MAXWIN << tp->request_r_scale < so->so_rcv.sb_hiwat)        //计算窗口的缩放因子
    				tp->request_r_scale++;
    		}
    	}
    
    	tp->t_idle = 0;        //将空闲时间置0
    	tp->t_timer[TCPT_KEEP] = tcp_keepidle;        //重新设置Keep-Alive选项
    
    	if (optp && tp->t_state != TCPS_LISTEN)        //如果存在选项,并且当前的状态不处于LISTEN状态,处理TCP选项
    		tcp_dooptions(tp, optp, optlen, ti,
    			&ts_present, &ts_val, &ts_ecr);
    
    	if (tp->t_state == TCPS_ESTABLISHED &&
    	    (tiflags & (TH_SYN|TH_FIN|TH_RST|TH_URG|TH_ACK)) == TH_ACK &&
    	    (!ts_present || TSTMP_GEQ(ts_val, tp->ts_recent)) &&
    	    ti->ti_seq == tp->rcv_nxt &&
    	    tiwin && tiwin == tp->snd_wnd &&
    	    tp->snd_nxt == tp->snd_max) {        //如果处于连接状态,没有设置特殊的选项,携带的时间戳选项必须大于以前收到的值,收到的seq=期望的seq,通告窗口存在并等于发送时的窗口,下一个发送的报文段不是重传报文段
    		if (ts_present && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent) &&
    		   SEQ_LT(tp->last_ack_sent, ti->ti_seq + ti->ti_len)) {        //如果存在时间戳选项,根据时间戳选项更新TCP PCB中的信息
    			tp->ts_recent_age = tcp_now;
    			tp->ts_recent = ts_val;
    		}
    
    		if (ti->ti_len == 0) {        //处理ACK
    			if (SEQ_GT(ti->ti_ack, tp->snd_una) &&
    			    SEQ_LEQ(ti->ti_ack, tp->snd_max) &&
    			    tp->snd_cwnd >= tp->snd_wnd) {        //如果确认的seq > 未确认的seq,确认的ack < 最大序号,拥塞窗口大于目前的窗口,即窗口完全打开,不处于慢启动和拥塞避免状态
    				++tcpstat.tcps_predack;        //更新全局变量
    				if (ts_present)    //如果存在事件戳选项,更新RTT值
    					tcp_xmit_timer(tp, tcp_now-ts_ecr+1);        
    				else if (tp->t_rtt &&
    					    SEQ_GT(ti->ti_ack, tp->t_rtseq))
    					tcp_xmit_timer(tp, tp->t_rtt);
    				acked = ti->ti_ack - tp->snd_una;        //计算确认的字节数
    				tcpstat.tcps_rcvackpack++;        //修改全局变量
    				tcpstat.tcps_rcvackbyte += acked;
    				sbdrop(&so->so_snd, acked);        //从发送缓存中丢弃已经确认的数据
    				tp->snd_una = ti->ti_ack;        //更新未确认的数值为刚收到的ACK
    				m_freem(m);    //释放mbuf
    
    				if (tp->snd_una == tp->snd_max)        //如果确定了所有的数据,关闭重传定时器
    					tp->t_timer[TCPT_REXMT] = 0;
    				else if (tp->t_timer[TCPT_PERSIST] == 0)        //如果持续定时器没有设定,启动重传定时器
    					tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;
    
    				if (so->so_snd.sb_flags & SB_NOTIFY)        //唤醒等待发送数据的进程
    					sowwakeup(so);
    				if (so->so_snd.sb_cc)        //如果发送缓存中依旧存在数据,发送数据,然后返回
    					(void) tcp_output(tp);
    				return;
    			}
    		} else if (ti->ti_ack == tp->snd_una &&
    		    tp->seg_next == (struct tcpiphdr *)tp &&
    		    ti->ti_len <= sbspace(&so->so_rcv)) {        //如果收到的报文端的数据>0,ACK是没有被确认的数据,不存在乱序的报文段,并且接收缓存可以容纳所有的数据
    			++tcpstat.tcps_preddat;        //更新全局计数器以及接收缓存
    			tp->rcv_nxt += ti->ti_len;
    			tcpstat.tcps_rcvpack++;
    			tcpstat.tcps_rcvbyte += ti->ti_len;
    			/*
    			 * Drop TCP, IP headers and TCP options then add data
    			 * to socket buffer.
    			 */
    			m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);
    			m->m_len -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);
    			sbappend(&so->so_rcv, m);        //将数据copy 进mbuf中
    			sorwakeup(so);        //唤醒等待接收数据的进程
    			tp->t_flags |= TF_DELACK;        //置位延迟ACK标志位
    			return;
    		}
    	}
    
            //首部预测失败时,执行的代码
    	m->m_data += sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);        //丢弃TCP,IP以及TCP选项的数据       
    	m->m_len  -= sizeof(struct tcpiphdr)+off-sizeof(struct tcphdr);
    
    	{ int win;
    
    	win = sbspace(&so->so_rcv);        //计算接收窗口缓存中的可用字节数
    	if (win < 0)
    		win = 0;
    	tp->rcv_wnd = max(win, (int)(tp->rcv_adv - tp->rcv_nxt));        //获取通告给对方的窗口和接收窗口较大的一个,为了当前的接收窗口不小于当前的通告窗口
    	}
    
    	switch (tp->t_state) {
    	case TCPS_LISTEN: {        //如果当前的状态处于LISTEN
    		struct mbuf *am;
    		register struct sockaddr_in *sin;
    
    		if (tiflags & TH_RST)        //如果置位了RST,丢弃
    			goto drop;
    		if (tiflags & TH_ACK)        //如果收到了ACK,丢弃并发送RST
    			goto dropwithreset;
    		if ((tiflags & TH_SYN) == 0)        //丢弃一切非SYN的标志
    			goto drop;
    	
    		if (m->m_flags & (M_BCAST|M_MCAST) ||
    		    IN_MULTICAST(ti->ti_dst.s_addr))        //如果收到了多播或者广播之类的数据报,丢弃
    			goto drop;
    		am = m_get(M_DONTWAIT, MT_SONAME);	    //分配一个mbuf,保存sockaddr_in结构,其中带有客户端IP和端口号
    		if (am == NULL)
    			goto drop;
    		am->m_len = sizeof (struct sockaddr_in);        //将mbuf的长度设置为sockaddr_in
    		sin = mtod(am, struct sockaddr_in *);
    		sin->sin_family = AF_INET;        //填充sockaddr_in中的信息
    		sin->sin_len = sizeof(*sin);
    		sin->sin_addr = ti->ti_src;
    		sin->sin_port = ti->ti_sport;
    		bzero((caddr_t)sin->sin_zero, sizeof(sin->sin_zero));
    		laddr = inp->inp_laddr;        //设定TCP中的本地地址
    		if (inp->inp_laddr.s_addr == INADDR_ANY)    //如果PCB中的本地地址为ANY
    			inp->inp_laddr = ti->ti_dst;        //设置为准确的地址
    		if (in_pcbconnect(inp, am)) {        //设置Internet PCB中的远程地址
    			inp->inp_laddr = laddr;         //如果失败,丢弃报文段 ,等待对方发送超时
    			(void) m_free(am);
    			goto drop;
    		}
    		(void) m_free(am);      //填充完成,释放作为临时存储的mbuf
    		tp->t_template = tcp_template(tp);        //设置新分配的TCP PCB中的TCP IP header
    		if (tp->t_template == 0) {        //如果失败
    			tp = tcp_drop(tp, ENOBUFS);        //丢弃报文段,等待对方发送超时
    			dropsocket = 0;		
    			goto drop;
    		}
    		if (optp)        //如果存在TCP选项,丢弃选项
    			tcp_dooptions(tp, optp, optlen, ti,
    				&ts_present, &ts_val, &ts_ecr);
    		if (iss)        //设置ISS
    			tp->iss = iss;
    		else
    			tp->iss = tcp_iss;
    		tcp_iss += TCP_ISSINCR/2;
    		tp->irs = ti->ti_seq;        //设置初始序列号
    		tcp_sendseqinit(tp);        //初始化发送相关的序列号
    		tcp_rcvseqinit(tp);        //初始化接收相关的序列号
    		tp->t_flags |= TF_ACKNOW;        //立刻发送ACK
    		tp->t_state = TCPS_SYN_RECEIVED;        //将目前的状态转化我SYN_RECV
    		tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT;        //重新设置Keep-Alive
    		dropsocket = 0;		//跳转到接下来的位置,完成对SYN报文段的处理
    		tcpstat.tcps_accepts++;
    		goto trimthenstep6;
    		}
    
    	case TCPS_SYN_SENT:        //已经发送了SYN报文段,等待接收SYN报文段以及对发送的SYN报文段的ACK
    		if ((tiflags & TH_ACK) &&
    		    (SEQ_LEQ(ti->ti_ack, tp->iss) ||
    		     SEQ_GT(ti->ti_ack, tp->snd_max)))        //如果收到ACK,ACK小于发送的ISS或者ACK大于最大的发送值,丢弃,并发送RST
    			goto dropwithreset;
    		if (tiflags & TH_RST) {        //如果收到了RST,并且有携带ACK,说明对端拒绝连接,对端的服务没有启动,丢弃连接并返回差错
    			if (tiflags & TH_ACK)
    				tp = tcp_drop(tp, ECONNREFUSED);
    			goto drop;        //如果不携带ACK的话,丢弃连接就好了,等待对方的发送超时
    		}
    		if ((tiflags & TH_SYN) == 0)        //如果SYN没有置位,丢弃
    			goto drop;
    		if (tiflags & TH_ACK) {        //如果SYN和ACK都已经置位
    			tp->snd_una = ti->ti_ack;        //将未确认的seq修改为收到的ACK
    			if (SEQ_LT(tp->snd_nxt, tp->snd_una))    //如果待发送的next小于未被确认的数据,调整待发送的数据
    				tp->snd_nxt = tp->snd_una;        
    		}
    		tp->t_timer[TCPT_REXMT] = 0;        //关闭连接建立定时器
    		tp->irs = ti->ti_seq;    //初始序号从接收的报文段中获取
    		tcp_rcvseqinit(tp);
    		tp->t_flags |= TF_ACKNOW;        //将立刻发送ACK置位
    		if (tiflags & TH_ACK && SEQ_GT(tp->snd_una, tp->iss)) {        //如果收到了ACK并且uma > 连接的ISS(第二个条件是多余的),说明连接已经建立
    			tcpstat.tcps_connects++;        //更新全局计数
    			soisconnected(so);        //设置socket进入连接状态
    			tp->t_state = TCPS_ESTABLISHED;
    		
    			if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) ==
    				(TF_RCVD_SCALE|TF_REQ_SCALE)) {        //确定是否远端使用窗口放大选项
    				tp->snd_scale = tp->requested_s_scale;
    				tp->rcv_scale = tp->request_r_scale;
    			}
    			(void) tcp_reass(tp, (struct tcpiphdr *)0,
    				(struct mbuf *)0);        //如果数据在连接尚未建立之前到达,将数据放到接收缓存中
    			
    			if (tp->t_rtt)        //更新RTT计数器
    				tcp_xmit_timer(tp, tp->t_rtt);
    		} else
    			tp->t_state = TCPS_SYN_RECEIVED;        //处理同时打开的情况
    
    trimthenstep6:        //处理SYN中携带数据的情况
    		ti->ti_seq++;        //收到了SYN,递增seq
    		if (ti->ti_len > tp->rcv_wnd) {        //如果收到的数据大于接收窗口的大小
    			todrop = ti->ti_len - tp->rcv_wnd;        //获取超出接收窗口的数据的长度
    			m_adj(m, -todrop);        //丢弃超出窗口的长度
    			ti->ti_len = tp->rcv_wnd;        //调整收到的数据的长度
    			tiflags &= ~TH_FIN;        //置位FIN标记
    			tcpstat.tcps_rcvpackafterwin++;        //修改全局数据
    			tcpstat.tcps_rcvbyteafterwin += todrop;
    		}
    		tp->snd_wl1 = ti->ti_seq - 1;        //强制更新窗口变量
    		tp->rcv_up = ti->ti_seq;
    		goto step6;
    	}
    
    	if (ts_present && (tiflags & TH_RST) == 0 && tp->ts_recent &&
    	    TSTMP_LT(ts_val, tp->ts_recent)) {        //处理序号回绕的情况。需要只有32位表示,如果发送速率过快的话,很快就会出现数据回绕的情况
    		if ((int)(tcp_now - tp->ts_recent_age) > TCP_PAWS_IDLE) {
    			tp->ts_recent = 0;
    		} else {
    			tcpstat.tcps_rcvduppack++;
    			tcpstat.tcps_rcvdupbyte += ti->ti_len;
    			tcpstat.tcps_pawsdrop++;
    			goto dropafterack;
    		}
    	}
    
    	todrop = tp->rcv_nxt - ti->ti_seq;        //如果发送的数据存在之前已经发送的数据,需要裁剪新到达的数据报
    	if (todrop > 0) {        //如果确实存在重复的数据
    		if (tiflags & TH_SYN) {    //如果已经置位了SYN标志
    			tiflags &= ~TH_SYN;        //清除重复的SYN数据报
    			ti->ti_seq++;    //递增seq
    			if (ti->ti_urp > 1)         //如果存在紧急数据偏移量
    				ti->ti_urp--;
    			else
    				tiflags &= ~TH_URG;        //如果不存在,置位URG标志
    			todrop--;
    		}
    		if (todrop >= ti->ti_len) {        //如果数据报完全重复
    			tcpstat.tcps_rcvduppack++;        //更新全局统计量
    			tcpstat.tcps_rcvdupbyte += ti->ti_len;
    			
    			if ((tiflags & TH_FIN && todrop == ti->ti_len + 1)){        //测试FIN是否重复
    				todrop = ti->ti_len;        //忽略重复的FIN
    				tiflags &= ~TH_FIN;        //清除FIN标记,并且立刻发送ACK标记
    				tp->t_flags |= TF_ACKNOW;        
    			} else {
    				if (todrop != 0 || (tiflags & TH_ACK) == 0)        //如果携带的数据全部是重复的,并且没有确认本地已发送数据的ACK
    					goto dropafterack;        //丢弃数据,并发送ACK
    			}
    		} else {
    			tcpstat.tcps_rcvpartduppack++;        //更新全局统计量
    			tcpstat.tcps_rcvpartdupbyte += todrop;
    		}
    		m_adj(m, todrop);        //调整丢弃多余的数据量
    		ti->ti_seq += todrop;
    		ti->ti_len -= todrop;
    		if (ti->ti_urp > todrop)    //删除重复的数据,更新紧急数据
    			ti->ti_urp -= todrop;
    		else {
    			tiflags &= ~TH_URG;
    			ti->ti_urp = 0;
    		}
    	}
    
    	if ((so->so_state & SS_NOFDREF) &&
    	    tp->t_state > TCPS_CLOSE_WAIT && ti->ti_len) {        //如果应用程序已经关闭连接,在新收到数据的时候,直接关闭连接,并向对方发送RST
    		tp = tcp_close(tp);
    		tcpstat.tcps_rcvafterclose++;
    		goto dropwithreset;
    	}
    
    	todrop = (ti->ti_seq+ti->ti_len) - (tp->rcv_nxt+tp->rcv_wnd);        //计算落在通告窗口右侧的字节数
    	if (todrop > 0) {        //如果有重复的数据
    		tcpstat.tcps_rcvpackafterwin++;
    		if (todrop >= ti->ti_len) {        //如果数据完全重复
    			tcpstat.tcps_rcvbyteafterwin += ti->ti_len;        
    			if (tiflags & TH_SYN &&
    			    tp->t_state == TCPS_TIME_WAIT &&
    			    SEQ_GT(ti->ti_seq, tp->rcv_nxt)) {        //如果携带SYN,当前状态处于TIME_WAIT,收到的序列号>期望接受的序列号
    				iss = tp->rcv_nxt + TCP_ISSINCR;        //说明对端在连接即将被关闭的情况下想要重新创建连接
    				tp = tcp_close(tp);        //释放已经存在的Internet PCB和TCP PCB,并跳转到起始处重新建立连接
    				goto findpcb;
    			}
    
    			if (tp->rcv_wnd == 0 && ti->ti_seq == tp->rcv_nxt) {        //如果接收窗口没有空间,并且发送的数据seq等于接收端准备接收的数据,意味着,此次接受到的是一个窗口探测报文
    				tp->t_flags |= TF_ACKNOW;        //立刻发送ACK确认报文
    				tcpstat.tcps_rcvwinprobe++;
    			} else        //否则的话报文在窗口之外并且非窗口探测报文,则应该丢弃这个报文段
    				goto dropafterack;
    		} else
    			tcpstat.tcps_rcvbyteafterwin += todrop;
    		m_adj(m, -todrop);        //从mbuf链中移除落在右侧的报文段,更新收到的报文段长度
    		ti->ti_len -= todrop;
    		tiflags &= ~(TH_PUSH|TH_FIN);        //将SYN和FIN置位
    	}
    
    	if (ts_present && SEQ_LEQ(ti->ti_seq, tp->last_ack_sent) &&
    	    SEQ_LT(tp->last_ack_sent, ti->ti_seq + ti->ti_len +
    		   ((tiflags & (TH_SYN|TH_FIN)) != 0))) {        //还是处理TCP的时间戳选项
    		tp->ts_recent_age = tcp_now;
    		tp->ts_recent = ts_val;
    	}
    
    	if (tiflags&TH_RST) switch (tp->t_state) {        //如果收到了RST标志,根据此刻的状态,做不同的处理。最后都会关闭SOCKET,区别在于会返回不同的错误
    
    	case TCPS_SYN_RECEIVED:        //如果在连接未建立阶段出现RST,直接关闭
    		so->so_error = ECONNREFUSED;
    		goto close;
    
    	case TCPS_ESTABLISHED:    //如果在连接状态或者在连接未完全关闭阶段出现RST,直接关闭
    	case TCPS_FIN_WAIT_1:
    	case TCPS_FIN_WAIT_2:
    	case TCPS_CLOSE_WAIT:
    		so->so_error = ECONNRESET;
    	close:
    		tp->t_state = TCPS_CLOSED;
    		tcpstat.tcps_drops++;
    		tp = tcp_close(tp);
    		goto drop;
    
    	case TCPS_CLOSING:
    	case TCPS_LAST_ACK:
    	case TCPS_TIME_WAIT:
    		tp = tcp_close(tp);
    		goto drop;
    	}
    
    	if (tiflags & TH_SYN) {        //如果此时的SYN依旧置位,丢弃报文并向对端发送RST
    		tp = tcp_drop(tp, ECONNRESET);
    		goto dropwithreset;
    	}
    
    	if ((tiflags & TH_ACK) == 0)    //如果ACK没有置位,直接丢弃连接
    		goto drop;
    	
            //接下来是根据ACK更新窗口的数据
    	switch (tp->t_state) {
    	case TCPS_SYN_RECEIVED:        //如果在Recv状态下收到了ACK。这种情况下,将处理被动打开以及同时打开的情况。
    		if (SEQ_GT(tp->snd_una, ti->ti_ack) ||        
    		    SEQ_GT(ti->ti_ack, tp->snd_max))        //如果未确认的seq > 接收到的ACK或者收到的ACK > 接收端的max seq,丢弃报文段并发送RST
    			goto dropwithreset;        //一般情况下,收到的ACK应该介于未确认的seq以及max seq之间
    		tcpstat.tcps_connects++;
    		soisconnected(so);        //唤醒在ACCEPT上等待的进程d
    		tp->t_state = TCPS_ESTABLISHED;        //调整连接状态为ESTABLISHED
    
    		if ((tp->t_flags & (TF_RCVD_SCALE|TF_REQ_SCALE)) ==
    			(TF_RCVD_SCALE|TF_REQ_SCALE)) {        //如果设置了窗口大小选项,更新窗口大小
    			tp->snd_scale = tp->requested_s_scale;
    			tp->rcv_scale = tp->request_r_scale;
    		}
    		(void) tcp_reass(tp, (struct tcpiphdr *)0, (struct mbuf *)0);        //将乱序队列中的数据提交给recvbuf
    		tp->snd_wl1 = ti->ti_seq - 1;        
    
    	case TCPS_ESTABLISHED:
    	case TCPS_FIN_WAIT_1:
    	case TCPS_FIN_WAIT_2:
    	case TCPS_CLOSE_WAIT:
    	case TCPS_CLOSING:
    	case TCPS_LAST_ACK:
    	case TCPS_TIME_WAIT:        
                    //处理重复的ACK
    		if (SEQ_LEQ(ti->ti_ack, tp->snd_una)) {        //如果收到的ACK小于未确认的seq
    			if (ti->ti_len == 0 && tiwin == tp->snd_wnd) {        //如果没有携带数据通告窗口的大小没有改变
    				tcpstat.tcps_rcvdupack++;        //增加重复ACK的计数
    
    				if (tp->t_timer[TCPT_REXMT] == 0 ||
    				    ti->ti_ack != tp->snd_una)        //如果收到的ACK小于uma或者重传定时器没有设置
    					tp->t_dupacks = 0;        //将重复的ACK计数置0
    				else if (++tp->t_dupacks == tcprexmtthresh) {        //如果重复的ACK == 3,指向拥塞避免算法
    					tcp_seq onxt = tp->snd_nxt;        //获取下一个即将发送的数值
    					u_int win =
    					    min(tp->snd_wnd, tp->snd_cwnd) / 2 /
    						tp->t_maxseg;        //窗口设置为拥塞窗口或者当前发送窗口中的最小值的一半
    
    					if (win < 2)        //窗口最小为2
    						win = 2;
    					tp->snd_ssthresh = win * tp->t_maxseg;        //将慢启动门限设置为:拥塞窗口与发送窗口中较小值的一半
        					tp->t_timer[TCPT_REXMT] = 0;        //关闭重传计数器
    					tp->t_rtt = 0;        //将RTT置0
    					tp->snd_nxt = ti->ti_ack;        //将即将发送的seq修改为收到的ack,也就丢失的报文段(对方期待的报文段)
    					tp->snd_cwnd = tp->t_maxseg;        //将拥塞窗口设置为MSS
    					(void) tcp_output(tp);        //发送数据
    					tp->snd_cwnd = tp->snd_ssthresh +
    					       tp->t_maxseg * tp->t_dupacks;        //调整拥塞窗口为慢启动门限+3×MSS
    					if (SEQ_GT(onxt, tp->snd_nxt))        //将待发送seq调整为之前保存的seq
    						tp->snd_nxt = onxt;
    					goto drop;        //然后丢弃本次收到的ACK
    				} else if (tp->t_dupacks > tcprexmtthresh) {        //如果重复的ACK计数 > 3
    					tp->snd_cwnd += tp->t_maxseg;    //每次收到一个ACK,就递增拥塞窗口
    					(void) tcp_output(tp);        //发送数据
    					goto drop;        //丢弃本次收到的ACK
    				}
    			} else
    				tp->t_dupacks = 0;        //收到的重复ACK中带有数据,不是一个单纯的ACK计数作用
    			break;
    		}
    		
    		if (tp->t_dupacks > tcprexmtthresh &&
    		    tp->snd_cwnd > tp->snd_ssthresh)        //如果收到的重复的ACK计数 > 3,说明这是在收到了四个或者四个以上的ACK后,收到的第一个非重复的ACK。快速重传算法结束
    			tp->snd_cwnd = tp->snd_ssthresh;        //调整拥塞窗口为慢启动门限
    		tp->t_dupacks = 0;        //将重复的ACK计数置0
    		if (SEQ_GT(ti->ti_ack, tp->snd_max)) {        //如果收到的ACK大于还没有发送的数据。可能是高速网络的回绕。
    			tcpstat.tcps_rcvacktoomuch++;        //丢弃数据报并回复一个ACK
    			goto dropafterack;
    		}
    		acked = ti->ti_ack - tp->snd_una;    //记录收到的ACK确认的数据量
    		tcpstat.tcps_rcvackpack++;
    		tcpstat.tcps_rcvackbyte += acked;
    
    		if (ts_present)        //如果存在时间戳选项,记录并更新RTT
    			tcp_xmit_timer(tp, tcp_now-ts_ecr+1);
    		else if (tp->t_rtt && SEQ_GT(ti->ti_ack, tp->t_rtseq))
    			tcp_xmit_timer(tp,tp->t_rtt);
    
    		if (ti->ti_ack == tp->snd_max) {如果已发送了所有数据,关闭重传定时器
    			tp->t_timer[TCPT_REXMT] = 0;
    			needoutput = 1;        
    		} else if (tp->t_timer[TCPT_PERSIST] == 0)        //如果持续定时器已经被关闭
    			tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;        //重启重传定时器
    	
    		{
    		register u_int cw = tp->snd_cwnd;    //获取拥塞窗口大小
    		register u_int incr = tp->t_maxseg;        //获取MSS
    
    		if (cw > tp->snd_ssthresh)        //如果拥塞窗口 > 慢启动门限,拥塞窗口的更新速度减慢
    			incr = incr * incr / cw + incr / 8;
    		tp->snd_cwnd = min(cw + incr, TCP_MAXWIN<<tp->snd_scale);
    		}
    		if (acked > so->so_snd.sb_cc) {        //如果确认的字节数 > 发送缓存中的字节数
    			tp->snd_wnd -= so->so_snd.sb_cc;        //在发送窗口中减去已经被确认的缓存
    			sbdrop(&so->so_snd, (int)so->so_snd.sb_cc);        //从socket的缓存中删除所有的字节
    			ourfinisacked = 1;
    		} else {
    			sbdrop(&so->so_snd, acked);        //如果发送缓存中的数据没有被完全的确认,从socket的缓存中丢弃已经被确认的字节数
    			tp->snd_wnd -= acked;        //从发送窗口减去发送的字节数
    			ourfinisacked = 0;
    		}
    		if (so->so_snd.sb_flags & SB_NOTIFY)        //唤醒等待发送的进程
    			sowwakeup(so);
    		tp->snd_una = ti->ti_ack;        //调整未被确认的数据为接收到的ACK seq
    		if (SEQ_LT(tp->snd_nxt, tp->snd_una))        //调整next seq
    			tp->snd_nxt = tp->snd_una;
    
                    //处理四种特殊情况下的ACK
    		switch (tp->t_state) {        
    			case TCPS_FIN_WAIT_1:        //如果处于FIN_WAIT_1时,收到ACK
    			if (ourfinisacked) {        //如果数据已经发送完毕
    				if (so->so_state & SS_CANTRCVMORE){        //将状态转换为FIN_WAIT_2,并将FIN_WAIT_2定时器置位
    					soisdisconnected(so);
    					tp->t_timer[TCPT_2MSL] = tcp_maxidle;
    				}
    				tp->t_state = TCPS_FIN_WAIT_2;
    			}
    			break;
    
    		case TCPS_CLOSING:        //如果此时的状态处于CLOSING
    			if (ourfinisacked) {    
    				tp->t_state = TCPS_TIME_WAIT;        //将状态转换为TIME_WAIT
    				tcp_canceltimers(tp);        //取消所有的定时器
    				tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;        //启动2MSL定时器
    				soisdisconnected(so);        //关闭插口
    			}
    			break;
    
    		case TCPS_LAST_ACK:        //如果被动关闭端收到ACK
    			if (ourfinisacked) {        //如果数据已经发送完毕
    				tp = tcp_close(tp);        //直接关闭插口
    				goto drop;
    			}
    			break;
    
    		case TCPS_TIME_WAIT:        //如果处于TIME_WAIT状态
    			tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;        //对TIME_WAIT重新计数
    			goto dropafterack;        //然后丢弃数据报并发送ACK
    		}
    	}
    
    step6:
    	if ((tiflags & TH_ACK) &&
    	    (SEQ_LT(tp->snd_wl1, ti->ti_seq) || tp->snd_wl1 == ti->ti_seq &&
    	    (SEQ_LT(tp->snd_wl2, ti->ti_ack) ||
    	     tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))) {        //这么多判断的目的是为了更新发送窗口,首先,接收到的是一个ACK等等的条件
    		/* keep track of pure window updates */
    		if (ti->ti_len == 0 &&
    		    tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd)        //报文段没有携带数据,但是报文段确认了新的数据
    			tcpstat.tcps_rcvwinupd++;
    		tp->snd_wnd = tiwin;        //更新发送窗口
    		tp->snd_wl1 = ti->ti_seq;        //更新最后接收到的报文段序号
    		tp->snd_wl2 = ti->ti_ack;        //更新最后接受到的ACK序号
    		if (tp->snd_wnd > tp->max_sndwnd)        //调整max seq的位置
    			tp->max_sndwnd = tp->snd_wnd;
    		needoutput = 1;        //清理了发送缓存,意味着可以继续发送数据
    	}
    
    	if ((tiflags & TH_URG) && ti->ti_urp &&
    	    TCPS_HAVERCVDFIN(tp->t_state) == 0) {        //处理URG数据,不重点关注
    		if (ti->ti_urp + so->so_rcv.sb_cc > sb_max) {
    			ti->ti_urp = 0;			/* XXX */
    			tiflags &= ~TH_URG;		/* XXX */
    			goto dodata;			/* XXX */
    		}
    
    		if (SEQ_GT(ti->ti_seq+ti->ti_urp, tp->rcv_up)) {
    			tp->rcv_up = ti->ti_seq + ti->ti_urp;
    			so->so_oobmark = so->so_rcv.sb_cc +
    			    (tp->rcv_up - tp->rcv_nxt) - 1;
    			if (so->so_oobmark == 0)
    				so->so_state |= SS_RCVATMARK;
    			sohasoutofband(so);
    			tp->t_oobflags &= ~(TCPOOB_HAVEDATA | TCPOOB_HADDATA);
    		}
    		
    		if (ti->ti_urp <= ti->ti_len
    #ifdef SO_OOBINLINE
    		     && (so->so_options & SO_OOBINLINE) == 0
    #endif
    		     )
    			tcp_pulloutofband(so, ti, m);
    	} else
    		if (SEQ_GT(tp->rcv_nxt, tp->rcv_up))
    			tp->rcv_up = tp->rcv_nxt;
    dodata:    //处理已经接受的数据
    
    	if ((ti->ti_len || (tiflags&TH_FIN)) &&
    	    TCPS_HAVERCVDFIN(tp->t_state) == 0) {        //如果携带数据,或者FIN置位,并且连接还没有收到过FIN
    		TCP_REASS(tp, ti, m, so, tiflags);        //判断是否需要将数据添加到乱序链表中
    		len = so->so_rcv.sb_hiwat - (tp->rcv_adv - tp->rcv_nxt);        //计算对端发送缓存的大小
    	} else {
    		m_freem(m);
    		tiflags &= ~TH_FIN;
    	}
    
            //处理带有FIN标志的数据报
            //如果A向B发送了FIN,相当于A告诉B:我不打算接收你发送的数据了,但是我还有往里面写数据
            //B在接收到FIN之后:那我只读你写的数据,不往管道中发送数据了
    	if (tiflags & TH_FIN) {        //
    		if (TCPS_HAVERCVDFIN(tp->t_state) == 0) {        //如果是接收到的第一个FIN数据报
    			socantrcvmore(so);        //将插口设置为只读
    			tp->t_flags |= TF_ACKNOW;        //立刻发送ACK
    			tp->rcv_nxt++;        //递增recv next
    		}
    		switch (tp->t_state) {    
    		case TCPS_SYN_RECEIVED:
    		case TCPS_ESTABLISHED:        //根据当前的状态设置在接收到FIN之后,状态如何变化
    			tp->t_state = TCPS_CLOSE_WAIT;        //如果当前是ESTABLISHED,状态转变为:等待关闭
    			break;
    
    		case TCPS_FIN_WAIT_1:        //如果处于FIN_WAIT_1,转换为CLOSING,如果收到ACK,转换为FIN_WAIT_2
    			tp->t_state = TCPS_CLOSING;
    			break;
    
    		case TCPS_FIN_WAIT_2:        //将状态转换为TIME_WAIT
    			tp->t_state = TCPS_TIME_WAIT;
    			tcp_canceltimers(tp);        //置位所有的定时器
    			tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;        //设置2MSL定时器
    			soisdisconnected(so);        //关闭插口
    			break;
    
    		case TCPS_TIME_WAIT:        //如果此时处于TIME_WAIT状态,收到了重复的FIN,重新开始2MSL计时
    			tp->t_timer[TCPT_2MSL] = 2 * TCPTV_MSL;
    			break;
    		}
    	}
    	if (so->so_options & SO_DEBUG)        //如果记录了
    		tcp_trace(TA_INPUT, ostate, tp, &tcp_saveti, 0);
    
    	if (needoutput || (tp->t_flags & TF_ACKNOW))        //如果需要发送数据,就立刻发送数据
    		(void) tcp_output(tp);
    	return;
    
    dropafterack:        //丢弃收到的数据并立刻发送ACK
    	if (tiflags & TH_RST)
    		goto drop;
    	m_freem(m);
    	tp->t_flags |= TF_ACKNOW;
    	(void) tcp_output(tp);
    	return;
    
    dropwithreset:        //丢弃收到的数据并立刻发送RST
    	if ((tiflags & TH_RST) || m->m_flags & (M_BCAST|M_MCAST) ||
    	    IN_MULTICAST(ti->ti_dst.s_addr))
    		goto drop;
    	if (tiflags & TH_ACK)
    		tcp_respond(tp, ti, m, (tcp_seq)0, ti->ti_ack, TH_RST);
    	else {
    		if (tiflags & TH_SYN)
    			ti->ti_len++;
    		tcp_respond(tp, ti, m, ti->ti_seq+ti->ti_len, (tcp_seq)0,
    		    TH_RST|TH_ACK);
    	}
    	if (dropsocket)
    		(void) soabort(so);
    	return;
    
    drop:        //丢弃收到的数据
    	if (tp && (tp->t_inpcb->inp_socket->so_options & SO_DEBUG))
    		tcp_trace(TA_DROP, ostate, tp, &tcp_saveti, 0);
    	m_freem(m);
    	/* destroy temporarily created socket */
    	if (dropsocket)
    		(void) soabort(so);
    	return;
    #ifndef TUBA_INCLUDE
    }
    

    tcp_dooptions

    • 功能A:处理TCP选项:EOL,NOP,MSS,窗口大小,时间戳
    void
    tcp_dooptions(tp, cp, cnt, ti, ts_present, ts_val, ts_ecr)
    	struct tcpcb *tp;
    	u_char *cp;
    	int cnt;
    	struct tcpiphdr *ti;
    	int *ts_present;
    	u_long *ts_val, *ts_ecr;
    {
    	u_short mss;
    	int opt, optlen;
    
    	for (; cnt > 0; cnt -= optlen, cp += optlen) {
    		opt = cp[0];
    		if (opt == TCPOPT_EOL)
    			break;
    		if (opt == TCPOPT_NOP)
    			optlen = 1;
    		else {
    			optlen = cp[1];
    			if (optlen <= 0)
    				break;
    		}
    		switch (opt) {
    
    		default:
    			continue;
    
    		case TCPOPT_MAXSEG:        //处理MSS选项
    			if (optlen != TCPOLEN_MAXSEG)        //如果MSS选项的长度!=4,结束处理
    				continue;
    			if (!(ti->ti_flags & TH_SYN))        //如果报文段不携带SYN选项,结束处理
    				continue;
    			bcopy((char *) cp + 2, (char *) &mss, sizeof(mss));        //将选项中的MSS copy进来,转换字节序,并调用tcp mss函数进行处理
    			NTOHS(mss);
    			(void) tcp_mss(tp, mss);	/* sets t_maxseg */
    			break;
    
    		case TCPOPT_WINDOW:        //如果携带窗口大小选项
    			if (optlen != TCPOLEN_WINDOW)        //如果窗口大小!=4,结束处理
    				continue;
    			if (!(ti->ti_flags & TH_SYN))        //如果没有出现在SYN报文段中,结束处理
    				continue;
    			tp->t_flags |= TF_RCVD_SCALE;        //保存窗口的缩放因子,如果双方都支持窗口大小的选项处理,使用窗口缩放处理功能
    			tp->requested_s_scale = min(cp[2], TCP_MAX_WINSHIFT);
    			break;
    
    		case TCPOPT_TIMESTAMP:        //处理时间戳选项
    			if (optlen != TCPOLEN_TIMESTAMP)        //如果时间戳选项的长度!=10,结束处理
    				continue;
    			*ts_present = 1;        //标志时间戳选项
    			bcopy((char *)cp + 2, (char *) ts_val, sizeof(*ts_val));        //保存时间戳的数值
    			NTOHL(*ts_val);
    			bcopy((char *)cp + 6, (char *) ts_ecr, sizeof(*ts_ecr));
    			NTOHL(*ts_ecr);
    
    			if (ti->ti_flags & TH_SYN) {    //如果在SYN选项中带有时间戳选项
    				tp->t_flags |= TF_RCVD_TSTMP;        //置位标志位
    				tp->ts_recent = *ts_val;        //填充时间戳
    				tp->ts_recent_age = tcp_now;
    			}
    			break;
    		}
    	}
    }
    
  • 相关阅读:
    (原)Lazarus 异构平台下多层架构思路、DataSet转换核心代码
    (学)新版动态表单研发,阶段成果3
    (学) 如何将 Oracle 序列 重置 清零 How to reset an Oracle sequence
    (学)XtraReport WebService Print 报错
    (原)三星 i6410 刷机 短信 无法 保存 解决 办法
    (原) Devexpress 汉化包 制作工具、测试程序
    linux下网络配置
    apache自带ab.exe小工具使用小结
    Yii::app()用法小结
    PDO使用小结
  • 原文地址:https://www.cnblogs.com/ukernel/p/9191039.html
Copyright © 2011-2022 走看看