Linux网络编程-socket到epoll

socket()函数

  • 查看文件描述符的上限:ulimit -a,修改方法:ulimit -HSn 2000
  • IPV4和IPV6区别
  • socket取值从3开始
    在这里插入图片描述

主机字节序与网络字节序

  • 大端字节序:将高序字节存放在起始位置
  • 小端字节序:将低序字节存放在起始位置
    -
  • 网络字节序:采用大端字节序,与cpu,操作系统无关
  • 主机字节序:不同机器主机字节序不相同,与cpu设计有关
  • htons(),ntohs(),htonl(),ntohl():host to network short long
  • 地址和端口:
    在这里插入图片描述
  • inet_addr:将网络主机地址(192.168.1.10)转化为网络字节序二进制值
  • inet_ntoa:转换网络字节序地址为标准的点分地址
    示例代码
#include<stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
int main(int argc,char* argv[])
{
   if(argc!=3){
     return -1;
    }
    in_addr_t p=inet_addr(argv[1]);
    uint16_t q=htons(atoi(argv[2]));
    printf("p=%ld,q=%ld\n",p,q);
    //char *inet_ntoa(struct in_addr in);
    struct in_addr s1;
    s1.s_addr=p;
    char* str1;
    str1=inet_ntoa(s1);
    uint16_t t=ntohs(q);
    printf("str1=%s,t=%ld\n",str1,t);
}

在这里插入图片描述

  • struct hostent *gethostbyname(const char *name);此函数的目的给域名和ip地址都可以解析出主机

bind函数

  • 绑定的端口号区间,端口被占用,释放连接后有时出现time_wait状态

listen,connect,accept

  • accept从已准备好的连接队列里获取一个连接请求
  • 监听socket是3,连接socket从4开始,依次是4,5,6…

TCP的三次握手

  • netstat -na|grep 5005:查看端口网络状态
  • listen的第二个参数含义:内核会为listen状态的socket维护两个队列,不完全连接请求队列,等待accept建立socket的队列

在这里插入图片描述

send,recv

  • 可以发送结构体,数组,字符串等常见的数据类型
  • 不同机器需要考虑网络字节序问题,大于一个字节的需要转换后才能发送
  • 缓冲区:网络中有缓冲区,客户端发送端有缓冲区,服务端接收缓冲区
  • 缓冲区满的话,send也需要等待

TCP报文分包和粘包

  • 分包:发送字符串,接收两个字符串
  • 粘包:发送两个字符串,一次接收完成
    解决办法:
    报文长度+报文内容一起发送
    ASCII长度,或者二进制数

多进程的socket服务端

僵尸进程处理方法:如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。

select的水平触发

  • 报告fd后事件没有被处理,或者数据没有被全部读取,下次select会再次报告该fd,会立即再次报告在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
  • select缺点:支持的文件描述符为1024,
  • 每次都需要内存的拷贝
    在这里插入图片描述
  • 每次都需要遍历文件字符集,判断到底是数据读写事件还是新的客户端连接事件
    在这里插入图片描述
    select缺点
  1. select 调用需要传入 fd 数组,需要拷贝一份到内核,高并发场景下这样的拷贝消耗的资源是惊人的。(可优化为不复制)
  2. select 在内核层仍然是通过遍历的方式检查文件描述符的就绪状态,是个同步过程,只不过无系统调用切换上下文的开销。(内核层可优化为异步事件通知)
  3. select 仅仅返回可读文件描述符的个数,具体哪个可读还是要用户自己遍历。(可优化为只返回给用户就绪的文件描述符,无需用户做无效的遍历)
    代码示例
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/fcntl.h>

// 初始化服务端的监听端口。
int initserver(int port);

int main(int argc,char *argv[])
{
  if (argc != 2)
  {
    printf("usage: ./tcpselect port\n"); return -1;
  }

  // 初始化服务端用于监听的socket。
  int listensock = initserver(atoi(argv[1]));
  printf("listensock=%d\n",listensock);

  if (listensock < 0)
  {
    printf("initserver() failed.\n"); return -1;
  }

  fd_set readfdset;  // 读事件的集合,包括监听socket和客户端连接上来的socket。
  int maxfd;  // readfdset中socket的最大值。

  // 初始化结构体,把listensock添加到集合中。
  FD_ZERO(&readfdset);

  FD_SET(listensock,&readfdset);
  maxfd = listensock;

  while (1)
  {
    struct timeval time;
    time.tv_sec=10;
    // 调用select函数时,会改变socket集合的内容,所以要把socket集合保存下来,传一个临时的给select。
    fd_set tmpfdset = readfdset;
//maxfd描述的是扫描位图的范围,也可以直接设置为1024,但是效率会很低
    int infds = select(maxfd+1,&tmpfdset,NULL,NULL,NULL);
    // printf("select infds=%d\n",infds);
printf("infds=%d\n",infds);
    // 返回失败。
    if (infds < 0)
    {
      printf("select() failed.\n"); perror("select()"); break;
    }

    // 超时,在本程序中,select函数最后一个参数为空,不存在超时的情况,但以下代码还是留着。
    if (infds == 0)
    {
      printf("select() timeout.\n"); continue;
    }

    // 检查有事情发生的socket,包括监听和客户端连接的socket。
    // 这里是客户端的socket事件,每次都要遍历整个集合,因为可能有多个socket有事件。
int ii=0;
    for (int eventfd=0; eventfd <= maxfd; eventfd++)
    {
      if (FD_ISSET(eventfd,&tmpfdset)<=0) continue;

      if (eventfd==listensock)
      {
        // 如果发生事件的是listensock,表示有新的客户端连上来。
        struct sockaddr_in client;
        socklen_t len = sizeof(client);
        int clientsock = accept(listensock,(struct sockaddr*)&client,&len);
        if (clientsock < 0)
        {
          printf("accept() failed.\n"); continue;
        }

        printf ("client(socket=%d) connected ok.\n",clientsock);

        // 把新的客户端socket加入集合。
        FD_SET(clientsock,&readfdset);

        if (maxfd < clientsock) maxfd = clientsock;

        continue;
      }
      else
      {
        // 客户端有数据过来或客户端的socket连接被断开。
        char buffer[1024];
        memset(buffer,0,sizeof(buffer));

        // 读取客户端的数据。
        //ssize_t isize=read(eventfd,buffer,sizeof(buffer));
        ssize_t isize=read(eventfd,buffer,10);

        // 发生了错误或socket被对方关闭。
        if (isize <=0)
        {
          printf("client(eventfd=%d) disconnected.\n",eventfd);

          close(eventfd);  // 关闭客户端的socket。

          FD_CLR(eventfd,&readfdset);  // 从集合中移去客户端的socket。

          // 重新计算maxfd的值,注意,只有当eventfd==maxfd时才需要计算。
          if (eventfd == maxfd)
          {
            for (int ii=maxfd;ii>0;ii--)
            {
              if (FD_ISSET(ii,&readfdset))
              {
                maxfd = ii; break;
              }
            }

            printf("maxfd=%d\n",maxfd);
          }

          continue;
        }

        printf("recv(eventfd=%d,size=%d):%s\n",eventfd,isize,buffer);

        // 把收到的报文发回给客户端。
        write(eventfd,buffer,strlen(buffer));
      }
    }
  }

  return 0;
}

// 初始化服务端的监听端口。
int initserver(int port)
{
  int sock = socket(AF_INET,SOCK_STREAM,0);
  if (sock < 0)
  {
    printf("socket() failed.\n"); return -1;
  }

  // Linux如下
  int opt = 1; unsigned int len = sizeof(opt);
  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,len);
  setsockopt(sock,SOL_SOCKET,SO_KEEPALIVE,&opt,len);

  struct sockaddr_in servaddr;
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
  servaddr.sin_port = htons(port);

  if (bind(sock,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0 )
  {
    printf("bind() failed.\n"); close(sock); return -1;
  }

  if (listen(sock,5) != 0 )
  {
    printf("listen() failed.\n"); close(sock); return -1;
  }

  return sock;
}

poll模型

poll采用数组来记录fd_set,
在这里插入图片描述
fd为一个struct pollfd的数组,nfds为遍历的最大的数,timeout为设置超时时间
在这里插入图片描述
fd为文件描述符,events为请求的时间,revents为返回的时间
poll优点:比较select,poll不需要拷贝一份数据,poll函数本身就有revents参数,可以根据revents参数来判断是否有时间发生

epoll模型

  • 创建epoll的句柄,本身就是一个fd
    epollfd = epoll_create(1);
  • 注册需要监听fd和事件
    struct epoll_event ev;
    ev.data.fd = listensock;
    ev.events = EPOLLIN;
    epoll_ctl(epollfd,EPOLL_CTL_ADD,listensock,&ev);
  • 等待事件发生
    int infds = epoll_wait(epollfd,events,MAXEVENTS,-1);
    水平触发:epoll默认为水平触发,如果报告了fd事件后没有被处理,或者数据没有被读取完整,那么epoll会立即再次报告该fd
    边缘触发:
    边缘触发分为两种,一种设置监听为边缘触发,一种设置连接的socket为边缘触发
    在这里插入图片描述
    在这里插入图片描述

I/O多路复用

参考公众号文章:
[https://mp.weixin.qq.com/s/YdIdoZ_yusVWza1PU7lWaw]

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Alex1_Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值