13.TCP通信状态转移,端口复用,IO多路转接,UDP通信 C++/Linux-2022-10-27

状态转移,端口复用,IO多路转接,UDP通信

6.状态转移
  • 三次握手:
    • SYN_SENT:client端第一次握手时发起端的状态,
    • LISTEN:server端未接受到握手时一直处于的状态
    • SYN_RCVD:第一次握手后server端的状态
    • ESTABLISHED:建立连接后client端和server端的状态
  • 四次挥手
    • FIN_WAIT_1:结束发起者的状态,第一次挥手
    • CLOSE_WAIT:接收到FIN_WAIT_1后的状态
    • LAST_ACK:发送ACK后的接收端的状态
    • FIN_WAIT_2:接收到接收端ACK信号的状态
    • TIME_WAIT:发送端接收到接收端发来的FIN信号的状态
  • 查看网络相关状态信息:
    • 命令:netstat
    • 参数:
      • -a:显示所有选项,默认不显示LISTEN相关
      • -p:显示建立相关链接的程序名
      • -n:拒绝显示别名,能显示数字的全部转化位数字
      • -t:仅显示tcp相关
      • -u:仅显示udp相关
      • -l:仅列出在listen的服务状态
7.端口复用
  • 用途:
    • 防止服务器重启时之前绑定的端口还未释放
    • 程序突然退出而系统没有释放端口
  • 设置方法
    • int opt = 1;
    • setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,(const void(*)&opt,sizeof(opt)));
  • 注意事项:绑定之前设置端口复用的属性
8.IO多路转接
  • 是什么:

    • 先构建一张有关文件描述符的列表,将要监听的文件描述符添加到该列表中
    • 然后调用一个函数,监听该表中的文件描述符,直到这些描述符表中的一个进行IO操作时,该函数返回、
      • 该函数为阻塞函数
      • 函数对文件描述符的检测操作是由内核完成的
    • 在返回时告诉进程有多少(哪些)描述符要进行IO操作
  • IO操作方式

    • 阻塞等待:不占用CPU的宝贵时间片,同一时间只能处理一个操作,效率低
    • 非阻塞等待,忙轮询:提高了效率,需要占用更多的cpu和系统资源
  • 解决办法:IO多路转接技术,select、poll,epoll

    • select

      • int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

        • nfds:要检测的文件描述符最大的fd+1 (或者直接用1024)
        • readfds:读集合
        • writefds:写集合
        • exceptfds:异常集合
        • timeout:
          • NULL:永久堵塞,检测到fd变化的时候返回
          • struct timeval a:
            • a.tv_sec = 10;
            • a.tv_sec = 0;
      • 文件描述符集类型:fd_set rdset;

      • 文件描述符操作函数:

        • 全部清空:
          • void FD_ZERO(fd_set *set);
        • 从集合中删除一项
          • void FD_CLR(int fd, fd_set *set);
        • 将文件描述符添加到集合
          • void FD_SET(int fd, fd_set *set);
        • 判断文件描述符是否在集合中
          • int FD_ISSET(int fd,fd_set *set);
      • 优缺点:

        • 优点:跨平台
        • 缺点:
          • 每次调用,需要把fd集合从用户态拷贝到内核态,开销很大
          • 需要在内核遍历传递进来所有的fd,开销也很大
          • 支持的文件描述符数量调小,默认是1024
      • code

        #include <stdio.h>
        #include <unistd.h>
        #include <stdlib.h>
        #include <sys/types.h>
        #include <string.h>
        #include <sys/socket.h>
        #include <arpa/inet.h>
        #include <ctype.h>
        
        
        int main(int argc, const char* argv[])
        {
            if(argc < 2)
            {
                printf("eg: ./a.out port\n");
                exit(1);
            }
            struct sockaddr_in serv_addr;
            socklen_t serv_len = sizeof(serv_addr);
            int port = atoi(argv[1]);
        
            // 创建套接字
            int lfd = socket(AF_INET, SOCK_STREAM, 0);
            // 初始化服务器 sockaddr_in 
            memset(&serv_addr, 0, serv_len);
            serv_addr.sin_family = AF_INET;                   // 地址族 
            serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
            serv_addr.sin_port = htons(port);            // 设置端口 
            // 绑定IP和端口
            bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
        
            // 设置同时监听的最大个数
            listen(lfd, 36);
            printf("Start accept ......\n");
        
            struct sockaddr_in client_addr;
            socklen_t cli_len = sizeof(client_addr);
        
            // 最大的文件描述符
            int maxfd = lfd;
            // 文件描述符读集合
            fd_set reads, temp;
            // init
            FD_ZERO(&reads);
            FD_SET(lfd, &reads);
        
            while(1)
            {
                // 委托内核做IO检测
                temp = reads;
                int ret = select(maxfd+1, &temp, NULL, NULL, NULL);
                if(ret == -1)
                {
                    perror("select error");
                    exit(1);
                }
                // 客户端发起了新的连接
                if(FD_ISSET(lfd, &temp))
                {
                    // 接受连接请求 - accept不阻塞
                    int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
                    if(cfd == -1)
                    {
                        perror("accept error");
                        exit(1);
                    }
                    char ip[64];
                    printf("new client IP: %s, Port: %d\n", 
                           inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
                           ntohs(client_addr.sin_port));
                    // 将cfd加入到待检测的读集合中 - 下一次就可以检测到了
                    FD_SET(cfd, &reads);
                    // 更新最大的文件描述符
                    maxfd = maxfd < cfd ? cfd : maxfd;
                }
                // 已经连接的客户端有数据到达
                for(int i=lfd+1; i<=maxfd; ++i)
                {
                    if(FD_ISSET(i, &temp))
                    {
                        char buf[1024] = {0};
                        int len = recv(i, buf, sizeof(buf), 0);
                        if(len == -1)
                        {
                            perror("recv error");
                            exit(1);
                        }
                        else if(len == 0)
                        {
                            printf("客户端已经断开了连接\n");
                            close(i);
                            // 从读集合中删除
                            FD_CLR(i, &reads);
                        }
                        else
                        {
                            printf("recv buf: %s\n", buf);
                            send(i, buf, strlen(buf)+1, 0);
                        }
                    }
                }
            }
        
            close(lfd);
            return 0;
        }
        
        
    • poll

      • int poll(struct pollfd *fd, nfds_t nfds, int timeout);

        • pollfd:数组的地址
        • nfds:数组的最大长度,数组中最后一个使用的元素下标+1
          • 内核会轮询检测fd数组的每个文件描述符
        • timeout:
          • -1:永久阻塞
          • 0:调用完立即返回
          • >0:等待的时间毫秒级
        • 返回值:IO发送变化的文件描述符个数
      • code

      #include <stdio.h>
      #include <unistd.h>
      #include <stdlib.h>
      #include <sys/types.h>
      #include <string.h>
      #include <sys/socket.h>
      #include <arpa/inet.h>
      #include <ctype.h>
      #include <poll.h>
      
      #define SERV_PORT 8989
      
      int main(int argc, const char* argv[])
      {
          int lfd, cfd;
          struct sockaddr_in serv_addr, clien_addr;
          int serv_len, clien_len;
      
          // 创建套接字
          lfd = socket(AF_INET, SOCK_STREAM, 0);
          // 初始化服务器 sockaddr_in 
          memset(&serv_addr, 0, sizeof(serv_addr));
          serv_addr.sin_family = AF_INET;                   // 地址族 
          serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
          serv_addr.sin_port = htons(SERV_PORT);            // 设置端口 
          serv_len = sizeof(serv_addr);
          // 绑定IP和端口
          bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
      
          // 设置同时监听的最大个数
          listen(lfd, 36);
          printf("Start accept ......\n");
      
          // poll结构体
          struct pollfd allfd[1024];
          int max_index = 0;
          // init
          for(int i=0; i<1024; ++i)
          {
              allfd[i].fd = -1;
          }
          allfd[0].fd = lfd;
      	allfd[0].events = POLLIN;
      
          while(1)
          {
              int i = 0;
              int ret = poll(allfd, max_index+1, -1); 
              if(ret == -1)
              {
                  perror("poll error");
                  exit(1);
              }
      
              // 判断是否有连接请求
              if(allfd[0].revents & POLLIN)
              {
                  clien_len = sizeof(clien_addr);
                  // 接受连接请求
                  int cfd = accept(lfd, (struct sockaddr*)&clien_addr, &clien_len);
                  printf("============\n");
      
                  // cfd添加到poll数组
                  for(i=0; i<1024; ++i)
                  {
                      if(allfd[i].fd == -1)
                      {
                          allfd[i].fd = cfd;
                          break;
                      }
                  }
                  // 更新最后一个元素的下标
                  max_index = max_index < i ? i : max_index;
              }
      
              // 遍历数组
              for(i=1; i<=max_index; ++i)
              {
                  int fd = allfd[i].fd;
                  if(fd == -1)
                  {
                      continue;
                  }
                  if(allfd[i].revents & POLLIN)
                  {
                      // 接受数据
                      char buf[1024] = {0};
                      int len = recv(fd, buf, sizeof(buf), 0);
                      if(len == -1)
                      {
                          perror("recv error");
                          exit(1);
                      }
                      else if(len == 0)
                      {
                          allfd[i].fd = -1;
                          close(fd);
                          printf("客户端已经主动断开连接。。。\n");
                      }
                      else
                      {
                          printf("recv buf = %s\n", buf);
                          for(int k=0; k<len; ++k)
                          {
                              buf[k] = toupper(buf[k]);
                          }
                          printf("buf toupper: %s\n", buf);
                          send(fd, buf, strlen(buf)+1, 0);
                      }
      
                  }
      
              }
          }
      
          close(lfd);
          return 0;
      }
      
      
    • epoll

      • 三个函数:
        • int epoll_create(int size);
          • 生成一个epoll专用的文件描述符
          • size:epoll上能关注的最大描述符,可以扩展
        • int epoll_ctl(int epdf, int op,int fd, struct epoll_event *event);
          • 用于控制某个epoll文件描述符事件,可以注册,修改,删除
          • epfd:epoll_create 生成的epoll专用描述符
          • op:
            • EPOLL_CTL_ADD --注册
            • EPOLL_CTL_MOD – 修改
            • EPOLL_CTL_DEL – 删除
          • fd:关联的文件描述符
          • event: 告诉内核要监听什么事件
        • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
          • 等待IO事件发生 - 可以设置阻塞的函数
          • epfd:要检测的句柄
          • events:用于回传待处理事件的数组
          • maxevents: 告诉内核这个events的大小
          • timeout: 为超时时间
            • -1:永久阻塞
            • 0:立即返回
            • >0

    code

    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    #include <ctype.h>
    #include <sys/epoll.h>
    
    
    int main(int argc, const char* argv[])
    {
        if(argc < 2)
        {
            printf("eg: ./a.out port\n");
            exit(1);
        }
        struct sockaddr_in serv_addr;
        socklen_t serv_len = sizeof(serv_addr);
        int port = atoi(argv[1]);
    
        // 创建套接字
        int lfd = socket(AF_INET, SOCK_STREAM, 0);
        // 初始化服务器 sockaddr_in 
        memset(&serv_addr, 0, serv_len);
        serv_addr.sin_family = AF_INET;                   // 地址族 
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);    // 监听本机所有的IP
        serv_addr.sin_port = htons(port);            // 设置端口 
        // 绑定IP和端口
        bind(lfd, (struct sockaddr*)&serv_addr, serv_len);
    
        // 设置同时监听的最大个数
        listen(lfd, 36);
        printf("Start accept ......\n");
    
        struct sockaddr_in client_addr;
        socklen_t cli_len = sizeof(client_addr);
    
        // 创建epoll树根节点
        int epfd = epoll_create(2000);
        // 初始化epoll树
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = lfd;
        epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    
        struct epoll_event all[2000];
        while(1)
        {
            // 使用epoll通知内核fd 文件IO检测
            int ret = epoll_wait(epfd, all, sizeof(all)/sizeof(all[0]), -1);
    
            // 遍历all数组中的前ret个元素
            for(int i=0; i<ret; ++i)
            {
                int fd = all[i].data.fd;
                // 判断是否有新连接
                if(fd == lfd)
                {
                    // 接受连接请求
                    int cfd = accept(lfd, (struct sockaddr*)&client_addr, &cli_len);
                    if(cfd == -1)
                    {
                        perror("accept error");
                        exit(1);
                    }
                    // 将新得到的cfd挂到树上
                    struct epoll_event temp;
                    temp.events = EPOLLIN;
                    temp.data.fd = cfd;
                    epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &temp);
                    
                    // 打印客户端信息
                    char ip[64] = {0};
                    printf("New Client IP: %s, Port: %d\n",
                        inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ip, sizeof(ip)),
                        ntohs(client_addr.sin_port));
                    
                }
                else
                {
                    // 处理已经连接的客户端发送过来的数据
                    if(!all[i].events & EPOLLIN) 
                    {
                        continue;
                    }
    
                    // 读数据
                    char buf[1024] = {0};
                    int len = recv(fd, buf, sizeof(buf), 0);
                    if(len == -1)
                    {
                        perror("recv error");
                        exit(1);
                    }
                    else if(len == 0)
                    {
                        printf("client disconnected ....\n");
                        
                        // fd从epoll树上删除
                        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                        close(fd);
                    }
                    else
                    {
                        printf(" recv buf: %s\n", buf);
                        write(fd, buf, len);
                    }
                }
            }
        }
    
        close(lfd);
        return 0;
    }
    
    
    • 文件描述符突破1024限制

      • select - 突破不了

      • poll和epoll可以突破

        • poll 链表
        • epoll 红黑树
      • 查看计算机硬件限制的文件描述符上限

        • cat/vi /proc/sys/fs/file-max
      • 通过配置文件修改上限值

        • /etc/security/limits.conf
          • 添加:
          • * soft nofile 8000
          • * hard nofile 8000
        • 重启或注销
    • epoll的三种工作模式

      • 水平触发模式
      • 边沿触发模式
      • 边沿非阻塞触发

3.UDP通信

  • 通信流程
    • 服务器端
      • 创建套接字
        • 第二个参数为SOCK_DGRAM
      • 绑定IP和Port:bind
        • fd
        • struct sockaddr
      • 通信
        • 接收数据:ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
          • fd 文件描述符
          • buf 接受数据的缓冲区
          • len buf 的最大容量
          • flag 0
          • src_addr 另一端的IP和PORT,传出参数
          • addrlen 传入传出参数
        • 发送数据:ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
          • sockfd socket函数创建的
          • buf 要发送的数据
          • len 发送数据的最大长度
          • flags 0
          • dest_addr 另一端的IP和PORT
          • addrlen dest_addr的长度
    • 客户端:
      • 创建套接字
      • 通信
        • 发送数据:sendto
        • 接受数据:recvfrom

code-server

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, const char* argv[])
{
    // 创建套接字
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1)
    {
        perror("socket error");
        exit(1);
    }
    
    // fd绑定本地的IP和端口
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8765);
    serv.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(fd, (struct sockaddr*)&serv, sizeof(serv));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }

    struct sockaddr_in client;
    socklen_t cli_len = sizeof(client);
    // 通信
    char buf[1024] = {0};
    while(1)
    {
        int recvlen = recvfrom(fd, buf, sizeof(buf), 0, 
                               (struct sockaddr*)&client, &cli_len);
        if(recvlen == -1)
        {
            perror("recvform error");
            exit(1);
        }
        
        printf("recv buf: %s\n", buf);
        char ip[64] = {0};
        printf("New Client IP: %s, Port: %d\n",
            inet_ntop(AF_INET, &client.sin_addr.s_addr, ip, sizeof(ip)),
            ntohs(client.sin_port));

        // 给客户端发送数据
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&client, sizeof(client));
    }
    
    close(fd);

    return 0;
}

code - client

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>

int main(int argc, const char* argv[])
{
    // create socket
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if(fd == -1)
    {
        perror("socket error");
        exit(1);
    }

    // 初始化服务器的IP和端口
    struct sockaddr_in serv;
    memset(&serv, 0, sizeof(serv));
    serv.sin_family = AF_INET;
    serv.sin_port = htons(8765);
    inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

    // 通信
    while(1)
    {
        char buf[1024] = {0};
        fgets(buf, sizeof(buf), stdin);
        // 数据的发送 - server - IP port
        sendto(fd, buf, strlen(buf)+1, 0, (struct sockaddr*)&serv, sizeof(serv));

        // 等待服务器发送数据过来
        recvfrom(fd, buf, sizeof(buf), 0, NULL, NULL);
        printf("recv buf: %s\n", buf);
    }
    
    close(fd);

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值