Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1333834
  • 博文数量: 284
  • 博客积分: 3251
  • 博客等级: 中校
  • 技术积分: 3046
  • 用 户 组: 普通用户
  • 注册时间: 2012-04-26 17:23
文章分类

全部博文(284)

文章存档

2019年(2)

2018年(5)

2015年(19)

2014年(13)

2013年(10)

2012年(235)

分类: LINUX

2012-12-21 16:49:37

 在实验环境中,我们试着在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。
阅读(674) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~