当创建了TCP协议套接字后,客户端再调用connect()函数请求建立TCP链接。该函数通过系统调用触发tcp_v4_connect函数的执行,产生一个包含SYN标志和一个32位的序号的连接请求包,并发送给服务器端。
这是TCP三次握手的第一步。
我们知道,两个不同主机间的进程要进行通信,他们所发送的数据包所经过的路径为:
应用层→传输层→网络层→网络介质层
所以tcp_v4_connect()函数发出的SYN标志的请求包也要经过这些路径。
传输层负责的是不同应用程序之间的通信。即不同端口
网络层负责的使不同主机之间的通信,即不同的IP地址。在这期间数据包的传输通过路由来实现。路由发生在网络层。
时间戳:发送方在报文中放置一个时间戳值,接受方在确认时返回这个值以及接受时的时间戳值,以此来计算RTT值。
TCP超时与重传中最重要的部分就是对一个给定连接的往返时间( RTT)的测量。由于路由器和网络流量均会变化,因此我们认为这个时间可能经常会发生变化, TCP应该跟踪这些变化并相应地改变其超时时间。
MSS:表示TCP传往另一端的最大块数据长度,在连接建立时(SYN)双方各自通告自己的Mss,否则默认为536字节(加上20字节IP报头和TCP报头构成576字节的IP数据报)。长度为4字节
在一般的体制中,I P可以从T C P、U D P、I C M P和I G M P接收数据报(即在本地生成的数据
报)并进行发送,或者从一个网络接口接收数据报(待转发的数据报)并进行发送。IP层在内存中有一个路由表。当收到一份数据报并进行发送时,它都要对该表搜索一次。当数据报来自某个网络接口时, IP首先检查目的IP地址是否为本机的I P地址之一或者I P广播地址。如果确实是这样,数据报就被送到由I P首部协议字段所指定的协议模块进行处理。如果数据报的目的不是这些地址,那么( 1)如果I P层被设置为路由器的功能,那么就对数据报进行转发(也就是说,像下面对待发出的数据报一样处理);否则( 2)数据报被丢弃。
由上述分析这个函数应包含路由,端口,产生SYN分节,生成序号,发送包等部分。
754 int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
{
756 struct inet_opt *inet = inet_sk(sk);
//变量inet指向套接字struct sock中的inet选项
757 struct tcp_opt *tp = tcp_sk(sk);
//变量tp指向套接字struct sock中的TCP选项
758 struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
//将通用地址结构转换为IPv4的地址结构
759 struct rtable *rt;
//记录路由表项
760 u32 daddr, nexthop;
//daddr记录目的地址,nesthop记录下一条地址
761 int tmp;
762 int err;
763
//判断地址长度是否合法,应该要大于或者等于其地址的长度
764 if (addr_len < sizeof(struct sockaddr_in))
765 return -EINVAL;
//判断是否为inet协议族
767 if (usin->sin_family != AF_INET)
768 return -EAFNOSUPPORT;
//初始化目的地址和下一条地址
770 nexthop = daddr = usin->sin_addr.s_addr;
771 if (inet->opt && inet->opt->srr) {
772 if (!daddr)
773 return -EINVAL;
774 nexthop = inet->opt->faddr;
775 }
//查找路由表项,并通过变量rt记录下来
777 tmp = ip_route_connect(&rt, nexthop, inet->saddr,
778 RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
779 IPPROTO_TCP,
780 inet->sport, usin->sin_port, sk);
781 if (tmp < 0)
782 return tmp;
783
784 if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
785 ip_rt_put(rt);
786 return -ENETUNREACH;
787 }
788
789 if (!inet->opt || !inet->opt->srr)
790 daddr = rt->rt_dst;//如果源地址为0,则把rt_ src赋给源地址
791
792 if (!inet->saddr)
793 inet->saddr = rt->rt_src;
794 inet->rcv_saddr = inet->saddr;
//初始化TCP选项
796 if (tp->ts_recent_stamp && inet->daddr != daddr) {
797 /* Reset inherited state */
798 tp->ts_recent = 0;//Time stamp to echo next
799 tp->ts_recent_stamp = 0;//Time we stored ts_recent (for aging)
800 tp->write_seq = 0;//序号初始化为0
801 }
802
803 if (sysctl_tcp_tw_recycle &&
804 !tp->ts_recent_stamp && rt->rt_dst == daddr) {
805 struct inet_peer *peer = rt_get_peer(rt);
806
807 /* VJ's idea. We save last timestamp seen from
808 * the destination in peer table, when entering state TIME-WAIT
809 * and initialize ts_recent from it, when trying new connection.
810 */
811
812 if (peer && peer->tcp_ts_stamp + TCP_PAWS_MSL >= xtime.tv_sec) {
813 tp->ts_recent_stamp = peer->tcp_ts_stamp;
814 tp->ts_recent = peer->tcp_ts;
815 }
816 }
817
818 inet->dport = usin->sin_port;//目的端口地址
819 inet->daddr = daddr;//目的IP地址
820
821 tp->ext_header_len = 0;
822 if (inet->opt)
823 tp->ext_header_len = inet->opt->optlen;//inet的opt选项的长度
824
825 tp->mss_clamp = 536;
826
827 /* Socket identity is still unknown (sport may be zero).
828 * However we set state to SYN-SENT and not releasing socket
829 * lock select source port, enter ourselves into the hash tables and
830 * complete initialization after this.
831 */
//把套接字结构sk的状态置为TCP_SYN_SENT
832 tcp_set_state(sk, TCP_SYN_SENT);
//为套接字绑定一个端口,并记录在TCP的哈希表中
833 err = tcp_v4_hash_connect(sk);
834 if (err)
835 goto failure;
836
837 err = ip_route_newports(&rt, inet->sport, inet->dport, sk);
838 if (err)
839 goto failure;
840
841 /* OK, now commit destination to socket. */
//设置套接字的路由出口信息
842 __sk_dst_set(sk, &rt->u.dst);
843 tcp_v4_setup_caps(sk, &rt->u.dst);
844 tp->ext2_header_len = rt->u.dst.header_len;
//生成一个序号
846 if (!tp->write_seq)
847 tp->write_seq = secure_tcp_sequence_number(inet->saddr,
848 inet->daddr,
849 inet->sport,
850 usin->sin_port);
851
852 inet->id = tp->write_seq ^ jiffies;
//调用tcp_connect(sk)函数,为请求包设置SYN标志,并发出请求包
854 err = tcp_connect(sk);
855 rt = NULL;//rt指针指向内存0开始处
856 if (err)
857 goto failure;
858
859 return 0;
860
861 failure:
//如果连接失败,则需把套接字状态设置为:TCP_CLOSE
862 /* This unhashes the socket and releases the local port, if necessary. */
863 tcp_set_state(sk, TCP_CLOSE);
864 ip_rt_put(rt);
865 sk->sk_route_caps = 0;//sk_route_caps,网络驱动特征标志
866 inet->dport = 0;
867 return err;
868