• C++后端开发(2.2.3)——POSIXAPI解析


    1.网络通信

    1.消息传递(管道、FIFO、消息队列)
    2.同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
    3.共享内存(匿名的和具名的)

    使用TCP/IP协议 通过socket完成

    2.posix API

    目的:实现不同系统上的源代码的可移植性。
    举例:linux和windows都要实现基本的posix标准,linux把fork函数封装成posix_fork(随便说的),windows把creatprocess函数也封装成posix_fork,都声明在unistd.h里。这样,程序员编写普通应用时候,只用包含unistd.h,调用

    3.POSIX网络API

    在这里插入图片描述

    4.函数内部过程解析

    4.1 socket套接字创建

    int socket(int domain, int type, int protocol);
    //参数分别是地址族、 套接字类型和协议
    //AF_INET  SOCK_STREAM 可默认为0
    //IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等
    
    • 1
    • 2
    • 3
    • 4

    4.2 bind 绑定端口

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    • 1

    sockfd:即socket描述字
    addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。

    
    
    struct sockaddr_in server_addr;
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0 监听时,监听所有的地址
    server_addr.sin_port = htons(port);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    `

    struct sockaddr_in {
        sa_family_t    sin_family; /* address family: AF_INET  协议族*/
        in_port_t      sin_port;   /* port in network byte order  端口号*/
        struct in_addr sin_addr;   /* internet address  IP地址*/
    };
    
    /* Internet address. */
    struct in_addr {
        uint32_t       s_addr;     /* address in network byte order */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通常服务器在启动的时候都会绑定一个众所周知的地址(如ip地址+端口号),用于提供服务,客户就可以通过它来接连服务器;而客户端就不用指定,有系统自动分配一个端口号和自身的ip地址组合。这就是为什么通常服务器端在listen之前会调用bind(),而客户端就不会调用,而是在connect()时由系统随机生成一个。但是我认为客户端也可以绑定。

    4.3 网络字节序和主机字节序

    小端:小字节放前面,大字节放后面
    大端:大字节放前面,小字节放后面
    转换端口
    uint16_t htons(uint16_t hostshort); //主机字节序->网络字节序
    uint16_t ntohs(uint16_t netshort); //网络字节序->主机字节序
    
    转IP
     htonl(uint32_t hostlong);//主机字节序->网络字节序
     ntohl(uint32_t netlong);//网络字节序->主机字节序
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    4.4 listen 监听fd

    int listen(fd,size) // 无错返回0,错误返回-1
    
    • 1

    服务端调用listen()后,开始监听网络上发送给socket的连接请求。
    也就是说,开始接收请求了。

    4.5 connect发起连接请求

    int connect(int sockfd, 
    const struct sockaddr *serv_addr,
    int socklen_t addrlen);
    
    
    • 1
    • 2
    • 3
    • 4

    4.6 accept() 接收请求,建立连接

    accept()函数只做两件事,将连接请求从全连接队列中取出,给该连接分配一个fd并返回。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    • 1

    4.7 消息的发送和接收

    read()/write()
    recv()/send()
    readv()/writev()
    recvmsg()/sendmsg()
    recvfrom()/sendto()

    #include 
    
    ssize_t read(int fd, void *buf, size_t count); // 目的fd 消息 消息长度
    ssize_t write(int fd, const void *buf, size_t count);
    
    #include 
    #include 
    
    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                   const struct sockaddr *dest_addr, socklen_t addrlen);
    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                     struct sockaddr *src_addr, socklen_t *addrlen);
    
    ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
    ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    4.8 粘包问题

    2个数据包同时被提出,但是由于数据是在一起的,没有办法分离
    解决方法:

    1. 在包头添加一个数据包长度的字段,标明长度来确定数据包
    2. 在包结束后添加分割符,这里注意分割符要选择不经常使用的
      注意: 1优于2,因为,添加分割符,需要遍历整个消息来找到分隔符,这样大大影响效率,但是2可以结合1使用。

    4.9 close

    #include 
    int close(int fd);
    
    • 1
    • 2

    close操作只是使相应socket描述字的引用计数-1,只有当引用计数为0的时候,才会触发TCP客户端向服务器发送终止连接请求。

    4.9.1 过程分析

    1.正常情况下一方调用close情况如下图:
    在这里插入图片描述
    2.当双方同时调用close,如下图:
    在这里插入图片描述
    当同时发送close时,两边同时发送fin 和ack 这时候调用time_wait等待消息发送完毕。

    1. Fin_wait_1作用?
      等待对方回复,超时自动重发fin。
    2. Fin_wait_2作用?
      等待对方业务逻辑处理后,发送fin包。这里有可能出现死等待的情况服务器如果出现大量的Fin_wait_2可能需要考虑是不是没有close,或者close之前做了耗时操作。
    3. time_wait 作用?
      防止最后一个ACK没有顺利到达对方,超时重新发送ack。time_wait时常一般是120s可以修改。
      4.服务器掉线重启出现端口被占用怎么办?
      其实主要是由于还处于time_wait状态,端口并没有真正释放。这时候可以设置SO_REUSEADDR属性,保证掉线能马上重连。
  • 相关阅读:
    C++异常
    昆仑芯 AI 加速卡 R200 与龙蜥操作系统完成产品兼容认证
    转换类的具体使用教程
    kubeadm + containerd 部署 k8s-v1.23.3(含证书升级)
    分享一些我的远程办公经验
    Java的虚拟线程和结构化并发,含完整示例代码
    B2B营销新策略 | B2B企业如何实现产品导向增长目标(附方案下载)
    多级缓存之实现多级缓存
    程序编译过程(扫盲贴)
    软考-系统架构-2023-反思
  • 原文地址:https://blog.csdn.net/qq_42265608/article/details/127810657