linux的TCP服务器设计C++

2023-11-17

服务器设计的类

myepoll.h

#ifndef MYEPOLL_H
#define MYEPOLL_H

#pragma once

#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <iostream>

#define MAX_BUFF_SIZE	1024    // TCP buff 最大缓存量
#define MAX_EVENTS      100     // 可允许处理的最大处理事件个数
#define FDSIZE          1000

#define CLIENT_MAX      4

/**
 * @brief Tcp服务器数据回调接口
 */
class TcpServerCallback
{
public:
    virtual void onTcpServerData(unsigned char* data, int dataLen) = 0;
};

class TcpServer
{
public:
    TcpServer();
    /**
     * @brief createServer
     * @param port          : socket端口
     * @param clientsMax    : 最大接入的客户端数量
     * @param callback      : 回调接口
     * @param isblock       : 是否阻塞
     * @return 
     */
    bool createServer(int port, int clientsMax, TcpServerCallback *callback, bool isblock);
    void tcpSend(unsigned char *data, int dataLen);

    bool do_epoll();

private:
    /** @brief 回调接口 */
    TcpServerCallback *m_callback;

    /** @brief socket 文件描述符 */
    int m_socketfd = 0;
    /** @brief 服务端虚拟地址 */
    struct sockaddr_in m_servaddr;
    /** @brief epoll 文件描述符 */
    int m_epollfd;

    /** @brief 最大的连接客户端个数 */
    int m_clientsMax = 0;
    /** @brief 接入的客户端个数 */
    int m_tcpClientNum = 0;
    /** @brief 客户端文件描述符 */
    int m_tcpClientFd[CLIENT_MAX + 1] = {0};

    /** @brief 线程 */
    pthread_t m_thread;


private:
    void add_event(int fd, int state);
    void del_event(int fd, int state);
    void mod_event(int fd, int state);

    void handle_events(epoll_event *events, int num);
    bool handle_accept();
    bool do_read(int fd);
    bool do_write(int fd, char * buf, int buflen);

    void select();
};

#endif // MYEPOLL_H

myepoll.cpp

#include "myepoll.h"


static void *tcp_recive_data(void *func)
{
    TcpServer *sm = (TcpServer *)func;
    sm->do_epoll();
    return 0;
}

TcpServer::TcpServer()
{
    m_socketfd = 0;
    memset(&m_servaddr, 0, sizeof(m_servaddr));
}

/**
 * @brief 建立连接
 * @param port      socket端口
 * @param maxEvents 最大处理事件
 * @param isblock   是否阻塞
 * @return
 */
bool TcpServer::createServer(int port, int clientsMax, TcpServerCallback *callback, bool isblock)
{
    m_callback = callback;
    m_tcpClientNum = 0;

    if(clientsMax > CLIENT_MAX)
        m_clientsMax = CLIENT_MAX;
    else
        m_clientsMax = clientsMax;

    if ((m_socketfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return false;
    }


    m_servaddr.sin_family = AF_INET;
    // IP地址设置成INADDR_ANY,让系统自动获取本机的IP地址。
    m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    m_servaddr.sin_port = htons(port);
    if (!isblock){
        int flags = fcntl(m_socketfd, F_GETFL, 0);
        fcntl(m_socketfd, F_SETFL, flags | O_NONBLOCK);//设置为非阻塞
    }

    // 设置重用地址,防止Address already in use 心跳包检测
    int on = 1;
    if (setsockopt(m_socketfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1){
        printf("set reuse addr error: %s(errno: %d)\n", strerror(errno), errno);
        return false;
    }

    // 将本地地址绑定到所创建的套接字上
    if (bind(m_socketfd, (struct sockaddr*)&m_servaddr, sizeof(m_servaddr)) == -1){
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return false;
    }
    // 开始监听是否有客户端连接
    if (listen(m_socketfd, 5) == -1){
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return false;
    }

    printf("port [%d] create socket success\n", port);
    select();
    return true;
}

/**
 * @brief 监听数据
 * @return
 */
bool TcpServer::do_epoll()
{
    struct epoll_event events[MAX_EVENTS];
    int ret;
    // 创建一个描述符
    if ((m_epollfd = epoll_create(FDSIZE)) == -1){
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return false;
    }
    // 添加监听描述符事件
    add_event(m_socketfd, EPOLLIN);
    while (true){
        // 获取已经准备好的描述符事件
        /*
        如果要设置read超时
        1,设置socket非阻塞
        2,设置epoll_wait超时1秒
        3,每次进入epoll_wait之前,遍历在线用户列表,踢出长时间没有请求的用户.

        PS:每次用户发来数据, read之后更新该用户last_request时间, 为了上面的步骤3而做
        */
        ret = epoll_wait(m_epollfd, events, MAX_EVENTS, -1);
        handle_events(events, ret);
    }
    close(m_epollfd);
}

/**
 * @brief 在文件描述符epfd所引用的epoll实例上注册目标文件描述符fd,并将事件事件与内部文件链接到fd
 * @param fd
 * @param state
 */
void TcpServer::add_event(int fd, int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(m_epollfd, EPOLL_CTL_ADD, fd, &ev);
}


/**
 * @brief 删除关联的文件描述符
 * @param fd
 * @param state
 */
void TcpServer::del_event(int fd, int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(m_epollfd, EPOLL_CTL_DEL, fd, &ev);
}

/**
 * @brief 更改与目标文件描述符fd相关联的事件事件。
 * @param fd
 * @param state
 */
void TcpServer::mod_event(int fd, int state)
{
    struct epoll_event ev;
    ev.events = state;
    ev.data.fd = fd;
    epoll_ctl(m_epollfd, EPOLL_CTL_MOD, fd, &ev);
}


/**
 * @brief 处理事件
 * @param events    事件描述符
 * @param num       事件个数
 * @param buf       缓存
 * @param buflen    缓存长度
 */
void TcpServer::handle_events(epoll_event * events, int num)
{
    int i;
    int fd;
    //进行选好遍历
    for (i = 0; i < num; i++){
        fd = events[i].data.fd;
        //根据描述符的类型和事件类型进行处理
        if ((fd == m_socketfd) && (events[i].events& EPOLLIN))
            handle_accept();
        else if (events[i].events & EPOLLIN)
            do_read(fd);
        else
            close(fd);
    }
}

/**
 * @brief 客户端连接处理
 * @return
 */
bool TcpServer::handle_accept(void)
{
    int clifd;
    struct sockaddr_in cliaddr;
    socklen_t cliaddrlen = sizeof(cliaddr);

    clifd = accept(m_socketfd, (struct sockaddr*) &cliaddr, &cliaddrlen);
    if (clifd == -1){
        printf("accpet error:");
        return false;
    }
    // 添加一个客户描述符和事件
    if (m_tcpClientNum < m_clientsMax){
        add_event(clifd, EPOLLIN);
        m_tcpClientFd[m_tcpClientNum] = clifd;
        m_tcpClientNum++;

        char msg[128] = { 0 };
        // 打印端口
        snprintf(msg, sizeof(msg), "accept a new client:%s:%d\n", inet_ntoa(cliaddr.sin_addr), cliaddr.sin_port);
        printf("%s\n", msg);
        tcpSend((unsigned char *)msg, strlen(msg));
    }else{
        printf("TCP Cmd Client Over Max Connect!");
        close(clifd);
        printf("close(tcpListe)\n");
        return false;

    }
    return true;
}

/**
 * @brief 读取事件数据
 * @param fd
 * @param buf
 * @param buflen
 * @return
 */
bool TcpServer::do_read(int fd)
{
    char buf[MAX_BUFF_SIZE] = { 0 };
    memset(buf, 0, sizeof(buf));
    int buflen = read(fd, buf, MAX_BUFF_SIZE);
    if (buflen == -1){
        printf("read error: %s(errno: %d)\n", strerror(errno), errno);
        std::cout << "read =-1\n";
        close(fd);
        del_event(fd, EPOLLIN);
        return false;
    }else if (buflen == 0){
        close(fd);
        std::cout << "client close.\n";
        del_event(fd, EPOLLIN);
        m_tcpClientNum--;
        return true;
    }else{
        m_callback->onTcpServerData((unsigned char*)buf, buflen);
    }
    return true;
}

/**
 * @brief 发送数据
 * @param data
 * @param dataLen
 */
void TcpServer::tcpSend(unsigned char *data, int dataLen)
{
    for (int i = 0; i < m_tcpClientNum; i++)
        if (!do_write(m_tcpClientFd[i], (char*) data, dataLen))
            printf("tcpSend %d fault!", m_tcpClientFd[i]);
}

/**
 * @brief 写入事件数据
 * @param fd
 * @param buf
 * @param buflen
 * @return
 */
bool TcpServer::do_write(int fd, char * buf, int buflen)
{
    int nwrite;
    nwrite = write(fd, buf, buflen);

    if (nwrite == -1){
        printf("write error:");
        return false;
    }
    return true;
}

/**
 * @brief 启动select监听(只允许调用一次且必须在所有文件描述符都添加之后才可以调用)
 */
void TcpServer::select()
{
    // 启动线程
    int res = pthread_create(&m_thread, NULL, tcp_recive_data, (void*)this);
    if (res){
        printf("SelectWraper::SelectWraper::create thread fail");
    }
}

使用的方法

#include "myepoll.h"

class ClientDevice : public TcpServerCallback
{

public:
    ClientDevice();
    void init();
    // 接收Tcp服务器数据
    virtual void onTcpServerData(unsigned char* data, int dataLen);

private:
    TcpServer *m_TCPServer;
};

class FileClientDevice : public TcpServerCallback
{

public:
    FileClientDevice();
    void init();
    // 接收Tcp服务器数据
    virtual void onTcpServerData(unsigned char* data, int dataLen);

private:
    TcpServer *m_TCPServer;
};


ClientDevice::ClientDevice()
{
}

void ClientDevice::init()
{
    m_TCPServer = new TcpServer();
    printf("创建8080\n");
    m_TCPServer->createServer(8080, 1, this, false);
}

void ClientDevice::onTcpServerData(unsigned char *data, int dataLen)
{
    printf("8080 str = [%s], len = <%d>\n", data, dataLen);
}

FileClientDevice::FileClientDevice()
{
}

void FileClientDevice::init()
{
    m_TCPServer = new TcpServer();
    printf("创建8000\n");
    m_TCPServer->createServer(8000, 1, this, false);
}

void FileClientDevice::onTcpServerData(unsigned char *data, int dataLen)
{
    printf("8000 str = [%s], len = <%d>\n", data, dataLen);
}

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux的TCP服务器设计C++ 的相关文章

随机推荐

  • 浅谈编程职业的乐趣和苦恼

    乐趣 编程为什么有趣 作为回报 它的从业者期望得到什么样的快乐 首先 这种快乐是一种创建事物的纯粹快乐 如同小孩在玩泥巴时感到快乐一样 成年人喜欢创建事物 特别是自己进行设计 我想这种快乐是上帝创造世界的折射 一种呈现在每片独特的 崭新的树
  • SpringCloud 商城系统搭建之eureka

    项目环境 1 IDE eclipse maven 2 操作系统 win10 3 jdk 1 8 4 springboot 2 1 0 RELEASE springcloud Greenwich SR5 SpringCloud对应Spring
  • BIG DATA 神奇的大数据 - HDFS分布式文件系统

    目录 自说 学习路径 基本概念 块 优劣 结构 读写流程 使用 命令行接口 自说 HDFS在Hadoop起到重要作用 解决了大规模的数据存储及管理问题 呢么有如此庞大的数据 hdfs是如何准确的做到数据的保存与不流失性 又是通过什么方式去存
  • iqoo手机可以刷鸿蒙系统吗,华为老机型可以更新鸿蒙系统么-华为哪些老机型支持鸿蒙系统...

    目前由于华为鸿蒙系统发布的火爆 导致花粉俱乐部直接崩溃 那么鸿蒙系统除了支持当下的旗舰机外 还支持老机型么 小编就为大家 带来了相关说明 华为老机型可以更新鸿蒙系统么 可以 据小编得到的消息 鸿蒙系统将会支持百万老机型 更新 不要错过了 华
  • Docker常用命令分享(Docker安装MySQL)

    一 Docker是什么 Docker 使用 Google 公司推出的 Go 语言 进行开发实现 基于 Linux 内核的 cgroup namespace 以及 OverlayFS 类的 Union FS 等技术 对进程进行封装隔离 属于
  • springsecurity教程

    springsecurity 课程 一 权限管理简介 1 什么是权限管理 基本上涉及到用户参与的系统都要进行权限管理 权限管理属于系统安全的范畴 权限管理实现对用户访问系统的控制 按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授
  • 排除服务器简单系统故障方法,引导CD排除服务器故障方法有哪些?

    尽管Linux系统以稳定可靠著称 但由于硬件问题有时仍会崩溃 或无法引 针对这一问题 最好的解决办法就是使用Linux系统引导CD 为了方便读者 笔者在下面列出了安装Red Hat Linux 8 0的最必须步骤 为安装过程作笔记 在Red
  • 机器视觉基础

    机器视觉基础 什么是机器视觉 机器视觉常见的应用 机器视觉常用的概念 硬件选型 控制器 相机 镜头 附件选型 什么是机器视觉 机器视觉常见的应用 机器视觉常用的概念 快门时间越短 图片越暗淡 光圈小了 照片会暗淡 硬件选型的时候考虑物距 W
  • Vmware安装vmware-tools后,仍无法上网

    步骤总结 保证vmware tools安装完成 参考下文步骤一 上一步骤如遇到问题 重启vmware的操作系统 步骤总结 1 保证vmware tools安装完成 2 参考下文 步骤一 VMware Workstation虚拟机不能联网的解
  • HTML-div,span,form,input标签

  • 记录docker 部署nessus

    1 开启容器 docker run itd name ramisec nessus p 8834 8834 ramisec nessus 2 登录 注意是https https ip 8843 3 修改admin密码 进入容器 docker
  • 【自学】若依系统-----权限控制

    文章目录 1 数据表 2 sql查询 3 首页菜单权限核心代码 4 菜单管理功能 5 流程 1 数据表 2 sql查询
  • [算法]LeetCode 专题 -- 二分查找专题 34. 在排序数组中查找元素的第一个和最后一个位置

    LeetCode 专题 二分查找专题 34 在排序数组中查找元素的第一个和最后一个位置 难度 中等 题目描述 给定一个按照升序排列的整数数组 nums 和一个目标值 target 找出给定目标值在数组中的开始位置和结束位置 你的算法时间复杂
  • 章节一:Vue.js简介

    1 1 介绍Vue js的基本概念和历史 Vue js是一个流行的JavaScript前端框架 用于构建交互式的Web界面 它采用了组件化的开发模式 使得构建复杂的用户界面变得简单和高效 Vue js由尤雨溪 Evan You 于2014年
  • C++杂谈 inline关键字

    1 inline 用于把函数指定为内联函数 且该关键字需要与函数定义放在一起 与函数声明放在一起没有效果 2 内联函数一般只适用于比较精简的小函数 实际上内联函数是通过牺牲空间去换取时间上的效率 如果函数过于庞大会造成大量空间的浪费 所以需
  • AWS 亚马逊云良好架构框架

    根据多年来AWS的专家们积累的经验 创建了这一份AWS良好架构框架 其中包含了以下五大支柱 安全性 Security 可靠性 Reliability 性能效率 Performance Efficiency 成本优化 Cost Optimis
  • 产品经理,要有怎样的思维方式?

    一 产品经理有哪些不同的思维方式 从直接回答问题 到先搞清楚问题 一看到问题 马上就想答案 是典型的学生思维 在职场中 面对的问题都是目标不明 信息片面 用户提的问题都是经过扭曲 具有欺骗性 不可完全听信用户的话 或者解决方案是没有标准答案
  • 10没有基于策略的qos_Win10 通过Qos提高网速、解除宽带限制的方法

    win10系统电脑上网 电脑的网速变得越来越慢了 下载文件或软件都要等很久 有时还会出现掉线的问题 检查发现网络并没有什么问题 使用其他电子设备连接宽带 网速却很快 这个可能是和windows10系统保留宽带有关系 Qos 也可能和网卡驱动
  • 4.1.3 为什么技术的尽头是艺术

    最后更新2021 08 22 我们面对太多的未知 无人能精确定义所有可能和现实 我们对自己也一无所知 即使牛顿能精确计算天体运行 也无法预言人心 我们要解决的大部分现实问题都是相对于人 作用于人这种不确定生物的问题 而所有这一切都随时间在改
  • linux的TCP服务器设计C++

    服务器设计的类 myepoll h ifndef MYEPOLL H define MYEPOLL H pragma once include