zoukankan      html  css  js  c++  java
  • udp connected socket

      由于UDP的无连接性,在实际client发送数据前,server是不知道四元组信息的,并且也不知道UDP通信的模式;对于类似QUIC这种 长连接 的流式数据传输而言,UDP只是承载数据的一个协议而已,以前udp主要是用于多播,目前使用UDP实现单播传输怎么处理? 当然也就可以专门创建一个socket,用connect来保持住这个 连接 。

       问题是,当有数据进入UDP处理的时候,内核协议栈如何查找到对应的socket呢?

    • 1、REUSEPORT的四元组hash模数取余机制会导致同一个四元组随着socket数量增减而对应到不同的socket,无法和线程,IO复用协同。详情看:

          udp: correct reuseport selection with connected sockets  

        关于udp connected socket https://lore.kernel.org/all/20190913011639.55895

      UDP reuseport groups can hold a mix unconnected and connected sockets. Ensure that connections only 
    receive all traffic to their 4-tuple.Fast reuseport returns on the first reuseport match on the assumption
    that all matches are equal.Only if connections are present, return to the previous behavior of scoring all sockets.
      Record if connections are present and if so
        (1) treat such connected sockets as an independent match from the group,
        (2) only return 2-tuple matches from reuseport and
        (3) do not return on the first 2-tuple reuseport match to allow for a higher scoring match later.
      New field has_conns is set without locks. No other fields in the bitmap are modified at runtime and the field is only ever

    set unconditionally, so an RMW cannot miss a change.
    • 2、如果不能用REUSEPORT的模数取余机制,所有socket只能二元组hash来组织。 所有的socket由于bind同一个地址和端口,将会位于同一个hash桶中。

    其实对于第一种方法:可以使用类似于tcp生成一个new_sock的来“connected” client 并且使用一个id或者使用TCP FTO 使用socket复用;

    看下 quic设计

    QUIC报文结构

    一个QUIC的基本单位为packet,也就是包。

    QUIC头部

    1、 Flags占8个字节,里面记录了报文的类型、ConnectionID的长度、Version的长度、Packet Number的长度。

    2、 Connection ID:一个QUIC连接对应着一个Connection ID,是一个QUIC 连接 的唯一标识。 QUIC 不用 源IP、源端口、目的IP、目的端口 四元组来为唯一标识一个连接,而是通过一个Connection ID,目的是为了连接转移。

    3、 Version : QUIC的版本号

    4、 Packet Number: 一个QUIC包的唯一标识,是递增的



    类似于TCP的方法:

    1. UDP svr创建UDP socket fd,设置socket为REUSEADDR和REUSEPORT、同时bind本地地址local_addr 
      1. listen_fd = socket(PF_INET, SOCK_DGRAM, 0)
        setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt,sizeof(opt))
        setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt))
        bind(listen_fd, (struct sockaddr * ) &local_addr, sizeof(struct sockaddr)
    2. 创建epoll fd,并将listen_fd放到epoll中 并监听其可读事件 
      1.   
        epoll_fd = epoll_create(1000);
        ep_event.events = EPOLLIN|EPOLLET;
        ep_event.data.fd = listen_fd;
        epoll_ctl(epoll_fd , EPOLL_CTL_ADD, listen_fd, &ep_event)
        in_fds = epoll_wait(epoll_fd, in_events, 1000, -1)
    3. epoll_wait返回时,如果epoll_wait返回的事件fd是listen_fd,调用recvfrom接收client第一个UDP包并根据recvfrom返回的client地址, 创建一个新的socket(new_fd)与之对应,设置new_fd为REUSEADDR和REUSEPORT、同时bind本地地址local_addr,然后connect上recvfrom返回的client地址
      1. recvfrom(listen_fd, buf, sizeof(buf), 0, (struct sockaddr )&client_addr, &client_len)
        new_fd = socket(PF_INET, SOCK_DGRAM, 0)
        setsockopt(new_fd , SOL_SOCKET, SO_REUSEADDR, &reuse,sizeof(reuse))
        setsockopt(new_fd , SOL_SOCKET, SO_REUSEPORT, &reuse, sizeof(reuse))
        bind(new_fd , (struct sockaddr ) &local_addr, sizeof(struct sockaddr));
        connect(new_fd , (struct sockaddr * ) &client_addr, sizeof(struct sockaddr)
    4. 将新创建的new_fd加入到epoll中并监听其可读等事件
      1.   
        client_ev.events = EPOLLIN;
        client_ev.data.fd = new_fd ;
        epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_fd , &client_ev) 
    5. 当epoll_wait返回时,如果epoll_wait返回的事件fd是new_fd 那么就可以调用recvfrom来接收特定client的UDP包了 
      1.   
        recvfrom(new_fd , recvbuf, sizeof(recvbuf), 0, (struct sockaddr * )&client_addr, &client_len) 
    •   client要使用固定的ip和端口和server端通信,也就是client需要bind本地local address。 如果client没有bind本地local address,那么在发送UDP数据包的时候,可能是不同的Port了,这样如果server 端的new_fd connect的是client的Port_CA端口,那么当Client的Port_CB端口的UDP数据包来到server时,内核不会投递到new_fd,相反是投递到listen_fd。由于需要bind和listen fd一样的IP地址和端口,因此SO_REUSEADDR和SO_REUSEPORT是必须的。 
    •   要小心处理上面步骤3中connect返回前,Client已经有多个UDP包到达Server端的情况。 如果server没处理好这个情况,在connect返回前,有2个UDP包到达server端了,这样server会new出两个new_fd1和new_fd2分别connect到client,那么后续的client的UDP到达server的时候,内核会投递UDP包给new_fd1和new_fd2中的一个
  • 相关阅读:
    Java学习第六篇:集合类
    Java学习第五篇:二进制(原码 反码 补码),位运算,移位运算,约瑟夫问题
    Java学习第四篇:数组,排序,查找
    Java学习第三篇:类的三大特征,抽象类,接口,final关键字
    Java学习第二篇:类,对象,成员属性,成员方法,构造方法,类变量,类方法
    Java学习第一篇:变量,数据类型,运算符,流程控制(简介)
    (转)学习使用Jmeter做压力测试(三)--数据库测试
    (转)学习使用Jmeter做压力测试(一)--压力测试基本概念
    Xcode-项目模板修改
    过程式编程 drawShapes
  • 原文地址:https://www.cnblogs.com/codestack/p/15600896.html
Copyright © 2011-2022 走看看