TCP服务器的演变过程:IO多路复用机制select实现TCP服务器

这篇具有很好参考价值的文章主要介绍了TCP服务器的演变过程:IO多路复用机制select实现TCP服务器。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。

为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。
本节,在上一章节的基础上,将并发的实现改为IO多路复用机制,使用select管理每个新接入的客户端连接,实现发送和接收。

二、新增使用API函数

2.1、select()函数

函数原型:

#include <sys/types.h>
#include <unistd.h>

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

select函数共有5个参数,其中参数:

  • maxfds:监视对象文件描述符数量。
  • readset:将所有关注“是否存在待读取数据”的文件描述符注册到fd_set变量,并传递其地址值。
  • writeset: 将所有关注“是否可传输无阻塞数据”的文件描述符注册到fd_set变量,并传递其地址值。
  • exceptset:将所有关注“是否发生异常”的文件描述符注册到fd_set变量,并传递其地址值。
  • timeout:调用select后,为防止陷入无限阻塞状态,传递超时信息。

返回值:

  • 错误返回-1。
  • 超时返回0。

当关注的事件返回时,返回大于0的值,该值是发生事件的文件描述符数。

2.2、FD_*系列函数

函数原型:

#include <sys/types.h>
#include <unistd.h>

void FD_CLR(int fd, fd_set *set);
int  FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

(1)FD_CLR函数用于将fd从set集合中清除,即不监控该fd的事件。

(2)FD_SET函数用于将fd添加到set集合中,监控其事件。

(3)FD_ZERO函数用于将set集合重置。

(4)FD_ISSET函数用于判断set集合中的fd是否有事件(读、写、错误)。

三、实现步骤

什么是IO多路复用?通俗的讲就是一个线程,通过记录IO流的状态来管理多个IO。解决创建多个进程处理IO流导致CPU占用率高的问题。

select是io多路复用的一种方式,其他的还有poll、epoll等。
TCP服务器的演变过程:IO多路复用机制select实现TCP服务器,Linux网络设计,tcp/ip,服务器,网络,select,c语言,网络协议,linux

(1)创建socket。

int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd==-1){
    printf("errno = %d, %s\n",errno,strerror(errno));
    return SOCKET_CREATE_FAILED;
}

(2)绑定地址。

struct sockaddr_in server;
memset(&server,0,sizeof(server));

server.sin_family=AF_INET;
server.sin_addr.s_addr=htonl(INADDR_ANY);
server.sin_port=htons(LISTEN_PORT);

if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
    printf("errno = %d, %s\n",errno,strerror(errno));
    close(listenfd);
    return SOCKET_BIND_FAILED;
}

(3)设置监听。

if(-1==listen(listenfd,BLOCK_SIZE)){
   printf("errno = %d, %s\n",errno,strerror(errno));
   close(listenfd);
   return SOCKET_LISTEN_FAILED;
}

(4)初始化可读文件描述符集合,将监听套接字加入集合。

fd_set writefds,readfds,wset,rset;
FD_ZERO(&readfds);
FD_ZERO(&writefds);
FD_SET(listenfd,&readfds);

(5)从可读文件描述符集合中选择一个就绪的套接字。

wset=writefds;
rset=readfds;

// 从可读文件描述符集合中选择就绪的套接字
int nready=select(maxfd+1,&rset,&wset,NULL,NULL);
        
if(nready==-1)
{
    printf("select errno = %d, %s\n",errno,strerror(errno));
    continue;
}

(6)如果监听套接字有新连接请求,处理新连接。

struct sockaddr_in client;
memset(&client,0,sizeof(client));
socklen_t len=sizeof(client);

int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
if(clientfd==-1){
    printf("accept errno = %d, %s\n",errno,strerror(errno));
}
else{
    printf("accept successdul, clientfd = %d\n",clientfd);
    // 将新套接字加入可读文件描述符集合
    FD_SET(clientfd,&readfds);
    if(clientfd>maxfd)
        maxfd=clientfd;
}

(7)处理客户端发来的数据和发送数据到客户端。

        int i=0;
        for(i=listenfd+1;i<=maxfd;i++)
        {
            if(FD_ISSET(i,&rset))
            {
                printf("recv fd=%d\n",i);
                ret=recv(i,buf,BUFFER_LENGTH,0);
                if(ret==0) {
                    // 客户端断开连接
                    printf("connection dropped\n");
                    // 从可读文件描述符集合中移除该套接字
                    FD_CLR(i,&readfds);
                    close(i);
                }
                else if(ret>0)
                {
                    printf("fd=%d recv --> %s\n",i,buf);
                    FD_CLR(i,&readfds);
                    FD_SET(i,&writefds);
                    
                }
                
            }
            else if(FD_ISSET(i,&wset))
            {
                printf("send to fd=%d\n",i);
                ret=send(i,buf,ret,0);
                if(ret==-1)
                {
                    printf("send() errno = %d, %s\n",errno,strerror(errno));
                }
                FD_CLR(i,&writefds);
                FD_SET(i,&readfds);
            }
        }

四、完整代码

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>

#include <errno.h>
#include <string.h>
#include <unistd.h>

#include <sys/select.h>

#define LISTEN_PORT     9999
#define BLOCK_SIZE      10
#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    SOCKET_CREATE_FAILED=-1,
    SOCKET_BIND_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4,
    SOCKET_SELECT_FAILED=-5
};


int main(int argc,char **argv)
{
    // 1.
    int listenfd=socket(AF_INET,SOCK_STREAM,0);
    if(listenfd==-1){
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;
    }

    // 2.
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));

    server.sin_family=AF_INET;
    server.sin_addr.s_addr=htonl(INADDR_ANY);
    server.sin_port=htons(LISTEN_PORT);

    if(-1==bind(listenfd,(struct sockaddr*)&server,sizeof(server))){
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_BIND_FAILED;
    }

    // 3.
    if(-1==listen(listenfd,BLOCK_SIZE)){
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(listenfd);
        return SOCKET_LISTEN_FAILED;
    }

    printf("listen port: %d\n",LISTEN_PORT);
    
    fd_set writefds,readfds,wset,rset;
    FD_ZERO(&readfds);
    FD_ZERO(&writefds);
    FD_SET(listenfd,&readfds);

    char buf[BUFFER_LENGTH]={0};
    int ret=0;
    int maxfd=listenfd;
    while(1)
    {
        wset=writefds;
        rset=readfds;

        // 从可读文件描述符集合中选择就绪的套接字
        int nready=select(maxfd+1,&rset,&wset,NULL,NULL);
        
        if(nready==-1)
        {
            printf("select errno = %d, %s\n",errno,strerror(errno));
            continue;
        }

        // 如果监听套接字有新连接请求,处理新连接
        if(FD_ISSET(listenfd,&rset))
        {
            // 4.
            printf("accept , listenfd = %d\n",listenfd);
            struct sockaddr_in client;
            memset(&client,0,sizeof(client));
            socklen_t len=sizeof(client);
            
            int clientfd=accept(listenfd,(struct sockaddr*)&client,&len);
            if(clientfd==-1){
                printf("accept errno = %d, %s\n",errno,strerror(errno));
            }
            else{
                printf("accept successdul, clientfd = %d\n",clientfd);
                // 将新套接字加入可读文件描述符集合
                FD_SET(clientfd,&readfds);
                if(clientfd>maxfd)
                    maxfd=clientfd;
            }

            
        }
        printf("listenfd=%d.maxfd=%d\n",listenfd,maxfd);
        int i=0;
        for(i=listenfd+1;i<=maxfd;i++)
        {
            if(FD_ISSET(i,&rset))
            {
                printf("recv fd=%d\n",i);
                ret=recv(i,buf,BUFFER_LENGTH,0);
                if(ret==0) {
                    // 客户端断开连接
                    printf("connection dropped\n");
                    // 从可读文件描述符集合中移除该套接字
                    FD_CLR(i,&readfds);
                    close(i);
                }
                else if(ret>0)
                {
                    printf("fd=%d recv --> %s\n",i,buf);
                    FD_CLR(i,&readfds);
                    FD_SET(i,&writefds);
                    
                }
                
            }
            else if(FD_ISSET(i,&wset))
            {
                printf("send to fd=%d\n",i);
                ret=send(i,buf,ret,0);
                if(ret==-1)
                {
                    printf("send() errno = %d, %s\n",errno,strerror(errno));
                }
                FD_CLR(i,&writefds);
                FD_SET(i,&readfds);
            }
        }
    }

    
    close(listenfd);

    return 0;
}

编译命令:

gcc -o server server.c

五、TCP客户端

5.1、自己实现一个TCP客户端

自己实现一个TCP客户端连接TCP服务器的代码:

#include <stdio.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <string.h>

#include <unistd.h>
#include <stdlib.h>

#define BUFFER_LENGTH   1024

enum ERROR_CODE{
    SOCKET_CREATE_FAILED=-1,
    SOCKET_CONN_FAILED=-2,
    SOCKET_LISTEN_FAILED=-3,
    SOCKET_ACCEPT_FAILED=-4
};

int main(int argc,char** argv)
{
    if(argc<3)
    {
        printf("Please enter the server IP and port.");
        return 0;
    }
    printf("connect to %s, port=%s\n",argv[1],argv[2]);

    int connfd=socket(AF_INET,SOCK_STREAM,0);
    if(connfd==-1)
    {
        printf("errno = %d, %s\n",errno,strerror(errno));
        return SOCKET_CREATE_FAILED;

    }
    struct sockaddr_in serv;
    serv.sin_family=AF_INET;
    serv.sin_addr.s_addr=inet_addr(argv[1]);
    serv.sin_port=htons(atoi(argv[2]));
    socklen_t len=sizeof(serv);
    int rwfd=connect(connfd,(struct sockaddr*)&serv,len);
    if(rwfd==-1)
    {
        printf("errno = %d, %s\n",errno,strerror(errno));
        close(rwfd);
        return SOCKET_CONN_FAILED;
    }
    int ret=1;
    while(ret>0)
    {
        char buf[BUFFER_LENGTH]={0};
        printf("Please enter the string to send:\n");
        scanf("%s",buf);
        send(connfd,buf,strlen(buf),0);

        memset(buf,0,BUFFER_LENGTH);
        printf("recv:\n");
        ret=recv(connfd,buf,BUFFER_LENGTH,0);
        printf("%s\n",buf);
        
    }
    close(rwfd);
    return 0;
}

编译:

gcc -o client client.c

5.2、Windows下可以使用NetAssist的网络助手工具

TCP服务器的演变过程:IO多路复用机制select实现TCP服务器,Linux网络设计,tcp/ip,服务器,网络,select,c语言,网络协议,linux
下载地址:http://old.tpyboard.com/downloads/NetAssist.exe

小结

至此,我们实现了一个使用IO多路复用机制实现的服务器,这时的TCP服务器可以使用一个线程就能处理多个客户端连接。通过记录IO流的状态来管理多个IO,解决创建多个进程处理IO流导致CPU占用率高的问题。

我们总结一下select的使用流程:

1、定义io管理状态变量:fd_set rfds,wfds;

2、初始化变量:FD_ZERO();

3、设置io流状态,最初只有监听的fd,将其设置:FD_SET(listenfd,rfds);

4、在循环中select。

5、FD_ISSET()判断端口是否有连接。

6、FD_ISSET()判断可读、可写状态。

select是io多路复用的一种方式,其他的还有poll、epoll等。下一章节我们将使用更高效的IO多路复用器epoll来实现TCP服务器。
TCP服务器的演变过程:IO多路复用机制select实现TCP服务器,Linux网络设计,tcp/ip,服务器,网络,select,c语言,网络协议,linux文章来源地址https://www.toymoban.com/news/detail-773149.html

到了这里,关于TCP服务器的演变过程:IO多路复用机制select实现TCP服务器的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • linux并发服务器 —— IO多路复用(八)

    半关闭只能实现数据单方向的传输;当TCP 接中A向 B 发送 FIN 请求关闭,另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态),并没有立即发送 FIN 给 A,A 方处于半连接状态 (半开关),此时 A 可以接收 B 发送的数据,但是 A 已经不能再向 B 发送数据 close不会影响到其他进程,shutdown会

    2024年02月09日
    浏览(35)
  • 【高并发服务器 02】——线程池与IO多路复用

    线程池的好处 :所有的池都是为了事先把资源准备好,在后续用的时候可以更加方便的拿到这个资源—— 不用去申请、释放资源 什么时候用线程池 ? IO事务并发较高 :人在杭州,但是数据库在北京,想要查询数据库,需要通过互联网建立TCP三次握手,频繁地创建和销毁线

    2024年03月23日
    浏览(35)
  • Linux多路IO复用技术——epoll详解与一对多服务器实现

    本文详细介绍了Linux中epoll模型的优化原理和使用方法,以及如何利用epoll模型实现简易的一对多服务器。通过对epoll模型的优化和相关接口的解释,帮助读者理解epoll模型的工作原理和优缺点,同时附带代码实现和图解说明。

    2024年02月05日
    浏览(32)
  • TCP服务器的演变过程:多进程实现一对多的TCP服务器

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,改为多进程方式实现TCP服务器,为每个新接入的客户端分配进程,实现一个

    2024年02月04日
    浏览(43)
  • TCP服务器的演变过程:揭秘使用多线程实现一对多的TCP服务器

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。本节在上一章节的基础上,添加多线程,为每个新接入的客户端分配线程,实现一个服务器程序处理多

    2024年02月04日
    浏览(36)
  • 【TCP服务器的演变过程】编写第一个TCP服务器:实现一对一的连接通信

    手把手教你从0开始编写TCP服务器程序,体验 开局一块砖,大厦全靠垒 。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 函数原型: 这个函数建立一个协议族、协议类型、协议编号的socket文件描述符。如果函数调用成功,

    2024年02月03日
    浏览(41)
  • TCP服务器的演变过程:C++使用libevent库开发服务器程序

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 在上一章节介绍了如何使用epoll构建reactor网络模型开发高效的服务器,有了上一节的基础,本节将介绍

    2024年01月23日
    浏览(39)
  • TCP服务器的演变过程:使用epoll构建reactor网络模型实现百万级并发(详细代码)

    手把手教你从0开始编写TCP服务器程序,体验开局一块砖,大厦全靠垒。 为了避免篇幅过长使读者感到乏味,对【TCP服务器的开发】进行分阶段实现,一步步进行优化升级。 本节,在上一章节介绍了如何使用epoll开发高效的服务器,本节将介绍使用epoll构建reactor网络模型,实

    2024年02月01日
    浏览(53)
  • 【高并发网络通信架构】引入IO多路复用(select,poll,epoll)实现高并发tcp服务端

    目录 一,往期文章 二,基本概念 IO多路复用 select 模型 poll 模型 epoll 模型 select,poll,epoll 三者对比 三,函数清单 1.select 方法 2.fd_set 结构体 3.poll 方法 4.struct pollfd 结构体 5.epoll_create 方法 6.epoll_ctl 方法 7.epoll_wait 方法 8.struct epoll_event 结构体 四,代码实现 select 操作流程 s

    2024年02月12日
    浏览(42)
  • epoll多路复用_并发服务器

    应用程序: 驱动程序:

    2024年02月15日
    浏览(42)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包