tcp_v4_connect
tcp_v4_connect函数定义在tcp_ipv4.c中。这个函数到底会做些什么事情呢?从TCP/IP协议中,TCP连接建立的过程中,我们知道:TCP连接需要三次握手。
1. 客户端发送SYN包
2. 服务器端收SYN包,发SYN+ACK包
3. 客户端收SYN+ACK包,发ACK
为什么要理解为三次握手(handshake),而不叫三次碰面,文青一点的解释是,三次握手说明两个人已经见了三次面了,基本上可以算上认识了。那么TCP连接的建立也相当于是两台机器”认识“了。
按照协议的内容来理解的话,tcp_v4_connect返回0的时候,说明一条TCP连接已经建立了。
169 int tcp_v4_connect(struct sock *sk, struct sockaddr *uaddr, int addr_len)
170 {
171 struct inet_sock *inet = inet_sk(sk);
172 struct tcp_sock *tp = tcp_sk(sk);
173 struct sockaddr_in *usin = (struct sockaddr_in *)uaddr;
174 struct rtable *rt;
175 __be32 daddr, nexthop;
176 int tmp;
177 int err;
179 if (addr_len < sizeof(struct sockaddr_in))
180 return -EINVAL;
182 if (usin->sin_family != AF_INET)
183 return -EAFNOSUPPORT;
185 nexthop = daddr = usin->sin_addr.s_addr;
186 if (inet->opt && inet->opt->srr) {
187 if (!daddr)
188 return -EINVAL;
189 nexthop = inet->opt->faddr;
190 }
192 tmp = ip_route_connect(&rt, nexthop, inet->saddr,
193 RT_CONN_FLAGS(sk), sk->sk_bound_dev_if,
194 IPPROTO_TCP,
195 inet->sport, usin->sin_port, sk, 1);
196 if (tmp < 0) {
197 if (tmp == -ENETUNREACH)
198 IP_INC_STATS_BH(IPSTATS_MIB_OUTNOROUTES);
199 return tmp;
200 }
202 if (rt->rt_flags & (RTCF_MULTICAST | RTCF_BROADCAST)) {
203 ip_rt_put(rt);
204 return -ENETUNREACH;
205 }
207 if (!inet->opt || !inet->opt->srr)
208 daddr = rt->rt_dst;
210 if (!inet->saddr)
211 inet->saddr = rt->rt_src;
212 inet->rcv_saddr = inet->saddr;
214 if (tp->rx_opt.ts_recent_stamp && inet->daddr != daddr) {
216 tp->rx_opt.ts_recent = 0;
217 tp->rx_opt.ts_recent_stamp = 0;
218 tp->write_seq = 0;
219 }
221 if (tcp_death_row.sysctl_tw_recycle &&
222 !tp->rx_opt.ts_recent_stamp && rt->rt_dst == daddr) {
223 struct inet_peer *peer = rt_get_peer(rt);
224 /*
225 * VJ's idea. We save last timestamp seen from
226 * the destination in peer table, when entering state
227 * TIME-WAIT * and initialize rx_opt.ts_recent from it,
228 * when trying new connection.
230 if (peer != NULL &&
231 peer->tcp_ts_stamp + TCP_PAWS_MSL >= get_seconds()) {
232 tp->rx_opt.ts_recent_stamp = peer->tcp_ts_stamp;
233 tp->rx_opt.ts_recent = peer->tcp_ts;
234 }
235 }
237 inet->dport = usin->sin_port;
238 inet->daddr = daddr;
240 inet_csk(sk)->icsk_ext_hdr_len = 0;
241 if (inet->opt)
242 inet_csk(sk)->icsk_ext_hdr_len = inet->opt->optlen;
244 tp->rx_opt.mss_clamp = 536;
251 tcp_set_state(sk, TCP_SYN_SENT);
这同样是一个很长的函数。传入的参数不再说了,前面已经说过。
函数开头定义的变量中,inet为socket在INET层中的表示,tp是socket在TCP层中的表示。rt是路由表在内核中的表示。IPV4中我们用4个数字来表示IP地址,每个数字范围为0-255,所以可以用一个32位数字来表示IP地址,所以有daddr, nexthop的字义。
179-191 每个函数例行的参数检查。nexthop和daddr被初始化为传入的IP地址。
192 想要连接必须知道远方的服务器是否是活动状态的,调用ip_route_connect函数。
这个函数的调用关系过于复杂,在学习IP协议时再展开。返回的结果记录在rt指针中,也就是找到或者新建了一个路由表实例。
196-200 如果函数返回的非0,说明网络连接有问题,就不尝试了,记录相关的信息,返回不可达错误。
202-205 如果得到的路由表的标识是广播或者多播类型,那么也有问题。因为,TCP是一对一的连接。
207-208 这个不太明白。应该是如果inet中的IP选项没有设备,则设置目的地址为查找到的路由表的目的地址。这种情况可能出现在路由重定向中。
210-212 设置inet的源地址。如果没有初始化的话,就用rt中的源地址。设置接收的源地址为inet的源地址。
214-219 如果现在的tp中接收选项中的时间戳不为0,而且发生了重定向,则重置接收的时间戳。
221-235 根据内核的注释知道,这里是针对处于 TIME-WAIT状态的TCP连接,同时要启动新的连接,新的连接和当前的TCP连接具有相同的hash参数,即TCP连接中的4元组是相同的。如果是这样的话,新建立的连接要从即将关闭的连接那里,继承一些时间戳。
237-238 初始化inet中的目的商品和目的地址,注意到这里的区别,dport是usin->sin_port来初始化的,而inet->daddr是用daddr来初始化的。回头再看前面的代码,发现基本上都是针对daddr开展的。因为前面都是在IP层处理的,而IP层是不使用端口号的。所以这里是用传入的商品来初始化当前inet实例的目的商品。
240-242 初始化sk的对应的inet_connection_sock的icsk_ext_hdr_len。这里根据成员变量的名称大概可以猜到是关于inet_connection相关的控制协议的头部长度。
244 设置tp的rx_opt成员中的mss_clamp。clamp是夹子的意思,那么这里代表接收队列的最大报文长度,是用于滑动窗口协议中的一个参数。
251 把sk的状态设置为TCP_SYN_SENT。虽然还没有对外发出SYN包,但还是提前设置了TCP的状态为TCP_SYN_SENT。这里给出的解释是,由于在此之后还要继续初始化,为了避免此sk被系统释放掉,要改下它的状态。我们可以这样理解,TCP有一个守护里程,会定期清理那些处于TCP_CLOSE状态的连接。
OK这次先写这么多。