linux的TCP服务器设计C++

1 篇文章 0 订阅

服务器设计的类

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);
}

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值