在实验环境中,我们试着在172.16.48.2上写了一个最为简单的TCP客户端程序,试图连接172.16.48.1上的5002端口,发出TCP
三次握手的第一个请求数据报。但是172.16.48.1并没有在该端口上侦听的socket,所以这个连接尝试注定是要失败的。下面是客户端程序:
#include
#include
#include
#include
#include "my_inet.h"
#include
int main(void)
{
int fd, n, r;
struct sockaddr_in srv, cli;
srv.sin_family = MY_AF_INET;
srv.sin_port = htons(5002);
inet_aton("172.16.48.1", &srv.sin_addr);
if( (fd = socket( MY_AF_INET, SOCK_STREAM, MY_IPPROTO_TCP) ) < 0 ){
perror("socket");
return -1;
}
if( connect(fd, (struct sockaddr *)&srv, sizeof(srv)) < 0 ){
perror("connect");
return -1;
}
close(fd);
}
发出的第一个请求数据报前面已经介绍过,主要是首部标志syn=1表示新发起一个连接,并通过TCP选项通报几个选项设置,下面是来自172.16.48.1的回应数据报(已剥去以太网首部和IP首部):
数据报内容 含义
13 8a 16位源端口(5002)
80 0c 16位目的端口(32780)
00 00 00 00 32位序号
00 00 07 bd 32位确认序号
5 首部长度(不带选项,5*4=20字节)
0 14 标志位,FIN=0, SYN=0, RST=1, PSH=0, ACK=1,URG=0
00 00 16位窗口大小(为0,表示不能接收数据)
5c 59 16位检验和
00 00 16位紧急指针
先来看序号和确认序号,该回应数据报的初始序号(ISN)为0,确认序号表示发送确认的一端所期望收到的下一个序号,即172.16.48.1期望
172.16.48.2如果还要发数据报过来的话,其序号应该是0x07bd(实际上已经不会再发过来了),因为172.16.48.2发送的第一个请求
数据报随机选择的ISN为0x07bc,并且SYN=1,而SYN标志也要消耗一个序号,所以,172.16.48.1期望收到的下一个数据报的序号为
0x07bd。
这是一个没有任何数据内容,也没有TCP选项,只有一个纯TCP首部的回应数据报,其首部长度为20字节。
标志位RST=1表示希望重建连接,即本次连接无效(因为172.16.48.1上没有socket在端口5002上侦听)。ACK=1表示确认序号有效,即0x07bd是一个有效的确认序号。
窗口大小为零,表示从确认序号0x07bd开始,172.16.48.1希望接收到0字节数据(即不希望再收到来自172.16.48.2的数据报)。
显然,这是一个拒绝连接请求的回应数据报,它通过TCP首部已经能够清晰表达所有内容,所以不需要负载数据。
下面,我们看看,客户端(172.16.48.2)收到这个TCP回应数据报后,会作出什么样的处理。
因为我们这个简单的客户端并没有设置异步模式,所以,connect系统调用走到函数myinet_stream_connect,发出连接请求数据报
后,调用myinet_wait_for_connect函数,在struct
sock->sk_sleep队列上进入睡眠状态,等待对端的回应数据报。
TCP属于传输层协议,回应数据报在数据链路层被剥去以太网首部,在网络层被剥去IP首部后。进入mytcp_v4_rcv函数,进行传输层的处理,此时
的TCP包只有一个TCP首部,并无其它数据。mytcp_v4_rcv首先进行一些常规的TCP首部及数据校验的正确性检查。
结构体struct sk_buff有一个成员cb,它总共有48字节长,可以被各层协议自由使用,在TCP的mytcp_v4_rcv的处理中,这个成员中放了一个结构体struct tcp_skb_cb:
struct tcp_skb_cb{
union{
struct inet_skb_parm h4;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
struct inet6_skb_parm h6;
#endif
}header;
__u32 seq;
__u32 end_seq;
__u32 when;
__u8 flags;
__u8 sacked;
__u16 urg_ptr;
__u32 ack_seq;
};
它保存的是TCP首部中的一些重要信息,seq是数据报的序号,end_seq是数据报的结束序号,即end_seq-seq为该TCP数据报的有效负载长度,我们的实验环境中的这个回应包的有效负载长度是0(因为只有一个TCP首部)。
when被置0,flags记录IP首部中的服务类型(tos),sacked被置0,urg_ptr是紧急指针。
接下来,根据IP首部中的源和目的地址,还有TCP首部中的源和目的端口,从mytcp_hashinfo的ehash哈希表中找到要接收这个数据的
TCP socket(因为该socket不处于listening状态,所以不从哈希表listening_hash中取)。
然后,因为这个socket当前处于TCP_SYN_SENT(发送初始化连接请求),所以我们要检查回应包的TCP首部的标志位,以确定对端是否允许连
接。首先确认ACK=1,即对方接收了请求数据,并正确处理了,然后检查到RST=1,即请求被对方拒绝,调用mytcp_reset复位本地TCP
socket。
mytcp_reset首先置struct
sock->sk_err为ECONNREFUSED,然后调用struct
sock->sk_error_report成员函数唤醒睡眠的进程,最后,调用tcp_done置struct
sock->sk_state为TCP_CLOSE。
前面讲到,调用connect的进程进入睡眠,此时被唤醒,继续执行。它发现目前的socket状态为TCP_CLOSE,并不是自己所预期的,所以断定
出现了异常,从struct sock->sk_err中取出错误号,并使struct
socket->state=SS_UNCONNECTED,返回。最后,我们的应用程序得到错误信息: Connection refused。
阅读(305) | 评论(0) | 转发(0) |