【Linux网络编程】TCP并发服务器的实现(IO多路复用select)

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

一、服务器模型

1.1 服务器概念

服务器模型主要分为两种,循环服务器并发服务器

循环服务器
在同一时间只能处理一个客户端的请求。
并发服务器
在同一时间内能同时处理多个客户端的请求。

TCP的服务器默认的就是一个循环服务器,原因是有两个阻塞 accept函数 和recv函数 之间会相互影响。
UDP的服务器默认的就是一个并发服务器,因为只有一个阻塞的 recvfrom函数。

1.2 TCP并发服务器的意义

在有些应用场景下,我们既要保证数据可靠,又要支持并发
这就需要用到TCP并发服务器。

1.3 实现TCP并发服务器的方式

  1. 使用多路IO复用实现TCP并发服务器(常用)
  2. 使用多进程实现TCP并发服务器
  3. 使用多线程实现TCP并发服务器

本次我们学习第一个方式,使用多路IO复用(select函数)实现TCP并发服务器的实现,在后续博客中我们会依次讲解其他实现方式。
感兴趣可以收藏加关注哦。

二、使用IO多路复用实现TCP并发服务器优势

对于实际开发过程中:
如果使用多进程实现TCP并发服务器,并发量大的时候,对系统的资源占用量也会很大。

如果使用多线程,业务逻辑复杂的时候,又涉及到临近资源访问的问题
比较好的方式是使用多路IO复用实现TCP并发服务器。

三、select函数

功能:
	实现IO多路复用
头文件:
	 #include <sys/select.h>
函数原型:
	int select(int nfds, fd_set *readfds, fd_set *writefds,
               fd_set *exceptfds, struct timeval *timeout);
参数
	@nfds:监视的最大文件描述符+1
	@readfds:要监视的读文件描述符集合,如果不关心,可以传NULL
	@writefds:要监视的写文件描述符集合,如果不关心,可以传NULL
	@exceptfds:要监视的异常的文件描述符集合,如果不关心,可以传NULL
	(一般我们只关心readfds)
	@timeout:超时时间
		为0时非阻塞
		为NULL时永久阻塞
		为结构体时阻塞一定时间
返回值:
	成功  返回就绪文件描述符的个数
	失败  返回-1,置位错误码
	超时  返回0

	
void FD_CLR(int fd, fd_set *set);
功能:
	删除集合中的文件描述符
参数:
	@fd:文件描述符
	@set:构建要监视的文件描述符集合
	
int  FD_ISSET(int fd, fd_set *set);
功能:
	判断文件描述符是否在集合中
参数:
	@fd:文件描述符
	@set:构建要监视的文件描述符集合
返回值:
	为0时不在里面
	非0时在里面
	
void FD_SET(int fd, fd_set *set);
功能:
	将文件描述符添加到集合中
参数:
	@fd:文件描述符
	@set:构建要监视的文件描述符集合
	
void FD_ZERO(fd_set *set);
功能:
	清空集合
参数:
	@set:构建要监视的文件描述符集合

注意:

  1. select只能监视小于 FD_SETSIZE(1024) 的文件描述符。
  2. select函数在返回时会将没有就绪的文件描述符在表中擦除,
    所以,在循环中调用select时,每次需要重新填充集合。

四、TCP并发服务器的构建

4.1 创建套接字

使用socket函数创建IPV4、TCP套接字

	int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }

4.2 填写服务器网络信息结构体

	struct sockaddr_in serviceaddr;
    memset(&serviceaddr, 0, sizeof(serviceaddr));
    serviceaddr.sin_family = AF_INET;
    serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
    serviceaddr.sin_port = htons(atoi(argv[2]));
    socklen_t serviceaddr_len = sizeof(serviceaddr);

4.3 将服务器网络信息结构体与套接字绑定

	if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
    {
        ERRLOG("bind error");
    }

4.4 将套接字设置为被动监听状态

	if (listen(sockfd, 5) == -1)
    {
        ERRLOG("listen error");
    }

4.5 创建文件描述符集合母本和子本并进行清空操作

	fd_set readfds;
    FD_ZERO(&readfds);
    fd_set readfds_msg;
    FD_ZERO(&readfds_msg);

4.6 将sockfd添加进入集合内,并更新最大文件描述符

	FD_SET(sockfd, &readfds);
    max_fd = max_fd > sockfd ? max_fd : sockfd;

4.7 循环实现内部功能伪代码

 	while(1){
        select();
        //遍历文件描述符集合
        for(){
            if(sockfd就绪了){
                //说明有新的客户端建立连接了
                acceptfd = accept();
                将acceptfd加入到readfds中
                更新最大文件描述符
            }else{
                //说明有客户端发来数据了
                recv();
                //如果recv返回0了 需要将当前的客户端的acceptfd在
                //readfds中删除,后续就不再监视它了
                strcat();
                send();
            }
        }
    }

五、客户端的构建

5.1步骤一和二和4.1,4.2一样

5.2 尝试与服务器建立连接

	if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("connect error");
    }

5.3 内部功能实现伪代码

	while(1){
        //从终端获取数据写入buff中
        fgets();
        buff[strlen(buff)-1] = '\0';//清理结尾的\n

        //发送数据
        if(-1 == send(sockfd, buff, sizeof(buff), 0)){
            ERRLOG("send error");
        }
        
        //接收服务器的应答信息
        if(-1 == (nbytes = recv(sockfd, buff, sizeof(buff), 0))){
            ERRLOG("recv error");
        }   
    }

六、测试结果

使用三个客户端连接一个TCP并发服务器。

测试TCP并发服务器功能实现
【Linux网络编程】TCP并发服务器的实现(IO多路复用select)
测试quit退出功能实现
【Linux网络编程】TCP并发服务器的实现(IO多路复用select)
测试ctrl+c终止程序断开来连接实现
【Linux网络编程】TCP并发服务器的实现(IO多路复用select)
测试退出文件,清除客户端的文件描述符,在新的客户端连接时,从最小的文件描述符开始实现。
【Linux网络编程】TCP并发服务器的实现(IO多路复用select)
成功实现IO多路复用TCP并发服务器和客户端。文章来源地址https://www.toymoban.com/news/detail-435543.html

七、TCP并发服务器源代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <pthread.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/select.h>

#define ERRLOG(msg)                                        \
    do                                                     \
    {                                                      \
        printf("%s %s %d:", __FILE__, __func__, __LINE__); \
        perror(msg);                                       \
        exit(-1);                                          \
    } while (0)

#define N 128

int main(int argc, const char *argv[])
{
    //检查入参合理性
    if (argc != 3)
    {
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        return -1;
    }
    //创建套接字
    int sockfd;
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        ERRLOG("socket error");
    }
    //填写服务器网络信息结构体
    struct sockaddr_in serviceaddr;
    memset(&serviceaddr, 0, sizeof(serviceaddr));
    serviceaddr.sin_family = AF_INET;
    serviceaddr.sin_addr.s_addr = inet_addr(argv[1]);
    serviceaddr.sin_port = htons(atoi(argv[2]));
    socklen_t serviceaddr_len = sizeof(serviceaddr);
    //将服务器网络信息结构体与套接字绑定
    if (bind(sockfd, (struct sockaddr *)&serviceaddr, serviceaddr_len) == -1)
    {
        ERRLOG("bind error");
    }
    //将套接字设置为被动监听状态
    if (listen(sockfd, 5) == -1)
    {
        ERRLOG("listen error");
    }
    char buf[N] = {0};
    int max_fd;
    int i;
    int ret;
    int acceptfd;
    int nbytes;
    //创建文件描述符集合母本和子本并进行清空操作
    fd_set readfds;
    FD_ZERO(&readfds);
    fd_set readfds_msg;
    FD_ZERO(&readfds_msg);
    //将sockfd添加进入集合内,并跟新最大文件描述符
    FD_SET(sockfd, &readfds);
    max_fd = max_fd > sockfd ? max_fd : sockfd;

    while (1)
    {
        //在每次循环前将子本重新赋值,因为select会将没有就绪的文件描述符在集合内擦除
        readfds_msg = readfds;
        if ((ret = select(max_fd + 1, &readfds_msg, NULL, NULL, NULL)) == -1)
        {
            ERRLOG("select error");
        }
        else //说明有文件描述符就绪了
        {
            //遍历文件描述符
            for (i = 3; i < max_fd + 1 && ret != 0; i++)
            {
                //判断是哪个文件描述符就绪了
                if (FD_ISSET(i, &readfds_msg))
                {
                	ret--;
                    if (i == sockfd) //如果套接字就绪了则等待客户端连接
                    {
                        if ((acceptfd = accept(sockfd, NULL, NULL)) == -1)
                        {
                            ERRLOG("accept error");
                        }
                        printf("客户端[%d]连接到服务器..\n", acceptfd);
                        //如果有客户端连接将产生的新的文件描述符添加到集合中,并更新最大文件描述符
                        FD_SET(acceptfd, &readfds);
                        max_fd = max_fd > acceptfd ? max_fd : acceptfd;
                    }
                    else //否则就是客户端发来消息了
                    {
                        memset(buf, 0, N);
                        if ((nbytes = recv(i, buf, N, 0)) == -1)
                        {
                            ERRLOG("recv error");
                        }
                        else if (nbytes == 0)
                        {
                            printf("客户端[%d]已断开连接..\n", i);
                            close(i);            //关闭当前客户端的文件描述符
                            FD_CLR(i, &readfds); //将该客户端的文件描述符在集合中删除
                            continue;
                        }
                        if (strcmp(buf, "quit") == 0)
                        {
                            printf("客户端[%d]已退出服务器..\n", i);
                            close(i);
                            FD_CLR(i, &readfds);
                            continue;
                        }
                        printf("客户端[%d]发来消息[%s]..\n", i, buf);
                        strcat(buf, "--夜猫徐");      //组装应答
                        if (send(i, buf, N, 0) == -1) //发送给客户端
                        {
                            ERRLOG("send error");
                        }
                    }
                }
            }
        }
    }
    close(sockfd);
    return 0;
}

八、客户端源代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>

#define ERRLOG(msg) do{\
        printf("%s %s %d:", __FILE__, __func__, __LINE__);\
        perror(msg);\
        exit(-1);\
}while(0)

#define N 128

int main(int argc, const char *argv[]){
    //入参合理性检查
    if(3 != argc){
        printf("Usage : %s <IP> <PORT>\n", argv[0]);
        return -1;
    }

    //1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(-1 == sockfd){
        ERRLOG("socket error");
    }

    //2.填充服务器网络信息结构体
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);

    socklen_t serveraddr_len = sizeof(serveraddr);

    char buff[128] = {0};
    int nbytes = 0;

    //3.尝试与服务器建立连接
    if(-1 == connect(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
        ERRLOG("connect error");
    }
    printf("与服务器建立连接成功..\n");
    while(1){
        memset(buff, 0, sizeof(buff));
        fgets(buff, N, stdin);
        buff[strlen(buff)-1] = '\0';//清理结尾的\n

        //发送数据
        if(-1 == send(sockfd, buff, sizeof(buff), 0)){
            ERRLOG("send error");
        }
        //接收服务器的应答信息
        if(-1 == (nbytes = recv(sockfd, buff, sizeof(buff), 0))){
            ERRLOG("recv error");
        }
        if(0 == nbytes){
            break;
        }
        //输出应答信息
        printf("应答为:[%s]\n", buff);
    }
    //关闭套接字
    close(sockfd);

    return 0;
}

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

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

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

相关文章

  • Linux网络编程:多进程 多线程_并发服务器

    文章目录: 一:wrap常用函数封装 wrap.h  wrap.c server.c封装实现 client.c封装实现 二:多进程process并发服务器 server.c服务器 实现思路 代码逻辑  client.c客户端 三:多线程thread并发服务器 server.c服务器 实现思路 代码逻辑  client.c客户端 ​​​​   read 函数的返回值 wrap.h  wrap

    2024年02月12日
    浏览(39)
  • 【Linux网络编程】网络编程套接字(TCP服务器)

    作者:爱写代码的刚子 时间:2024.4.4 前言:本篇博客主要介绍TCP及其服务器编码 只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP地址 但是我们通常用点分十进制的字符串表示IP地址,以下函数可以在字符串表示和in_addr表示之间转换 字符串转in

    2024年04月14日
    浏览(43)
  • 【Linux网络编程】高并发服务器框架 线程池介绍+线程池封装

    前言 一、线程池介绍 💻线程池基本概念 💻线程池组成部分 💻线程池工作原理  二、线程池代码封装 🌈main.cpp 🌈ThreadPool.h 🌈ThreadPool.cpp 🌈ChildTask.h  🌈ChildTask.cpp 🌈BaseTask.h 🌈BaseTask.cpp 三、测试效果 四、总结 📌创建线程池的好处 本文主要学习 Linux内核编程 ,结合

    2024年01月16日
    浏览(74)
  • 计算机网络套接字编程实验-TCP多进程并发服务器程序与单进程客户端程序(简单回声)

    1.实验系列 ·Linux NAP-Linux网络应用编程系列 2.实验目的 ·理解多进程(Multiprocess)相关基本概念,理解父子进程之间的关系与差异,熟练掌握基于fork()的多进程编程模式; ·理解僵尸进程产生原理,能基于|sigaction()或signal(),使用waitpid()规避僵尸进程产生; ·

    2024年02月12日
    浏览(33)
  • Linux网络编程:线程池并发服务器 _UDP客户端和服务器_本地和网络套接字

    文章目录: 一:线程池模块分析 threadpool.c 二:UDP通信 1.TCP通信和UDP通信各自的优缺点 2.UDP实现的C/S模型 server.c client.c 三:套接字  1.本地套接字 2.本地套 和 网络套对比 server.c client.c threadpool.c   server.c client.c server.c client.c

    2024年02月11日
    浏览(42)
  • [Linux] 网络编程 - 初见TCP套接字编程: 实现简单的单进程、多进程、多线程、线程池tcp服务器

    网络的上一篇文章, 我们介绍了网络变成的一些重要的概念, 以及 UDP套接字的编程演示. 还实现了一个简单更简陋的UDP公共聊天室. [Linux] 网络编程 - 初见UDP套接字编程: 网络编程部分相关概念、TCP、UDP协议基本特点、网络字节序、socket接口使用、简单的UDP网络及聊天室实现…

    2024年02月16日
    浏览(45)
  • Linux网络编程:socket、客户端服务器端使用socket通信(TCP)

    socket(套接字),用于网络中不同主机间进程的通信。 socket是一个伪文件,包含读缓冲区、写缓冲区。 socket必须成对出现。 socket可以建立主机进程间的通信,但需要协议(IPV4、IPV6等)、port端口、IP地址。          (1)创建流式socket套接字。                 a)此s

    2024年02月11日
    浏览(42)
  • Linux网络编程之TCP/IP实现高并发网络服务器设计指南

    目录 引言: 多进程服务器 例程分享: 多线程服务器  例程分享: I/O多路复用服务器 select 例程分享: poll 例程分享: epoll 例程分享: 总结建议         随着互联网的迅猛发展,服务器面临着越来越多的并发请求。如何设计一个能够高效处理大量并发请求的服务器成为

    2024年02月20日
    浏览(38)
  • Linux网络编程二(TCP三次握手、四次挥手、TCP滑动窗口、MSS、TCP状态转换、多进程/多线程服务器实现)

    TCP三次握手 TCP 三次握手 (TCP three-way handshake)是TCP协议建立可靠连接的过程,确保客户端和服务器之间可以进行可靠的通信。下面是TCP三次握手的详细过程: 假设客户端为A,服务器为B 1 、第一次握手(SYN=1,seq=500) A向B发送一个带有SYN标志位的数据包,表示A请求建立连接。

    2024年02月06日
    浏览(43)
  • Linux网络编程二(TCP图解三次握手及四次挥手、TCP滑动窗口、MSS、TCP状态转换、多进程/多线程服务器实现)

    1、TCP三次握手 TCP 三次握手 (TCP three-way handshake)是 TCP协议建立可靠连接 的过程,确保客户端和服务器之间可以进行可靠的通信。下面是TCP三次握手的 详细过程 : 假设客户端为A,服务器为B。 (1) 第一次握手 第一次握手(SYN=1,seq=500) A向B发送一个带有 SYN 标志位的数据包,

    2024年04月22日
    浏览(34)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包