基于select函数实现的tcp简单服务器

源码地址 https://github.com/duchenlong/linux-text/tree/master/network/IO/SelectTcp

在这里插入图片描述

回忆TCP的连接过程

  • 服务端

在这里插入图片描述

  • 客户端

在这里插入图片描述

select

关于select 的介绍,可以参考上一篇博客 https://blog.youkuaiyun.com/duchenlong/article/details/106758718

我们使用select函数的地方,是我们服务端所在的地方。

利用select可以监控可读事件的特性,将客户端所发起的连接产生的新的套接字(也就是一个文件描述符),添加到可读事件的集合中。
在这里插入图片描述
由于select中涉及到的处理有点多,我们可以对这些功能进行封装,构造一个selectSvr的类

select 的封装

在这里插入图片描述

tcp类的封装

在这里插入图片描述

程序流程

在这里插入图片描述

程序

cli.cpp 客户端建立连接

#include "Tcpsvr.hpp"
#include <cstdlib>
int main(int argc,char* argv[])
{
   
   
    if(argc != 3)
    {
   
   
        cout<<"请输入正确的参数 [./client] [ip] [port]"<<endl;
        return 0;
    }

    string ip = argv[1];
    uint16_t port = atoi(argv[2]);

    Tcpsvr tcp;
    if(!tcp.CreateSocket())
    {
   
   
        return 0;
    }

    if(!tcp.Connect(ip,port))
    {
   
   
        return 0;
    }

    while(1)
    {
   
   
        cout<<"请输入想给服务端说的话 : ";
        fflush(stdout);
        string buf;
        cin>>buf;
        tcp.Send(buf);
        buf.clear(); 
        //等待接收数据
        
        if(!tcp.Recv(buf))
        {
   
   
            cout<<"我方程序退出"<<endl;
            break;
        }
        cout<<"服务端说 : "<<buf<<endl;
    }
    tcp.Close();

    return 0;
}

SelectSvr.hpp 服务器的头文件

#pragma once 

#include <unistd.h>
#include <sys/select.h>
#include <sys/types.h>
#include <vector>
#include <cstdio>

#include "Tcpsvr.hpp"

using namespace std;

class SelectSvr
{
   
   
    public:
        SelectSvr()
        {
   
   
            _maxFd = -1;
            FD_ZERO(&_readfds);
        }

        //添加文件描述符
        void AddFd(int fd)
        {
   
   
            FD_SET(fd,&_readfds)
<think>好的,用户让我介绍一下在Linux下使用select函数实现TCP并发服务器。首先,我需要回忆一下select函数的基本原理和用途。select是一种I/O多路复用机制,可以同时监控多个文件描述符的状态变化,比如可读、可写或异常。这对于实现并发服务器非常有用,因为服务器需要同时处理多个客户端的连接请求和数据传输。 接下来,我需要确定讲解的结构。用户可能希望了解整个实现流程,所以应该分步骤说明。首先,可能需要先介绍TCP服务器的基本创建步骤,比如创建socket、绑定地址、监听等。然后,再引入select的使用,如何将监听socket和客户端连接的socket加入监控集合,以及如何处理不同的就绪事件。 然后,考虑到用户可能需要具体的代码示例,我应该准备一个简单的示例代码,并解释关键部分。比如,初始化服务器socket,设置fd_set结构,使用select等待事件,处理新的连接请求,处理客户端的数据读取等。同时,需要注意错误处理和资源管理,比如关闭文件描述符,避免内存泄漏。 可能还需要比较select与其他多路复用技术,如epoll或poll的优缺点,但用户的问题集中在select,所以这部分可以简要提及,或者作为扩展内容。不过用户没有特别要求比较,可能不需要深入讨论。 另外,用户可能关心select的局限性,比如最大文件描述符数量的限制(FD_SETSIZE),以及在高并发场景下的性能问题。这些应该在注意事项中提到,帮助用户理解适用场景。 需要确保术语准确,比如“文件描述符就绪”、“阻塞”、“非阻塞”等,避免术语错误。同时,要使用中文表达,代码注释可能需要用中文,但代码本身应该保持正确性。 最后,检查是否有遗漏的关键步骤,比如如何维护客户端连接列表,如何处理多个客户端同时发送数据的情况,以及如何正确使用FD_ISSET宏来检测就绪的描述符。确保逻辑清晰,逐步引导用户理解整个过程。</think>在Linux系统中,使用select函数实现TCP并发服务器的核心思路是通过I/O多路复用技术监控多个文件描述符的状态变化。以下是实现步骤和原理详解: --- ### **一、实现步骤** 1. **创建监听套接字** ```c int listen_fd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serv_addr; // 设置地址复用 setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int)); bind(listen_fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); listen(listen_fd, 5); ``` 2. **初始化fd_set集合** ```c fd_set readfds; FD_ZERO(&readfds); // 清空集合 FD_SET(listen_fd, &readfds); // 添加监听套接字 int max_fd = listen_fd; // 记录当前最大文件描述符 ``` 3. **进入事件循环** ```c while(1) { fd_set tmpfds = readfds; // 创建临时副本 int ready = select(max_fd + 1, &tmpfds, NULL, NULL, NULL); // 处理监听套接字(新连接) if(FD_ISSET(listen_fd, &tmpfds)) { int client_fd = accept(listen_fd, ...); FD_SET(client_fd, &readfds); // 加入监控集合 max_fd = (client_fd > max_fd) ? client_fd : max_fd; } // 遍历处理客户端套接字 for(int fd = 0; fd <= max_fd; fd++) { if(fd != listen_fd && FD_ISSET(fd, &tmpfds)) { ssize_t n = read(fd, buffer, sizeof(buffer)); if(n <= 0) { // 客户端断开 close(fd); FD_CLR(fd, &readfds); } else { // 处理接收到的数据 } } } } ``` --- ### **二、核心机制解析** 1. **select工作原理** - 通过位掩码(fd_set)管理文件描述符集合 - 同步阻塞等待以下任一事件: - 监听套接字就绪(新连接) - 客户端套接字可读(数据到达) - 异常发生 - 时间复杂度O(n),每次需要线性扫描所有描述符 2. **关键参数说明** | 参数 | 作用描述 | |------------|----------------------------------| | max_fd+1 | 监控的最大文件描述符+1 | | readfds | 监控可读事件的描述符集合 | | writefds | 监控可写事件的描述符集合(未使用) | | exceptfds | 监控异常事件的描述符集合(未使用) | | timeout | 设置超时时间(NULL为阻塞等待) | --- ### **三、优化建议** 1. **使用非阻塞IO** ```c // 设置套接字为非阻塞模式 fcntl(client_fd, F_SETFL, O_NONBLOCK); ``` 2. **维护独立客户端列表** 建议使用动态数组单独维护客户端描述符,避免每次遍历0~max_fd 3. **处理EINTR错误** ```c while(1) { int ready = select(...); if(ready == -1 && errno == EINTR) { continue; // 被信号中断则重试 } } ``` --- ### **四、局限性** 1. 文件描述符数量限制(FD_SETSIZE通常为1024) 2. 每次调用需要重置监控集合 3. 线性扫描效率随连接数增加下降 4. 无法区分具体事件类型(需通过后续IO操作判断) --- ### **五、完整示例代码** 可访问GitHub仓库查看完整实现: https://github.com/example/tcp-select-server 对于高性能场景建议改用epoll(Linux特有)或kqueue(BSD系统),但select仍然是跨平台开发的基础技术。理解select的运作机制对学习更高级的IO复用技术有重要帮助。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值