[Programme réseau TCP] Serveur TCP de la version du pool de threads

Makefile

.PHONY:all
all:tcp_client tcp_server

tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11 #-lpthread
tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_client tcp_server

tcp_server.hpp

Ce code implémente un serveur TCP simple avec les caractéristiques suivantes :

  • Utilisez une boucle infinie pour traiter la demande du client. Une fois qu'une demande arrive, utilisez la fonction de lecture pour recevoir les données, puis utilisez la fonction d'écriture pour renvoyer les données reçues au client.
  • Lorsque le client ferme la connexion, le serveur ferme également la connexion correspondante.
  • Mise en œuvre de trois méthodes de service différentes : service, change, dictOnline. La méthode service est un simple service d'écho. La méthode change convertit les lettres minuscules des données envoyées par le client en lettres majuscules avant de les renvoyer. La méthode dictOnline renvoie la traduction chinoise correspondante en fonction des données envoyées par le client. Si le client envoie Si les données ne sont pas dans le dictionnaire, retournez "Je ne sais pas...".
  • Le pool de threads ThreadPool est utilisé pour traiter les demandes des clients, en ajoutant la tâche Task à la file d'attente des tâches, puis en la traitant dans le pool de threads.
  • Les conteneurs STL tels que std :: string et std :: unordered_map en C++ 11 sont utilisés pour gérer facilement le mappage de chaînes et de dictionnaires.

Comment utiliser le pool de threads

Dans la fonction start(), un pool de threads est utilisé pour gérer les demandes de connexion de plusieurs clients. Dans une boucle infinie, le serveur continue d'essayer d'accepter de nouvelles connexions à partir du socket d'écoute et, en cas de succès, crée une tâche Task (les paramètres constructeur de la classe Task incluent le descripteur de socket de la connexion, l'adresse IP et le numéro de port du client, et le traitement La fonction de rappel de la demande de connexion) et le soumettre au pool de threads pour traitement. Lors du traitement, la fonction de rappel enregistrée dans la tâche Tâche sera appelée pour traiter la demande du client. Si l'acceptation échoue, enregistrez le message d'erreur et continuez d'attendre la prochaine demande de connexion.

#pragma once

#include <iostream>
#include <string>
#include <unordered_map>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <signal.h>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <pthread.h>
#include <ctype.h>
#include "ThreadPool/log.hpp"
#include "ThreadPool/threadPool.hpp"
#include "ThreadPool/Task.hpp"

static void service(int sock, const std::string &clientip,
                    const uint16_t &clientport, const std::string &thread_name)
{
    
    
    // echo server
    //  同时在线10人
    //  所以,我们一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    //  后面有其他方案!
    char buffer[1024];
    while (true)
    {
    
    
        // read && write 可以直接被使用!
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if (s > 0)
        {
    
    
            buffer[s] = 0; // 将发过来的数据当做字符串
            std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
        }
        else if (s == 0) // 对端关闭连接
        {
    
    
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else
        {
    
     //
            logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
            break;
        }

        write(sock, buffer, strlen(buffer));
    }
    close(sock);
}

static void change(int sock, const std::string &clientip,
                   const uint16_t &clientport, const std::string &thread_name)
{
    
    
    // echo server
    //  同时在线10人
    //  所以,我们一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    //  后面有其他方案!
    char buffer[1024];
    // read && write 可以直接被使用!
    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
    
    
        buffer[s] = 0; // 将发过来的数据当做字符串
        std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
        std::string message;
        char *start = buffer;
        while(*start)
        {
    
    
            char c;
            if(islower(*start)) c = toupper(*start);
            else c = *start;
            message.push_back(c);
            start++;
        }

        write(sock, message.c_str(), message.size());
    }
    else if (s == 0) // 对端关闭连接
    {
    
    
        logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
    }
    else
    {
    
     //
        logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    }

    close(sock);
}
static void dictOnline(int sock, const std::string &clientip,
                   const uint16_t &clientport, const std::string &thread_name)
{
    
    
    // echo server
    //  同时在线10人
    //  所以,我们一般服务器进程业务处理,如果是从连上,到断开,要一直保持这个链接, 长连接
    //  后面有其他方案!
    char buffer[1024];
    static std::unordered_map<std::string, std::string> dict = {
    
    
        {
    
    "apple", "苹果"},
        {
    
    "bite", "比特"},
        {
    
    "banana", "香蕉"},
        {
    
    "hard", "好难啊"}
    };
    // read && write 可以直接被使用!
    ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
    if (s > 0)
    {
    
    
        buffer[s] = 0; // 将发过来的数据当做字符串
        std::cout << thread_name << "|" << clientip << ":" << clientport << "# " << buffer << std::endl;
        std::string message;
        auto iter = dict.find(buffer);
        if(iter == dict.end()) message = "我不知道...";
        else message = iter->second;
        write(sock, message.c_str(), message.size());
    }
    else if (s == 0) // 对端关闭连接
    {
    
    
        logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
    }
    else
    {
    
     //
        logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
    }

    close(sock);
}

class TcpServer
{
    
    
private:
    const static int gbacklog = 20; // 后面再说
public:
    TcpServer(uint16_t port, std::string ip = "0.0.0.0")
        : _listensock(-1), _port(port),
          _ip(ip), _threadpool_ptr(ThreadPool<Task>::getThreadPool())
    {
    
    
    }
    void initServer()
    {
    
    
        // 1. 创建socket -- 进程和文件
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
    
    
            logMessage(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", _listensock); // 3

        // 2. bind -- 文件 + 网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &local.sin_addr);
        // inet_aton(_ip.c_str(), &local.sin_addr);
        // local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
    
    
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3. 因为TCP是面向连接的,当我们正式通信的时候,需要先建立连接
        if (listen(_listensock, gbacklog) < 0)
        {
    
    
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "init server success");
    }
    void start()
    {
    
    
        // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD,主动忽略SIGCHLD信号,子进程退出的时候,会自动释放自己的僵尸状态
        _threadpool_ptr->run();
        while (true)
        {
    
    
            // sleep(1);
            // 4. 获取连接
            struct sockaddr_in src;
            socklen_t len = sizeof(src);
            // fd(李四,王五等提供服务的服务员) vs _sock(张三 --- 获取新连接)
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);
            if (servicesock < 0)
            {
    
    
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue;
            }
            // 获取连接成功了
            uint16_t client_port = ntohs(src.sin_port);
            std::string client_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",
                       servicesock, client_ip.c_str(), client_port);
            // verison4 --- 线程池版本
            // Task t(servicesock, client_ip, client_port, service);
            // Task t(servicesock, client_ip, client_port, change);
            Task t(servicesock, client_ip, client_port, dictOnline);
            _threadpool_ptr->pushTask(t);

            // version 3 --- 多线程版本
            // ThreadData *td = new ThreadData();
            // td->_sock = servicesock;
            // td->_ip = client_ip;
            // td->_port = client_port;
            // pthread_t tid;
            // //在多线程这里用不用进程关闭特定的文件描述符呢??1 0
            // pthread_create(&tid, nullptr, threadRoutine, td);
            // close(servicesock);

            // pthread_join();
            // version2.1 -- 多进程版
            // pid_t id = fork();
            // if(id == 0)
            // {
    
    
            //     // 子进程
            //     close(_listensock);
            //     if(fork() > 0/*子进程本身*/) exit(0); //子进程本身立即退出
            //     // 孙子进程, 孤儿进程,OS领养, OS在孤儿进程退出的时候,由OS自动回收孤儿进程!
            //     service(servicesock, client_ip, client_port);
            //     exit(0);
            // }
            // // 父进程
            // waitpid(id, nullptr, 0); //不会阻塞!
            // close(servicesock);

            // 开始进行通信服务啦
            // version 1 -- 单进程循环版 -- 只能够进行一次处理一个客户端,处理完了一个,才能处理下一个
            // 很显然,是不能够直接被使用的! -- 为什么? 单进程
            // service(servicesock, client_ip, client_port);

            // version 2.0 -- 多进程版 --- 创建子进程
            // 让子进程给新的连接提供服务,子进程能不能打开父进程曾经打开的文件fd呢?1 0
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
    
    
            //     // 子进程, 子进程会不会继承父进程打开的文件与文件fd呢?1, 0
            //     // 子进程是来进行提供服务的,需不需要知道监听socket呢?
            //     close(_listensock);
            //     service(servicesock, client_ip, client_port);
            //     exit(0); // 僵尸状态
            // }
            // close(servicesock); // 如果父进程关闭servicesock,会不会影响子进程??下节课
            // 父进程
            // waitpid(); //
        }
    }
    ~TcpServer() {
    
    }

private:
    uint16_t _port;
    std::string _ip;
    int _listensock;
    std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};

tcp_server.cc

Ce code est une implémentation d'un serveur TCP.

#include "tcp_server.hpp"
#include <memory>

static void usage(std::string proc)
{
    
    
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    
    
    if(argc != 2)
    {
    
    
    //usage(argv[0]);:如果参数个数不为 2,调用 usage 函数打印程序的使用方法。
        usage(argv[0]);
        exit(1);
    }
    //如果参数个数为 2,将第二个参数转换为整数类型并赋值给 port 变量。
    uint16_t port = atoi(argv[1]);
    //创建一个 TcpServer 对象,将监听的端口号作为参数传入构造函数。
    std::unique_ptr<TcpServer> svr(new TcpServer(port));
    //初始化 TcpServer 对象。
    svr->initServer();
    //启动 TcpServer 对象。
    svr->start();
    return 0;
}

tcp_client.cc (client)

Ce code est la fonction principale d'un serveur TCP, faisant principalement les choses suivantes :

  • Obtenez le numéro de port que le serveur écoute via les paramètres de ligne de commande. Si le nombre de paramètres n'est pas 2, affichez la méthode d'utilisation et quittez le programme.
  • Créez un objet TcpServer et transmettez le numéro de port, puis stockez le pointeur d'objet dans un pointeur intelligent std :: unique_ptr pour vous assurer que la mémoire peut être automatiquement libérée lorsque le programme se termine.
  • Appelez la méthode initServer() de l'objet TcpServer pour initialiser le serveur. Cette méthode est principalement utilisée pour créer une socket d'écoute et définir l'adresse du serveur.
  • Appelez la méthode start() de l'objet TcpServer pour démarrer le serveur. Cette méthode traite principalement les demandes de connexion des clients via une boucle d'événements, reçoit les demandes des clients et y répond.
  • Retourner 0 signifie que le programme s'est terminé normalement.
#include "tcp_server.hpp"
#include <memory>

static void usage(std::string proc)
{
    
    
    std::cout << "\nUsage: " << proc << " port\n" << std::endl;
}

// ./tcp_server port
int main(int argc, char *argv[])
{
    
    
    if(argc != 2)
    {
    
    
        usage(argv[0]);
        exit(1);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<TcpServer> svr(new TcpServer(port));
    svr->initServer();
    svr->start();
    return 0;
}

log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志是有日志级别的
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

const char *gLevelMap[] = {
    
    
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./threadpool.log"

// 完整的日志功能,至少: 日志等级 时间 支持用户自定义(日志内容, 文件行,文件名)
void logMessage(int level, const char *format, ...)
{
    
    
#ifndef DEBUG_SHOW
    if(level== DEBUG) return;
#endif
    // va_list ap;
    // va_start(ap, format);
    // while()
    // int x = va_arg(ap, int);
    // va_end(ap); //ap=nullptr
    char stdBuffer[1024]; //标准部分
    time_t timestamp = time(nullptr);
    // struct tm *localtime = localtime(&timestamp);
    snprintf(stdBuffer, sizeof stdBuffer, "[%s] [%ld] ", gLevelMap[level], timestamp);

    char logBuffer[1024]; //自定义部分
    va_list args;
    va_start(args, format);
    // vprintf(format, args);
    vsnprintf(logBuffer, sizeof logBuffer, format, args);
    va_end(args);

    // FILE *fp = fopen(LOGFILE, "a");
    printf("%s%s\n", stdBuffer, logBuffer);
    // fprintf(fp, "%s%s\n", stdBuffer, logBuffer);
    // fclose(fp);
}

Je suppose que tu aimes

Origine blog.csdn.net/weixin_47952981/article/details/129986819
conseillé
Classement