• TCP连接


    1. 创建Socket

    1. #include<sys/types.h>
    2. #include<sys/socket.h>
    3. int sock = ::socket(PF_INET, SOCK_STREAM, 0);

    原型:int socket(int domain, int type, int protocol);

    domain: 协议族,可以是PF_INET,PF_INET6,PF_UNIX

    type: socke类型,可以是SOCK_STREAM,SOCK_DGRAM,SOCK_RAW,可以或(|)上SOCK_NONBLOCK(非阻塞), SOCK_CLOEXEC(close on exec: fork的子进程在执行exec的时候自动关闭已经打开的sock)

            其中:SOCK_STREAM:传输层,TCP

                       SOCK_DGRAM:传输层,UDP

                       SOCK_RAW:网络层(处理ICMP、IGMP等网络报文、特殊的IPv4报文、可以通过IP_HDRINCL套接字选项由用户构造IP头)

    protocol: 在对应domain中对应的协议类型,通常为一一对应,设为0

    参考:

    SOCK_RAW 与 SOCK_STREAM 、SOCK_DGRAM 的区别_zhu2695的博客-CSDN博客

    2. 设置socket的options

    1. struct linger opt = {1, 1};
    2. setsockopt(sock, SOL_SOCKET, SO_LINGER, (void *)&opt, sizeof(opt));

    原型:int setsockopt(int sockfd, int level, int optname,
                                      const void *optval, socklen_t optlen);

    sockfd:创建的fd

    level:需要设置option所在的协议层,可以是SOL_SOCKET(套接字选项),IPPROTO_IP(IP选项),IPPROTO_TCP(TCP选项), IPPROTO_UDP(UDP选项)

               其中一些SOL_SOCKET选项有:man 7 socket

                       SO_REUSEADDR: bind()时,即使需要绑定的地址被其他处于TIME_WAIT状态的socket占用,也可以立刻重用(也可以改/proc/sys/net/ipv4/tcp_tw_recycle来快速回收TIME_WAIT的socket,不建议)

                       SO_KEEPALIVE: tcp keepalive,设置后,如果tcp连接在7200s内没有数据交互,会触发tcp keepalive机制,(通常服务器端)会给对端主机发送keep-alive probe的tcp数据包(包含1个字节或者不含数据):

                            如果对方回复ack,则再次等7200s发送keep-alive;

                            如果对方崩溃以重启会回复RST,此时socket待处理错误被置为ECONNRESET,socket本身则被关闭;

                            如果对方没有回复,会每隔75s发送一次,发送9次,如果仍然没有应答,会断掉tcp连接,socket待处理错误被置为ETIMEOUT并且被关闭,如ICMP错误是“host unreachable(主机不可达)”,说明对方主机并没有崩溃,但是不可达,这种情况下待处理错误被置为 EHOSTUNREACH。

                      SO_LINGER: 控制socket在close或shutdown时,是否需要在缓冲区的数据发送出去后再返回

                            默认情况,在调用close()后,close会立即返回,内核的TCP模块会负责把socket对应缓冲区的数据发给对方。

                            如果l_onoff为0,则SO_LINGER不起作用,close用默认的行为关闭socket

                            如果l_onoff为1,l_linger为0,则close立即返回,TCP模块会丢弃发送缓冲区的数据,并且给对方发一个RST报文(可避免TIME_WAIT,但是可能会丢失数据)

                            如果l_onoff为1,l_linger大于0:

                                    如果close是阻塞的,close会等待l_linger的时间,直到TCP模块把缓冲区的数据发完并收到ACK,如果在l_linger时间内,没有发送并确认完,close会返回-1,并且errno置为EWOULDBLOCK。

                                    如果close是非阻塞的,close立即返回并且丢弃缓冲区数据。

    1. struct linger {
    2. int l_onoff; /* linger active */
    3. int l_linger; /* how many seconds to linger for */
    4. };

                    SO_OOBINLINE:在常规数据流中接收带外数据

                    SO_SNDBUF:发送缓冲区大小(udp没用,因为直接发到网络中去了)

                    SO_RCVBUF:接收缓冲区大小(socket级别,所以可以tcp/udp)

                    SO_SNDLOWAT:发送缓冲区的低水平标记位 (performance优化措施,防止cpu一直占用)

                                                   只对non-blocking的socket起作用,对于non-blocking的socket,send只能发送全部的数据或者SO_SNDLOWAT以上的数据(如果SO_SNDLOWAT为100,如果发送50字节,send会成功发送50字节或者返回失败,如果发送200字节,send可能成功发送100到200字节之间大小的数据,或者返回失败。默认SO_SNDLOWAT为1,send能发送1字节或者返回EWOULDBLOCK)

                                                    如果socket是block的,如果send不能发送全部的数据,会block,一直到有足够的缓冲区空间或者timeout(SO_SNDTIMEO)。

                                                    如果是select/poll/epoll,只有在缓冲区至少SO_SNDLOWAT的数据可写之后,才会返回socket可写。

                    SO_RCVLOWAT:接收缓冲区的低水平标记位

                                                   (非阻塞如何?值得验证) 对于阻塞recv会返回request的字节数或者超过SO_RCVLOWAT字节的数据

                                                    select/poll/epoll,只有在缓冲区至少有SO_RCVLOWAT的数据可读之后,才会返回socket可读

                    SO_RCVTIMEO:接收数据的超时时间。如果设置了该选项,recv/recvfrom函数会有一个block的时限,如果超过该时间就仍没有接收到更多的数据,会返回部分数据或者没有收到数据,返回失败(errno置为EWOULDBLOCK或EAGAIN),默认值为0(一直不会超时)。使用的结构体为struct timeval

    1. #include <sys/time.h>
    2. struct timeval {
    3. time_t tv_sec; /* seconds */
    4. suseconds_t tv_usec; /* microseconds */
    5. };

                    SO_SNDTIMEO:发送数据的超时时间。类似SO_RCVTIMEO

    optname:上面提到的SO_SOCKET选项

    optval:选项对应的结构体,通常为int/bool

                 SO_LINGER/SO_SNDTIMEO/SO_RCVTIMEO分别有专门的结构体

    optlen:optval的字节大小

    参考:

    setsockopt

    Network Sockets and Communication (Guile Reference Manual)

    setsockopt 详解_张忠琳的博客-CSDN博客_linux setsockopt

    10.2. Changing Network Kernel Settings Red Hat Enterprise Linux 5 | Red Hat Customer Portal

    linux - What's the purpose of the socket option SO_SNDLOWAT - Stack Overflow

    Related:

    更改TCP keep-alive时间间隔:

    TCP keepalive的详解(解惑) - 翔云123456 - 博客园

    方法1 - 整个系统范围:

            7200秒, 75秒, 9次这3个值可以通过更改这几个值         

    1. /proc/sys/net/ipv4/tcp_keepalive_time
    2. /proc/sys/net/ipv4/tcp_keepalive_intvl
    3. /proc/sys/net/ipv4/tcp_keepalive_probes

            或者在Linux中我们可以通过修改 /etc/sysctl.conf 的全局配置, 添加完下面的配置后输入 sysctl -p 使其生效,并使用命令sysctl -a | grep keepalive 来查看当前的默认配置

    1. net.ipv4.tcp_keepalive_time=7200
    2. net.ipv4.tcp_keepalive_intvl=75
    3. net.ipv4.tcp_keepalive_probes=9

    方法2 - TCP单个连接的:man 7 tcp

            使用tcp options:TCP_KEEPCNTTCP_KEEPIDLETCP_KEEPINTVL

    1. #include <sys/socket.h>
    2. #include <netinet/in.h>
    3. #include <netinet/tcp.h>
    4. int keepalive = 1;
    5. int count = 5;
    6. setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive, sizeof(keepalive));
    7. setsockopt(sock, IPPROTO_TCP, TCP_KEEPCNT, (void *)&count, sizeof(count));

    TCP keep-alive与HTTP keep-alive的区别:

    面试官:TCP Keepalive 和 HTTP Keep-Alive 是一个东西吗?_小林coding的技术博客_51CTO博客

    两者实现在不同的层次,作用也不同:

            TCP keep-alive是TCP层(内核态)实现的,称为 TCP 保活机制,用于确保TCP连接的有效性,检测连接错误;

            HTTP keep-alive是在应用层(用户态)实现的,称为 HTTP 长连接,用于HTTP连接复用,同一个连接上串行方式传递请求-响应数据,减少HTTP短连接需要多次建立/释放TCP连接消耗的资源;

    有TCP keep-alive,仍需要应用层的心跳包的原因:

    ​​​​​​TCP中已有SO_KEEPALIVE选项,为什么还要在应用层加入心跳包机制?? - 知乎

    1. tcp keepalive只能检测连接是否存活,不能检测连接是否可用。比如服务器因为负载过高导致无法响应请求但是连接仍然存在,或者出现进程死锁或阻塞,此时keepalive可以正常收发,但检测出连接出错。
    2. 客户端与服务器之间有四层代理或负载均衡,即在传输层之上的代理,只有传输层以上的数据才被转发,例如socks5等
    3. 如果TCP连接中的另一方因为停电突然断网,我们并不知道连接断开,此时发送数据失败会进行重传,由于重传包的优先级要高于keepalive的数据包,因此keepalive的数据包无法发送出去。只有在长时间的重传失败之后我们才能判断此连接断开了。

    TCP读写缓冲区大小:man tcp

    通过sysctl -a | grep tcp_rmem 和 tcp_wmem

    1. net.ipv4.tcp_rmem = 4096 131072 6291456
    2. net.ipv4.tcp_wmem = 4096 16384 4194304

    或者cat /proc/sys/net/ipv4/tcp_rmem cat /proc/sys/net/ipv4/tcp_wmem

    1. # cat /proc/sys/net/ipv4/tcp_rmem
    2. 4096 131072 6291456
    3. # cat /proc/sys/net/ipv4/tcp_wmem
    4. 4096 16384 4194304

    这三个数值左中右分别是系统中tcp接收和发送缓冲区的最小值,默认值,最大值。用getsockopt可以得到默认值。

    1. int wmem = 0;
    2. socklen_t wmem_len = sizeof(wmem);
    3. getsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&wmem, &wmem_len);

    可以通过设置SO_SNDBUF/SO_RCVBUF值设置单个socket的tcp发送/接收缓冲区。验证后,在一定范围内,系统会将设置值加倍,比如设置10240后,通过getsockopt得到的为20480

    1. int wmem = 10240;
    2. setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (void *)&wmem, sizeof(wmem));

    UDP的读缓冲区,没有写缓冲区(因为没用): 

    通过sysctl -a | grep rmem

    1. net.core.rmem_default = 212992
    2. net.core.rmem_max = 212992

    可以通过设置SO_RCVBUF值设置单个udp socket(SOCK_DGRAM)的接收缓冲区,和tcp缓冲区一样,系统会将设置值加倍

    1. int rmem = 10240;
    2. socklen_t rmem_len = sizeof(rmem);
    3. setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rmem, sizeof(rmem));
    4. getsockopt(sock, SOL_SOCKET, SO_RCVBUF, (void *)&rmem, &rmem_len);

    SO_SNDBUF/SO_RCVBUF与滑动窗口的关系:

    SO_SNDBUF和SO_RCVBUF_库昊天的博客-CSDN博客_so_rcvbuf 设置上限

    TCP滑动窗口(发送窗口和接受窗口) - hongdada - 博客园

    【大厂面试必备系列】滑动窗口协议 - 飞天小牛肉 - 博客园

    TCP/IP学习笔记:TCP拥塞控制 - 明明1109 - 博客园

    3. socket绑定地址(命名socket)

    几种不同的地址结构:

    1. 通用的socket地址

    1. //总长度16字节
    2. struct sockaddr  {
    3. sa_family_t sa_family; // unsigned short int,16bit,2字节,AF_xxx
    4. char data[14];
    5. };
    6. //总长度128字节
    7. struct sockaddr_storage {
    8. sa_family_t sa_family;
    9. char __ss_padding[_SS_PADSIZE];
    10. __ss_aligntype __ss_align; /* Force desired alignment. */
    11. };

    2. 专用的socket地址 

    1. // IP协议族 IPv4
    2. // 总长度16字节
    3. struct sockaddr_in {
    4. sa_family_t sin_family; // AF_INET
    5. uint16_t sin_port; // e.g. htons(3490)
    6. struct in_addr sin_addr;
    7. /* Pad to size of `struct sockaddr'. 8 字节*/
    8. unsigned char sin_zero[sizeof (struct sockaddr)
    9. - sizeof (sa_family_t)
    10. - sizeof (uint16_t)
    11. - sizeof (struct in_addr)];
    12. };
    13. struct in_addr {
    14. uint32_t s_addr;
    15. };
    16. // IP协议族 IPv6
    17. // 总长度28字节
    18. struct sockaddr_in6 {
    19. sa_family_t sin6_family; // AF_INET6
    20. uint16_t sin6_port; /* Transport layer port # */
    21. uint32_t sin6_flowinfo; /* IPv6 flow information */
    22. struct in6_addr sin6_addr; /* IPv6 address */
    23. uint32_t sin6_scope_id; /* IPv6 scope-id */
    24. };
    25. struct in6_addr {
    26. union {
    27. uint8_t __u6_addr8[16];
    28. uint16_t __u6_addr16[8];
    29. uint32_t __u6_addr32[4];
    30. } __in6_u;
    31. };
    32. //UNIX本地协议族
    33. struct sockaddr_un {
    34. sa_family_t sun_family; // AF_LOCAL/AF_UNIX
    35. char sun_path[108];
    36. };

     

    PF_INET: 协议族,AF_INET:地址族

    illumos: manual page: sockaddr_storage.3socket

    Implementing AF-independent application

    recvfrom 辅助数据

    recvmsg(2) - OpenBSD manual pages

     UNIX网络编程读书笔记:辅助数据 - ITtecman - 博客园

    unix(7) - Linux manual page

    Linux msghdr结构讲解 | Ivanzz


     

  • 相关阅读:
    QMap之自定义Key和Value
    周四见|物流人的一周资讯
    Python集合-set使用
    C练题笔记之:Leetcode-137. 只出现一次的数字 II
    JSON 数据类型转换工具
    Hubble数据库再获得国家级重点项目推荐,作为HTAP国产数据库入选工信部全国试点
    深入浅出Java多线程(十一):AQS
    递归-深度优先遍历-99. 恢复二叉搜索树
    kali安装LANMP出现错误求解
    Docker深入讲解
  • 原文地址:https://blog.csdn.net/iamanda/article/details/125223900