免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
12下一页
最近访问板块 发新帖
查看: 5244 | 回复: 14
打印 上一主题 下一主题

[网络子系统] tcp_rcv_synsent_state_process函数的问题【已解决】 [复制链接]

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2014-02-17 22:14 |只看该作者 |倒序浏览
本帖最后由 jiufei19 于 2014-02-19 22:47 编辑

linux V2.6.23版本,3次握手tcp客户端收到SYN+ACK后,调用tcp_rcv_synsent_state_process函数进行处理,如下所示

tcp_rcv_synsent_state_process(...)
{
    ...
    if (sk->sk_write_pending ||
              icsk->icsk_accept_queue.rskq_defer_accept ||
              icsk->icsk_ack.pingpong)
{
              /* Save one ACK. Data will be ready after
               * several ticks, if write_pending is set.
               *
               * It may be deleted, but with this feature tcpdumps
               * look so _wonderfully_ clever, that I was not able
               * to stand against the temptation      --ANK
               */      
              inet_csk_schedule_ack(sk);
              icsk->icsk_ack.lrcvtime = tcp_time_stamp;
              icsk->icsk_ack.ato   = TCP_ATO_MIN;
              tcp_incr_quickack(sk);
              tcp_enter_quickack_mode(sk);
              inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
                            TCP_DELACK_MAX, TCP_RTO_MAX);
             ...
   
}

我的问题是,上述红色代码中if判断的上下文场景是怎样的?或者说什么情况会导致上述if条件成立?
我搜索了代码,发现tcp_rcv_synsent_state_process这个函数只会在3次握手过程中被调用,那么
1、sk->sk_write_pending怎么可能会有不为0的可能呢?
2、rskq_defer_accept这个设置通常是在TCP服务端通过setsocketopt进行设置,那么怎会出现在这里的客户端代码中呢?
3、pingpong模式可以在客户端和服务器端通过setsocketopt进行设置,这个似乎还好理解
4、上述代码中的注释表明了这种写法有利于tcpdumps,这是什么意思?

希望大家帮助,谢谢!

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
2 [报告]
发表于 2014-02-18 10:09 |只看该作者
本帖最后由 jiufei19 于 2014-02-18 10:11 编辑

作为TCP的客户端,sk->sk_write_pending不为0的情况是否发生于非阻塞模式?即当connect发出后,客户端直接发出数据,导致调用tcp_sendmsg之类的函数被执行,从而在发送应用数据的同时,将最后一个ACK发给了服务器。

这是我的一个猜想,不知是否能部分解释我的这几个问题?换句话讲,这个问题的实质就是将ACK延迟发送,直到真正有数据发送时才一起捎带发出。

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
3 [报告]
发表于 2014-02-18 10:23 |只看该作者
为了验证我刚才说的问题,我查了下代码,如下

   668 int tcp_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg,
   669         size_t size)
   670 {
   671     struct sock *sk = sock->sk;
   672     struct iovec *iov;
   673     struct tcp_sock *tp = tcp_sk(sk);
   674     struct sk_buff *skb;
   675     int iovlen, flags;
   676     int mss_now, size_goal;
   677     int err, copied;
   678     long timeo;
   679
   680     lock_sock(sk);
   681     TCP_CHECK_TIMER(sk);
   682
   683     flags = msg->msg_flags;
   684     timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT);
   685
   686     /* Wait for a connection to finish. */
   687     if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
   688         if ((err = sk_stream_wait_connect(sk, &timeo)) != 0)
   689             goto out_err;

   而
   51 int sk_stream_wait_connect(struct sock *sk, long *timeo_p)
   52 {
   53     struct task_struct *tsk = current;
   54     DEFINE_WAIT(wait);
   55     int done;
   56
   57     do {
   58         int err = sock_error(sk);
   59         if (err)
   60             return err;
   61         if ((1 << sk->sk_state) & ~(TCPF_SYN_SENT | TCPF_SYN_RECV))
   62             return -EPIPE;
   63         if (!*timeo_p)
   64             return -EAGAIN;
   65         if (signal_pending(tsk))
   66             return sock_intr_errno(*timeo_p);
   67
   68         prepare_to_wait(sk->sk_sleep, &wait, TASK_INTERRUPTIBLE);
   69         sk->sk_write_pending++;
              。。。

显然,当第3个ACK没有发回时,客户端将不处于ESTABLISHED状态,于是,将调用红色代码sk_stream_wait_connect,于是在客户端休眠之前,置sk->sk_write_pending为1,这就基本解释了我之前的第1个疑问,我的解释正确吗?

但是第2个问题我还没有找到答案

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
4 [报告]
发表于 2014-02-18 13:35 |只看该作者
回复 1# jiufei19
1、sk->sk_write_pending怎么可能会有不为0的可能呢?
2、rskq_defer_accept这个设置通常是在TCP服务端通过setsocketopt进行设置,那么怎会出现在这里的客户端代码中呢?
3、pingpong模式可以在客户端和服务器端通过setsocketopt进行设置,这个似乎还好理解


关于第一个,可能会在sk_stream_wait_connect处进行改变吧,没有验证。

关于第二个,存在client端先bind,然后再connect的情况。已经在本地通过systemtap验证

   

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
5 [报告]
发表于 2014-02-18 13:57 |只看该作者
回复 3# jiufei19
我早上9点打开这个帖子,一直没有回复。回复后才发现已经有人回复了。
第一种情况,跟我猜测的一致。
出现的流程如下:

client 设置socket为nonblock, connect,之后直接调用send

send->tcp_sendmsg->sk_stream_wait_connect->sk_write_pending++.....(waiting)....
                                                                                                              这个时候,ack到来,调用tcp_rcv_synsent_state_process。满足sk_write_pending不为0的情况

   

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
6 [报告]
发表于 2014-02-18 15:00 |只看该作者
感谢斑竹的解答,但是我没有明白我的第2个问题斑竹是如何解答的,斑竹说的是:关于第二个,存在client端先bind,然后再connect的情况。已经在本地通过systemtap验证,我没有明白这个解释如何就解释了rskq_defer_accept在客户端被设置的场景?

请斑竹再不吝赐教

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
7 [报告]
发表于 2014-02-18 15:40 |只看该作者
本帖最后由 瀚海书香 于 2014-02-18 15:40 编辑

回复 6# jiufei19
感谢斑竹的解答,但是我没有明白我的第2个问题斑竹是如何解答的,斑竹说的是:关于第二个,存在client端先bind,然后再connect的情况。已经在本地通过systemtap验证,我没有明白这个解释如何就解释了rskq_defer_accept在客户端被设置的场景?


场景是client端先bind到一个端口和ip,setsockopt(TCP_DEFER_ACCEPT),然后connect server,这个时候就会出现rskq_defer_accept=1的情况。

我在本地通过systemtap,在内核打点的方式验证了这种情况下,当收到server发来的syn/ack的时候,这个时候icsk->icsk->icsk_accept_queue.rskq_defer_accept=1.

client测试代码:
  1. int main(int argc,char **argv)
  2. {
  3.         int listenfd,connfd,opt=1;
  4.         pid_t childpid;
  5.         int i;
  6.         socklen_t clilen;
  7.         struct sockaddr_in servaddr;
  8.         struct sockaddr_in realaddr;
  9.         if (argc != 4) {
  10.                 printf("usage: %s bindip connectip connectport\n", argv[0]);
  11.                 return 0;
  12.         }   
  13.         if ((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
  14.                 err_quit("socket error errno=%d", errno);
  15.         bzero(&servaddr,sizeof(servaddr));
  16.         servaddr.sin_family=AF_INET;
  17.         inet_aton(argv[1], &(servaddr.sin_addr));
  18.         servaddr.sin_port = htons(2014);
  19.         opt = 1;
  20.         setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

  21.         opt = 1;
  22.         setsockopt(listenfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &opt, sizeof(opt));
  23.         if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))==-1)
  24.                 printf("bind error errno=%d\n", errno);

  25.         bzero(&realaddr,sizeof(realaddr));
  26.         realaddr.sin_family=AF_INET;
  27.         realaddr.sin_port=htons(atoi(argv[3]));
  28.         inet_pton(AF_INET,argv[2],&realaddr.sin_addr);
  29.         if (connect(listenfd,(struct sockaddr*)&realaddr,sizeof(realaddr))==-1)
  30.                 printf("connect error errno=%d", errno);
  31.         while(1)
  32.                 sleep(10);
  33. }
复制代码
systemtap验证代码:
  1. %{
  2. #include <net/sock.h>
  3. #include <net/inet_connection_sock.h>
  4. %}

  5. function show_sock:long(skaddr:long)
  6. %{
  7.         struct sock *sk = (struct sock*)(THIS->skaddr);
  8.         struct inet_connection_sock *icsk = inet_csk(sk);
  9.         printk("tcp_defer_accept=%d sk_write_pending=%d\n",
  10.                         icsk->icsk_accept_queue.rskq_defer_accept,
  11.                         sk->sk_write_pending);
  12.         THIS->__retvalue = 1;
  13. %}

  14. probe kernel.function("tcp_rcv_synsent_state_process")
  15. {
  16.         printf("Called function tcp_rcv_synsent_state_process\n")
  17.         show_sock($sk);
  18. }
复制代码

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
8 [报告]
发表于 2014-02-18 16:10 |只看该作者
回复 7# 瀚海书香


    哦。斑竹说的这个方式的设置我能理解,我不能理解的是对于TCP客户端,这样设置有何作用,或者说什么样的应用场景才需要这样?
    而对于TCP服务端,这样设置defer模式的好处是显而易见的。关键就是客户端为啥这样做

论坛徽章:
6
金牛座
日期:2013-10-08 10:19:10技术图书徽章
日期:2013-10-14 16:24:09CU十二周年纪念徽章
日期:2013-10-24 15:41:34狮子座
日期:2013-11-24 19:26:19未羊
日期:2014-01-23 15:50:002015年亚洲杯之阿联酋
日期:2015-05-09 14:36:15
9 [报告]
发表于 2014-02-18 16:12 |只看该作者
回复 8# jiufei19
哦。斑竹说的这个方式的设置我能理解,我不能理解的是对于TCP客户端,这样设置有何作用,或者说什么样的应用场景才需要这样?
    而对于TCP服务端,这样设置defer模式的好处是显而易见的。关键就是客户端为啥这样做


客户端可以携带data一块ack,减少一次只进行ack的数据交互啊。

   

论坛徽章:
1
IT运维版块每日发帖之星
日期:2015-11-17 06:20:00
10 [报告]
发表于 2014-02-18 16:23 |只看该作者
本帖最后由 jiufei19 于 2014-02-18 16:28 编辑

回复 9# 瀚海书香


    恩,这样做当然可以减少一次纯ACK的发送,但是总觉的有点感觉奇怪。不过这里还有另外一个问题,再请斑竹解惑。

        if (sk->sk_write_pending ||
              icsk->icsk_accept_queue.rskq_defer_accept ||
              icsk->icsk_ack.pingpong) {
              /* Save one ACK. Data will be ready after
               * several ticks, if write_pending is set.
               *
               * It may be deleted, but with this feature tcpdumps
               * look so _wonderfully_ clever, that I was not able
               * to stand against the temptation      --ANK
               */
              inet_csk_schedule_ack(sk);
              icsk->icsk_ack.lrcvtime = tcp_time_stamp;
              icsk->icsk_ack.ato   = TCP_ATO_MIN;
             tcp_incr_quickack(sk);
              tcp_enter_quickack_mode(sk);

              。。。

上面两句红色代码实际是重叠的,即在tcp_enter_quickack_mode这个函数中又会再次执行一遍 tcp_incr_quickack,为啥不直接略掉tcp_incr_quickack,而直接执行后者不就可以了吗,作者为啥偏偏这样写?
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP