同一时刻能处理多个客户端
多进程:
int init_tcp_ser(const char *ip,unsigned short port)
{
    int sockfd= socket(AF_INET,SOCK_STREAM,0);
    if(-1== sockfd)
    {
        perror("fail socket");
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port = htons(port);
    ser.sin_addr.s_addr = inet_addr(ip);
    int ret = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
    if(-1 == ret)
    {
        perror("bind fail");
        return -1;
    }
    ret = listen(sockfd,128);
    if(-1 == ret)
    {
        perror("listen fail");
        return -1;
    }
    return sockfd;
}
void handler(int signo)
{
    wait(NULL);
}
int main(int argc, char *argv[])
{
    pid_t pid = 0;
    int connfd = 0;
    char buf[1024] = {0};
    signal(SIGCHLD,handler);
    int sockfd = init_tcp_ser("192.168.42.128",50000);
    if(-1 == sockfd)
    {
        return -1;
    }
    while(1)
    {
        connfd = accept(sockfd,NULL,NULL);
        if(-1==connfd)
        {
            perror("fail accept");
            return -1;
        }
        pid = fork();
        if (pid > 0)
        {
        }
        else if(0==pid)
        {
            while(1)
            {
                memset(buf,0,sizeof(buf));
                ssize_t size = recv(connfd,buf,sizeof(buf),0);
                if(size <= 0)
                {
                    break;
                }
                printf("cli----->%s\n",buf);
                strcat(buf,"---->ok!\n");
                send(connfd,buf,strlen(buf),0);
            }
            close(connfd);
        }
    }
    return 0;
}多线程:
int init_tcp_ser(const char *ip,unsigned short port)
{
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1 == sockfd)
    {
        perror("fail socket");
        return -1;
    }
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;
    ser.sin_port =htons(port);
    ser.sin_addr.s_addr = inet_addr(ip);
    int ret = bind(sockfd,(struct sockaddr *)&ser,sizeof(ser));
    if(-1 == ret)
    {
        perror("bind fail");
        return -1;
    }
    ret = listen(sockfd,128);
    if(-1 == ret)
    {
        perror("listen fail");
        return -1;
    }
    return sockfd;
}
void *do_Something(void *arg)
{
    char buf[1024]= {0};
    int connfd = *(int *)arg;
    while(1)
    {
        memset(buf,0,sizeof(buf));
        ssize_t size = recv(connfd,buf,sizeof(buf),0);
        if(size <= 0)
        {
            break;
        }
        printf("----->%s\n",buf);
        strcat(buf,"------>ok!\n");
        send(connfd,buf,strlen(buf),0);
    }
    close(connfd);
    return NULL;
}
int main(int argc, char *argv[])
{
    int connfd = 0;
    int sockfd = init_tcp_ser("192.168.42.128",50000);
    if(sockfd == -1)
    {
        printf("init_tcp_ser fail");
        return -1;
    }
    while(1)
    {
        connfd = accept(sockfd,NULL,NULL);
        if(-1 == sockfd)
        {
            perror("accept fail");
            return -1;
        }
        pthread_t tid;
        int ret1 = pthread_create(&tid,NULL,do_Something,&connfd);
        if(ret1 != 0)
        {
            perror("pthread_create fail");
            return -1;
        }
        pthread_detach(tid);
        void *retval;
        if(pthread_join(tid,&retval) == 0)
        {
            perror("pthread_join fail");
            return -1;
        }
   }
}IO多路复用:
select:
select模型通过调用select函数来检查一个或多个文件描述符(在socket编程中通常指套接字)的状态,包括可读、可写以及异常。select函数的原型如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const struct timeval *timeout);
功能:
监听文件描述符集合
参数:
nfds:监测的文件描述符上限值(最大文件描述符的值+1)
readfds:读文件描述符集合
writefds:写文件描述符集合
exceptfds:异常条件的描述符集合
timeout:设置超时时间
NULL:一直等待
返回值:
成功返回产生事件文件描述符个数
失败返回-1
定时时间到达仍没有事件产生返回0void FD_CLR(int fd, fd_set *set);
将fd从文件描述符集合中清除
int FD_ISSET(int fd, fd_set *set);
判断文件描述符fd是否仍在文件描述符集合中
void FD_SET(int fd, fd_set *set);
将fd加入文件描述符集合中
void FD_ZERO(fd_set *set);
文件描述符集合清0
优点:
- 跨平台性:select模型是跨平台的,几乎所有的操作系统都支持。
- 简单易用:select模型的API相对简单,使用起来比较方便。
- 灵活性:支持监视多个文件描述符,并且可以通过设置超时参数来实现超时等待。
缺点:
- 效率低:在大规模并发的场景下效率较低,因为需要遍历所有的文件描述符。
- 文件描述符数量限制:对监视的文件描述符数量有限制,通常最多支持1024个(可修改,但受系统限制)。
- IO效率不高:每次调用select都需要将监视的所有文件描述符传递给内核,效率较低。
poll:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:
监听文件描述符集合中的事件
参数:
fds:文件描述符集合事件数组首地址
nfds:事件个数
timeout:超时时间
返回值:
成功返回产生事件的文件描述符个数
失败返回-1
超时时间到达仍没有产生事件返回0
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};
epoll:
epoll模型:
 1)epoll_create 创建epoll文件描述符集合
 2)epoll_ctl   添加关注的文件描述符
 3)epoll_wait 监控io事件
 4)epoll_ctl  从事件集合中删除完成的文件描述符
int epoll_create(int size);
   功能:
                  创建一个监听事件表(内核中)
   参数:
                 size:监听事件最大个数
   返回值:
                 成功返回非负值:表示epoll事件表对象(句柄)
                 失败返回-1 
 5.epoll_ctl
   int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
   功能:
                 在监听事件表中新增一个事件
   参数:
                 epfd:事件表文件描述符 
                 op:EPOLL_CTL_ADD 新增事件
                    EPOLL_CTL_MOD 修改事件 
                    EPOLL_CTL_DEL 删除事件
                 fd:文件描述符 
                 events:事件相关结构体
   返回值:
                 成功返回0 
                 失败返回-1 
 typedef union epoll_data {
    void        *ptr;
    int          fd;
    uint32_t     u32;
    uint64_t     u64;
 } epoll_data_t;
 struct epoll_event {
    uint32_t     events;      /* Epoll events */
    epoll_data_t data;        /* User data variable */
 };
  events:
                 EPOLLIN 读事件
                 EPOLLOUT 写事件
                 EPOLLET 边沿触发
                 LT 水平触发
 6.epoll_wait
   int epoll_wait(int epfd, struct epoll_event *events,
                       int maxevents, int timeout);
       功能:
监听事件表中的事件,并将产生的事件存放到结构体数组中
参数:
epfd:事件表文件描述符
events:存放结果事件结构体数组空间首地址
maxevents:最多存放事件个数
timeout:超时时间
-1:阻塞等待直到有事件发生
返回值:
成功返回产生事件个数
失败返回-1
超时时间到达没有事件发生返回0
select,poll,epoll优缺点对比:
select
优点:
- 可移植性好:select是较早出现的I/O多路复用技术,具有较好的跨平台性,可以在多种操作系统上运行,包括Windows、Linux和Unix等。
- 超时控制:select支持超时设置,允许程序在等待I/O事件时指定一个超时时间,增加了程序的灵活性。
缺点:
- 效率较低:select在处理大量文件描述符(FD)时效率较低。每次调用select时,都需要将用户空间的FD集合复制到内核空间,并且内核需要遍历所有的FD来检查是否有I/O事件发生,这个过程的时间复杂度为O(n),其中n为FD的数量。当FD数量很大时,这个开销会非常大。
- FD数量限制:select对单个进程可监视的FD数量有限制,这个限制通常与系统的文件描述符限制相关,但select通常有一个较小的硬限制,比如1024。
- 信号干扰:如果select在等待期间收到了信号,并且该信号的处理函数修改了select监控的文件描述符集合,那么select的行为将是不确定的。
poll
优点:
- 无连接数限制:poll与select相比,一个显著的优势是它没有最大连接数的限制,因为它基于链表来存储文件描述符。
- 简化编程:poll的接口相对于select来说更简洁一些,它不需要开发者计算最大文件描述符加一的大小。
缺点:
- 效率问题:poll同样存在效率问题,每次调用poll时,也需要将用户空间的FD集合复制到内核空间,并且内核需要遍历所有的FD来检查是否有I/O事件发生,时间复杂度同样为O(n)。
- 数据拷贝:大量的FD数组在每次调用poll时都会被整体复制到用户态和内核地址空间之间,这会导致不必要的性能开销。
epoll
优点:
- 高效处理大量并发连接:epoll是专为处理大量并发连接而设计的,它采用了一种基于事件驱动的方式来工作,只处理“活跃”的socket,从而避免了无谓的遍历和等待。
- IO效率稳定:epoll的IO效率不会随着FD数量的增加而线性下降,这是因为它只关心那些真正发生I/O事件的socket。
- 使用mmap加速数据传递:epoll通过mmap机制实现了内核空间与用户空间之间的消息传递,减少了数据拷贝的次数,进一步提高了效率。
缺点:
- 跨平台性差:epoll是Linux特有的I/O多路复用技术,无法在其他操作系统上使用。
- 学习曲线较陡峭:相对于select和poll来说,epoll的API较为复杂,需要一定的学习成本。
- 活动连接多时性能下降:虽然epoll在处理大量并发连接时效率很高,但当活动连接非常多时,频繁的调用epoll_wait可能会导致性能下降。
综上所述,select、poll和epoll各有优缺点,在实际应用中应根据具体需求和环境选择合适的I/O多路复用技术。对于需要处理大量并发连接的场景,epoll是更好的选择;而对于跨平台性要求较高的场景,则可能需要考虑使用select或poll。




















