TCP套接字编程

一、TCP_socket的API接口详解

下面是程序中用到的socket API,且这些函数都在sys/socket.h头文件中

1.1socket()

2、bind()

3、listen()

4、accept()

5、connect()

注意:客户端会在发起connect的时候,进行随机端口绑定。

二、单进程版的TCP服务端和客户端的连接以及实现

2.1TCP服务端

tcpServer.hpp文件:

cpp 复制代码
#pragma once

#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include "Log.hpp"

log lg;

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5; // 这个数字一般不要设置的太大
#define BUFFER_SIZE 4096

enum
{
    usage_error = 1,
    socket_error,
    bind_error,
    listen_error
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
        : _listen_sockfd(defaultfd)
        , _port(port)
        , _ip(ip)
    {
    }

    void InitServer()
    {
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            // 创建套接字失败
            lg.logmessage(Fatal, "create listen sockfd fail, errno: %d, errmessage: %s", errno, strerror(errno));
            exit(socket_error);
        }
        else
        {
            // 创建成功
            lg.logmessage(Info, "create listen socket success, sockfd:%d", _listen_sockfd);

            // 填充网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            inet_aton(_ip.c_str(), &(local.sin_addr));
            // 或者把它写成
            // local.sin_addr.s_addr = INADDR_ANY;

            // 绑定
            int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                lg.logmessage(Fatal, "bind fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(bind_error);
            }
            else
            {
                // 绑定套接字成功
                lg.logmessage(Info, "bind socket success, _listen_sockfd:%d", _listen_sockfd);
            }

            // tcp是面向连接的,服务器一般是比较被动的,也就是服务器一直处于一种一直在等待连接到来的状态
            if (listen(_listen_sockfd, backlog) < 0)
            {
                lg.logmessage(Fatal, "listen fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(listen_error);
            }
            else
            {
                // 监听成功
                lg.logmessage(Info, "listen socket success, _listen_sockfd:%d", _listen_sockfd);
            }
        }
    }

    void RunServer()
    {
        lg.logmessage(Info, "tcpServer id running......");
        while (true)
        {
            // 1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg.logmessage(Warning, "accept fail, errno: %d, errmessage: %s", errno, strerror(errno));
                continue;
            }
            else
            {
                // 获取客户端的ip地址和端口号
                uint16_t clientport = ntohs(client.sin_port);
                char clientip[32];
                inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

                // 2、根据新链接来进行通信
                lg.logmessage(Info, "get a new link..., sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip, clientport);

                // 通信场景一:发什么返回什么
                Server(sockfd, clientip, clientport);
                close(sockfd);
            }
        }
    }

    void Server(const int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        char buffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n < 0)
            {
                lg.logmessage(Warning, "read error, sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip.c_str(), clientport);
                break;
            }
            else if (n == 0)
            {
                lg.logmessage(Info, "%s:%d quit,server close, sockfd:%d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                buffer[n] = '\0';
                std::cout << "client says#:" << buffer << std::endl;

                std::string echo_string = "tcpServer says#:";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listen_sockfd;
    uint16_t _port;
    std::string _ip;
};

Log.hpp文件:

cpp 复制代码
#pragma once

#include <iostream>
#include <stdarg.h>
#include <time.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include <fcntl.h>
#include<string.h>

#define SIZE 1024

// 设置日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define logFile "log.txt"

class log
{
public:
    log()
    {
        PrintMethod = Screen;
    }
    void Enable(int method)
    {
        PrintMethod = method;
    }

    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        default:
            return "None";
        }
    }

    void logmessage(int level, const char *format, ...) // 后面的省略号表示可变参数
    {

        char leftbuffer[SIZE];

        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);

        snprintf(leftbuffer, sizeof(leftbuffer), "[%s],[%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        char rightbuffer[SIZE];

        va_list s;
        va_start(s, format);
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        // printf("%d-%d-%d %d:%d:%d\n",ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday, ctime->tm_hour,ctime->tm_min,ctime->tm_sec);

        //printf("%s", logtxt);
        PrintLog(level,logtxt);
        // 格式:默认部分+自定义部分(可变参数部分)
    }

    void PrintLog(int level, const std::string& logtxt)
    {
        switch(PrintMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
            break;
            case Onefile:
                printOneFile(logFile, logtxt);
            break;
            case Classfile:
                printClassFile(level, logtxt);
            break;
            default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    { 
        int fd = open(logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd < 0)
        {
            return  ;
        }
        write(fd,logtxt.c_str(),logtxt.size());
        close(fd);

    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = logFile;
        filename += ".";
        filename += levelToString(level);

    }
    ~log()
    {
    }
private:
    int PrintMethod;

};

int sum(int n, ...)
{
    va_list s;
    va_start(s, n);

    int sum = 0;
    while (n)
    {
        sum += va_arg(s, int);
        n--;
    }

    va_end(s);
    return sum;
}

main.cc文件:

cpp 复制代码
#include "tcpServer.hpp"

void Usage(std::string process)
{
    std::cout << "\n\rUsage:" << process << " port[1024+]" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(usage_error);
    }
    uint16_t port = std::stoi(argv[1]);
    std::unique_ptr<TcpServer> ts(new TcpServer(port));

    ts->InitServer();
    ts->RunServer();
    return 0;
}

2.2TCP客户端

tcpClient.cc文件:

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>

#define IN_BUFFER_SIZE 4096

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage:" << proc << "serverip serverport\n"
              << std::endl;
}

//./tcpClient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        return 1;
    }

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    // tcp客户端也要进行绑定,但是也是系统进行绑定,绑定随机端口
    // 客户端在发起connect的时候,进行随机绑定
    int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {
        // 连接失败
        std::cerr << "connet error..." << std::endl;
        return 2;
    }

    std::string message;
    // 链接成功
    while (true)
    {
        std::cout << "please Enter@:";
        std::getline(std::cin, message);

        write(sockfd, message.c_str(), message.size());

        char in_buffer[IN_BUFFER_SIZE];
        int n = read(sockfd, in_buffer, sizeof(in_buffer));
        if (n > 0)
        {
            in_buffer[n] = '\0';
            std::cout << in_buffer << std::endl;
        }
    }

    close(sockfd);
    return 0;
}

2.3Makefile文件

bash 复制代码
.PHONY:all
all:tcpServer tcpClient

tcpServer:main.cc
	g++ -o $@ $^ -std=c++11
tcpClient:tcpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -rf tcpClient tcpServer

2.4运行结果以及总结:

三、多进程版本的TCP服务端和客户端的连接以及实现

注意:多进程版本的只需要在服务端的代码上进行更改,客户端的代码就和前面的一样

TCP服务端:

tcpServer.hpp文件:

cpp 复制代码
#pragma once
#include<unistd.h>
#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include<sys/wait.h>
#include<signal.h>
#include "Log.hpp"

log lg;

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5; // 这个数字一般不要设置的太大
#define BUFFER_SIZE 4096

enum
{
    usage_error = 1,
    socket_error,
    bind_error,
    listen_error
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
        : _listen_sockfd(defaultfd)
        , _port(port)
        , _ip(ip)
    {
    }

    void InitServer()
    {
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            // 创建套接字失败
            lg.logmessage(Fatal, "create listen sockfd fail, errno: %d, errmessage: %s", errno, strerror(errno));
            exit(socket_error);
        }
        else
        {
            // 创建成功
            lg.logmessage(Info, "create listen socket success, sockfd:%d", _listen_sockfd);

            // 填充网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            inet_aton(_ip.c_str(), &(local.sin_addr));
            // 或者把它写成
            // local.sin_addr.s_addr = INADDR_ANY;

            // 绑定
            int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                lg.logmessage(Fatal, "bind fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(bind_error);
            }
            else
            {
                // 绑定套接字成功
                lg.logmessage(Info, "bind socket success, _listen_sockfd:%d", _listen_sockfd);
            }

            // tcp是面向连接的,服务器一般是比较被动的,也就是服务器一直处于一种一直在等待连接到来的状态
            if (listen(_listen_sockfd, backlog) < 0)
            {
                lg.logmessage(Fatal, "listen fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(listen_error);
            }
            else
            {
                // 监听成功
                lg.logmessage(Info, "listen socket success, _listen_sockfd:%d", _listen_sockfd);
            }
        }
    }

    void RunServer()
    {
        lg.logmessage(Info, "tcpServer id running......");
        while (true)
        {
            // 1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg.logmessage(Warning, "accept fail, errno: %d, errmessage: %s", errno, strerror(errno));
                continue;
            }
            else
            {
                //方法二、不用等待
                //signal(SIGCHLD, SIG_IGN);

                // 获取客户端的ip地址和端口号
                uint16_t clientport = ntohs(client.sin_port);
                char clientip[32];
                inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

                // 2、根据新链接来进行通信
                lg.logmessage(Info, "get a new link..., sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip, clientport);

                // 通信场景二:发什么返回什么(多进程版)
                pid_t id = fork();
                if(id == 0)
                {
                    //子进程
                    close(_listen_sockfd);
                    //实现并发执行的方案一,让该子进程的孙子进程去执行代码
                    if(fork() > 0)
                    {
                        exit(0);
                    }
                    //孙子进程执行---任务执行完,孙子进程会被系统领养
                    Server(sockfd, clientip, clientport);
                    close(sockfd);
                    exit(0);
                }
                //sockfd已经被子进程拿到了,所以我们需要关闭父进程的sockfd,不然父进程的文件描述符会越用越少
                close(sockfd);
                //父进程
                pid_t rid = waitpid(id, nullptr, 0);
            }
        }
    }

    void Server(const int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        char buffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n < 0)
            {
                lg.logmessage(Warning, "read error, sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip.c_str(), clientport);
                break;
            }
            else if (n == 0)
            {
                lg.logmessage(Info, "%s:%d quit,server close, sockfd:%d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                buffer[n] = '\0';
                std::cout << "client says#:" << buffer << std::endl;

                std::string echo_string = "tcpServer says#:";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listen_sockfd;
    uint16_t _port;
    std::string _ip;
};

运行结果以及总结:

四、多线程版本的TCP服务端和客户端的连接以及实现

注意:多线程版也只需要更改服务端的代码即可:

TCP服务端:

tcpServer.hpp文件:

cpp 复制代码
#pragma once

#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include<pthread.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include "Log.hpp"

log lg;

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5; // 这个数字一般不要设置的太大
#define BUFFER_SIZE 4096

enum
{
    usage_error = 1,
    socket_error,
    bind_error,
    listen_error
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t)
    :sockfd(fd)
    ,clientip(ip)
    ,clientport(port)
    ,_ts(t)
    {
    }
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *_ts;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
        : _listen_sockfd(defaultfd)
        , _port(port)
        , _ip(ip)
    {
    }

    void InitServer()
    {
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            // 创建套接字失败
            lg.logmessage(Fatal, "create listen sockfd fail, errno: %d, errmessage: %s", errno, strerror(errno));
            exit(socket_error);
        }
        else
        {
            // 创建成功
            lg.logmessage(Info, "create listen socket success, sockfd:%d", _listen_sockfd);

            // 填充网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            inet_aton(_ip.c_str(), &(local.sin_addr));
            // 或者把它写成
            // local.sin_addr.s_addr = INADDR_ANY;

            // 绑定
            int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                lg.logmessage(Fatal, "bind fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(bind_error);
            }
            else
            {
                // 绑定套接字成功
                lg.logmessage(Info, "bind socket success, _listen_sockfd:%d", _listen_sockfd);
            }

            // tcp是面向连接的,服务器一般是比较被动的,也就是服务器一直处于一种一直在等待连接到来的状态
            if (listen(_listen_sockfd, backlog) < 0)
            {
                lg.logmessage(Fatal, "listen fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(listen_error);
            }
            else
            {
                // 监听成功
                lg.logmessage(Info, "listen socket success, _listen_sockfd:%d", _listen_sockfd);
            }
        }
    }

    static void *Routinue(void *args)
    {
        //线程分离
        pthread_detach(pthread_self());
        ThreadData * td = static_cast<ThreadData *>(args);
        td->_ts->Server(td->sockfd, td->clientip, td->clientport);
        delete td;
        return nullptr;
    }

    void RunServer()
    {
        lg.logmessage(Info, "tcpServer id running......");
        while (true)
        {
            // 1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg.logmessage(Warning, "accept fail, errno: %d, errmessage: %s", errno, strerror(errno));
                continue;
            }
            else
            {
                // 获取客户端的ip地址和端口号
                uint16_t clientport = ntohs(client.sin_port);
                char clientip[32];
                inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

                // 2、根据新链接来进行通信
                lg.logmessage(Info, "get a new link..., sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip, clientport);

                // 通信场景三:发什么返回什么(多线程版本)
               ThreadData *td = new ThreadData(sockfd, clientip, clientport, this);
               pthread_t tid;
               pthread_create(&tid, nullptr, Routinue, td);
            }
        }
    }

    void Server(const int sockfd, const std::string &clientip, const uint16_t &clientport)
    {
        char buffer[BUFFER_SIZE];
        while (true)
        {
            ssize_t n = read(sockfd, buffer, sizeof(buffer));
            if (n < 0)
            {
                lg.logmessage(Warning, "read error, sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip.c_str(), clientport);
                break;
            }
            else if (n == 0)
            {
                lg.logmessage(Info, "%s:%d quit,server close, sockfd:%d", clientip.c_str(), clientport, sockfd);
                break;
            }
            else
            {
                buffer[n] = '\0';
                std::cout << "client says#:" << buffer << std::endl;

                std::string echo_string = "tcpServer says#:";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listen_sockfd;
    uint16_t _port;
    std::string _ip;
};

运行结果以及总结:

五、线程池版本的TCP服务端和客户端的连接以及实现

注意:进程池与前面一样,只需要加入进程池即可

TCP服务端:

(1)tcpServer.hpp文件:

cpp 复制代码
#pragma once

#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include<pthread.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include "Log.hpp"
#include "threadpool.hpp"
#include "Task.hpp"

log lg;

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5; // 这个数字一般不要设置的太大
#define BUFFER_SIZE 4096

enum
{
    usage_error = 1,
    socket_error,
    bind_error,
    listen_error
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t)
    :sockfd(fd)
    ,clientip(ip)
    ,clientport(port)
    ,_ts(t)
    {
    }
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *_ts;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
        : _listen_sockfd(defaultfd)
        , _port(port)
        , _ip(ip)
    {
    }

    void InitServer()
    {
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            // 创建套接字失败
            lg.logmessage(Fatal, "create listen sockfd fail, errno: %d, errmessage: %s", errno, strerror(errno));
            exit(socket_error);
        }
        else
        {
            // 创建成功
            lg.logmessage(Info, "create listen socket success, sockfd:%d", _listen_sockfd);

            // 填充网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            inet_aton(_ip.c_str(), &(local.sin_addr));
            // 或者把它写成
            // local.sin_addr.s_addr = INADDR_ANY;

            // 绑定
            int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                lg.logmessage(Fatal, "bind fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(bind_error);
            }
            else
            {
                // 绑定套接字成功
                lg.logmessage(Info, "bind socket success, _listen_sockfd:%d", _listen_sockfd);
            }

            // tcp是面向连接的,服务器一般是比较被动的,也就是服务器一直处于一种一直在等待连接到来的状态
            if (listen(_listen_sockfd, backlog) < 0)
            {
                lg.logmessage(Fatal, "listen fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(listen_error);
            }
            else
            {
                // 监听成功
                lg.logmessage(Info, "listen socket success, _listen_sockfd:%d", _listen_sockfd);
            }
        }
    }

    void RunServer()
    {
        //启动线程池
        ThreadPool<Task>::GetInstance()->start();

        lg.logmessage(Info, "tcpServer id running......");
        while (true)
        {
            // 1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg.logmessage(Warning, "accept fail, errno: %d, errmessage: %s", errno, strerror(errno));
                continue;
            }
            else
            {
                // 获取客户端的ip地址和端口号
                uint16_t clientport = ntohs(client.sin_port);
                char clientip[32];
                inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

                // 2、根据新链接来进行通信
                lg.logmessage(Info, "get a new link..., sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip, clientport);

                // 通信场景四:执行任务(线程池版本)
               ThreadPool<Task>::GetInstance()->push(Task(sockfd, clientip, clientport));
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listen_sockfd;
    uint16_t _port;
    std::string _ip;
};

(2)进程池代码---threadpool.hpp文件:

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>
#include "Task.hpp"

struct threadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defaultnum = 10;

template <typename T>
class ThreadPool
{
protected:
    void lock()
    {
        pthread_mutex_lock(&_mutex);
    }

    void unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void ThreadWakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void ThreadSleep()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }

    bool QueueIsEmpty()
    {
        return _tasks.empty();
    }

    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : _threads)
        {
            if (ti.tid == tid)
            {
                return ti.name;
            }
        }
        return "None";
    }

public:
    ThreadPool(int num = defaultnum)
        : _threads(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    static void *HandlerTask(void *args) // 使用static把该函数变成静态成员函数,而不再是类内成员函数,从而没有了this指针
    {
        // 因为加上了static,已经是全局的静态函数,而不再是类内的函数了,无法直接访问类内成员,所以要传this指针
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());

        while (true)
        {
            tp->lock();

            while (tp->QueueIsEmpty())
            {
                tp->ThreadSleep();
            }

            // 取出任务
            T t = tp->pop();

            tp->unlock();


            t();

            sleep(1);
        }
    }

    void start()
    {
        for (int i = 0; i < _threads.size(); i++)
        {
            _threads[i].name = "thread-" + std::to_string(i);
            pthread_create(&(_threads[i].tid), nullptr, HandlerTask, this);
        }
    }

    T pop()
    {
        T t = _tasks.front();
        _tasks.pop();

        return t;
    }

    void push(const T &task)
    {
        lock();

        _tasks.push(task);
        // 唤醒休眠的进程
        ThreadWakeup();

        unlock();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == _tp) 
        {
            pthread_mutex_lock(&_lock);
            if (nullptr == _tp)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                _tp = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&_lock);
        }

        return _tp;
    }

    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

protected:
    std::vector<threadInfo> _threads; // 用数组维护线程池
    std::queue<T> _tasks;             // 任务队列

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool<T> *_tp;
    static pthread_mutex_t _lock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;

(3)任务代码Task.hpp文件:

cpp 复制代码
#pragma once
#pragma once
#include <iostream>
#include <string>
#include "Log.hpp"


#define BUFFER_SIZE 4096
extern log lg;

class Task
{
public:
    Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
        : _sockfd(sockfd), _clientip(clientip), _clientport(clientport)
    {
    }
    Task()
    {
    }
    void Run()
    {
        // 测试代码
        char buffer[BUFFER_SIZE];
        ssize_t n = read(_sockfd, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = "tcpserver echo#";
            echo_string += buffer;

            n = write(_sockfd, echo_string.c_str(), echo_string.size());
            if (n < 0)
            {
                lg.logmessage(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));
            }
        }
        else if (n == 0)
        {
            lg.logmessage(Info, "%s:%d quit, server close sockfd: %d", _clientip.c_str(), _clientport, _sockfd);
        }
        else
        {
            lg.logmessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d"
                , _sockfd, _clientip.c_str(), _clientport);
        }
        
        close(_sockfd);
    }
    void operator()()
    {
        Run();
    }
    ~Task()
    {
    }

private:
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
};

(4)运行结果以及总结

六、tcp_socket套接字应用之字典的实现(守护进程版本)

6.1客户端代码(tcpClient.cc文件):

注意:客户端代码实现了断联后重新与服务器连接等功能

cpp 复制代码
#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>

#define IN_BUFFER_SIZE 4096

void Usage(const std::string &proc)
{
    std::cout << "\n\rUsage:" << proc << "serverip serverport\n"
              << std::endl;
}

//./tcpClient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }

    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &(server.sin_addr));

    while (true)
    {
        // 创建重连模块
        int cnt = 7;
        int iscorrecting = false;
        int sockfd = 0;
        do
        {
            sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (sockfd < 0)
            {
                std::cerr << "socket error" << std::endl;
                return 1;
            }

            // tcp客户端也要进行绑定,但是也是系统进行绑定,绑定随机端口
            // 客户端在发起connect的时候,进行随机绑定
            int n = connect(sockfd, (struct sockaddr *)&server, sizeof(server));
            if (n < 0)
            {
                // 连接失败
                cnt--;
                std::cerr << "connet error...,正在尝试重新连接...[" << cnt << "/7]"  << std::endl;
                iscorrecting = true;
                sleep(1);
            }
            else
            {
                break;
            }
        } while (cnt && iscorrecting);

        // 五次重连失败后:
        if (cnt == 0)
        {
            std::cerr << "user offline" << std::endl;
            break;
        }

        // 链接成功

        while (true)
        {
            std::string message;
            std::cout << "please Enter@:";
            std::getline(std::cin, message);

            int n = write(sockfd, message.c_str(), message.size());
            if (n < 0)
            {
                iscorrecting = true;
                std::cerr << "write error..." << std::endl;
                break;
            }

            char in_buffer[IN_BUFFER_SIZE];
            n = read(sockfd, in_buffer, sizeof(in_buffer));
            if (n > 0)
            {
                in_buffer[n] = '\0';
                 std::cout << in_buffer << std::endl;
            }
            else
            {
                break;
            }
        }
        close(sockfd);
    }

    return 0;
}

6.2服务端代码:

tcpServer.hpp文件:

cpp 复制代码
#pragma once

#include <stdio.h>
#include <iostream>
#include <memory>
#include <vector>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include<signal.h>
#include <arpa/inet.h>
#include <string>
#include <arpa/inet.h>
#include<pthread.h>
#include <strings.h>
#include <functional>
#include <stdbool.h>
#include "Log.hpp"
#include "threadpool.hpp"
#include "Task.hpp"
#include "Daemon.hpp"

log lg;

const int defaultfd = -1;
const std::string defaultip = "0.0.0.0";
const int backlog = 5; // 这个数字一般不要设置的太大
#define BUFFER_SIZE 4096

enum
{
    usage_error = 1,
    socket_error,
    bind_error,
    listen_error
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &port, TcpServer *t)
    :sockfd(fd)
    ,clientip(ip)
    ,clientport(port)
    ,_ts(t)
    {
    }
public:
    int sockfd;
    std::string clientip;
    uint16_t clientport;
    TcpServer *_ts;
};

class TcpServer
{
public:
    TcpServer(const uint16_t &port, const std::string &ip = defaultip)
        : _listen_sockfd(defaultfd)
        , _port(port)
        , _ip(ip)
    {
    }

    void InitServer()
    {
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            // 创建套接字失败
            lg.logmessage(Fatal, "create listen sockfd fail, errno: %d, errmessage: %s", errno, strerror(errno));
            exit(socket_error);
        }
        else
        {
            // 创建成功
            lg.logmessage(Info, "create listen socket success, sockfd:%d", _listen_sockfd);

            // 填充网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            inet_aton(_ip.c_str(), &(local.sin_addr));
            // 或者把它写成
            // local.sin_addr.s_addr = INADDR_ANY;

            // 绑定
            int n = bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local));
            if (n < 0)
            {
                lg.logmessage(Fatal, "bind fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(bind_error);
            }
            else
            {
                // 绑定套接字成功
                lg.logmessage(Info, "bind socket success, _listen_sockfd:%d", _listen_sockfd);
            }

            // tcp是面向连接的,服务器一般是比较被动的,也就是服务器一直处于一种一直在等待连接到来的状态
            if (listen(_listen_sockfd, backlog) < 0)
            {
                lg.logmessage(Fatal, "listen fail, errno: %d, errmessage: %s", errno, strerror(errno));
                exit(listen_error);
            }
            else
            {
                // 监听成功
                lg.logmessage(Info, "listen socket success, _listen_sockfd:%d", _listen_sockfd);
            }
        }
    }

    void RunServer()
    {
        //守护进程
        Daemon();
        //忽略sigpipe信号,防止突然一端断开后服务区终止
        signal(SIGPIPE, SIG_IGN);
        //启动线程池
        ThreadPool<Task>::GetInstance()->start();

        lg.logmessage(Info, "tcpServer id running......");
        while (true)
        {
            // 1、获取新连接
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                lg.logmessage(Warning, "accept fail, errno: %d, errmessage: %s", errno, strerror(errno));
                continue;
            }
            else
            {
                // 获取客户端的ip地址和端口号
                uint16_t clientport = ntohs(client.sin_port);
                char clientip[32];
                inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));

                // 2、根据新链接来进行通信
                lg.logmessage(Info, "get a new link..., sockfd:%d, clientip:%s, clientport: %d"
                    , sockfd, clientip, clientport);

                // 通信场景四:执行任务(线程池版本)
               ThreadPool<Task>::GetInstance()->push(Task(sockfd, clientip, clientport));
            }
        }
    }

    ~TcpServer()
    {
    }

private:
    int _listen_sockfd;
    uint16_t _port;
    std::string _ip;
};

threadpool.hpp文件:

cpp 复制代码
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>
#include <stdbool.h>
#include <time.h>
#include "Task.hpp"

struct threadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defaultnum = 10;

template <typename T>
class ThreadPool
{
protected:
    void lock()
    {
        pthread_mutex_lock(&_mutex);
    }

    void unlock()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void ThreadWakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void ThreadSleep()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }

    bool QueueIsEmpty()
    {
        return _tasks.empty();
    }

    std::string GetThreadName(pthread_t tid)
    {
        for (const auto &ti : _threads)
        {
            if (ti.tid == tid)
            {
                return ti.name;
            }
        }
        return "None";
    }

public:
    ThreadPool(int num = defaultnum)
        : _threads(num)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    static void *HandlerTask(void *args) // 使用static把该函数变成静态成员函数,而不再是类内成员函数,从而没有了this指针
    {
        // 因为加上了static,已经是全局的静态函数,而不再是类内的函数了,无法直接访问类内成员,所以要传this指针
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        std::string name = tp->GetThreadName(pthread_self());

        while (true)
        {
            tp->lock();

            while (tp->QueueIsEmpty())
            {
                tp->ThreadSleep();
            }

            // 取出任务
            T t = tp->pop();

            tp->unlock();


            t();

            sleep(1);
        }
    }

    void start()
    {
        for (int i = 0; i < _threads.size(); i++)
        {
            _threads[i].name = "thread-" + std::to_string(i);
            pthread_create(&(_threads[i].tid), nullptr, HandlerTask, this);
        }
    }

    T pop()
    {
        T t = _tasks.front();
        _tasks.pop();

        return t;
    }

    void push(const T &task)
    {
        lock();

        _tasks.push(task);
        // 唤醒休眠的进程
        ThreadWakeup();

        unlock();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    static ThreadPool<T> *GetInstance()
    {
        if (nullptr == _tp) 
        {
            pthread_mutex_lock(&_lock);
            if (nullptr == _tp)
            {
                std::cout << "log: singleton create done first!" << std::endl;
                _tp = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&_lock);
        }

        return _tp;
    }

    ThreadPool(const ThreadPool<T> &) = delete;
    const ThreadPool<T> &operator=(const ThreadPool<T> &) = delete;

protected:
    std::vector<threadInfo> _threads; // 用数组维护线程池
    std::queue<T> _tasks;             // 任务队列

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool<T> *_tp;
    static pthread_mutex_t _lock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;

template <class T>
pthread_mutex_t ThreadPool<T>::_lock = PTHREAD_MUTEX_INITIALIZER;

Task.hpp文件:

cpp 复制代码
#pragma once
#pragma once
#include <iostream>
#include <string>
#include<unordered_map>
#include "Log.hpp"
#include "Init.hpp"


#define BUFFER_SIZE 4096
extern log lg;
Init init;

class Task
{
public:
    Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
        : _sockfd(sockfd), _clientip(clientip), _clientport(clientport)
    {
    }
    Task()
    {
    }
    void Run()
    {
        // 测试代码
        char buffer[BUFFER_SIZE];
        ssize_t n = read(_sockfd, buffer, sizeof(buffer));
        if (n > 0)
        {
            buffer[n] = 0;
            std::cout << "client key# " << buffer << std::endl;
            std::string echo_string = init.translation(buffer);

            n = write(_sockfd, echo_string.c_str(), echo_string.size());
            if (n < 0)
            {
                lg.logmessage(Warning, "write error, errno : %d, errstring: %s", errno, strerror(errno));
            }
        }
        else if (n == 0)
        {
            lg.logmessage(Info, "%s:%d quit, server close sockfd: %d", _clientip.c_str(), _clientport, _sockfd);
        }
        else
        {
            lg.logmessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d"
                , _sockfd, _clientip.c_str(), _clientport);
        }
        
        close(_sockfd);
    }
    void operator()()
    {
        Run();
    }
    ~Task()
    {
    }

private:
    int _sockfd;
    std::string _clientip;
    uint16_t _clientport;
};

Log.hpp文件:

cpp 复制代码
#pragma once

#include <iostream>
#include <stdarg.h>
#include <time.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
#include <fcntl.h>
#include<string.h>

#define SIZE 1024

// 设置日志等级
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4

#define Screen 1
#define Onefile 2
#define Classfile 3

#define logFile "log.txt"

class log
{
public:
    log()
    {
        PrintMethod = Screen;
        path = "./log/";
    }
    void Enable(int method)
    {
        PrintMethod = method;
    }

    std::string levelToString(int level)
    {
        switch (level)
        {
        case Info:
            return "Info";
        case Debug:
            return "Debug";
        case Warning:
            return "Warning";
        case Error:
            return "Error";
        default:
            return "None";
        }
    }

    void logmessage(int level, const char *format, ...) // 后面的省略号表示可变参数
    {

        char leftbuffer[SIZE];

        time_t t = time(nullptr);
        struct tm *ctime = localtime(&t);

        snprintf(leftbuffer, sizeof(leftbuffer), "[%s],[%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),
                 ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday,
                 ctime->tm_hour, ctime->tm_min, ctime->tm_sec);

        char rightbuffer[SIZE];

        va_list s;
        va_start(s, format);
        vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);
        va_end(s);

        char logtxt[SIZE * 2];
        snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);

        // printf("%d-%d-%d %d:%d:%d\n",ctime->tm_year + 1900, ctime->tm_mon, ctime->tm_mday, ctime->tm_hour,ctime->tm_min,ctime->tm_sec);

        //printf("%s", logtxt);
        PrintLog(level,logtxt);
        // 格式:默认部分+自定义部分(可变参数部分)
    }

    void PrintLog(int level, const std::string& logtxt)
    {
        switch(PrintMethod)
        {
            case Screen:
                std::cout << logtxt << std::endl;
            break;
            case Onefile:
                printOneFile(logFile, logtxt);
            break;
            case Classfile:
                printClassFile(level, logtxt);
            break;
            default:
            break;
        }
    }
    void printOneFile(const std::string &logname, const std::string &logtxt)
    { 
        std::string _logname = path + logname;
        int fd = open(_logname.c_str(),O_WRONLY|O_CREAT|O_APPEND,0666);
        if(fd < 0)
        {
            return  ;
        }
        write(fd,logtxt.c_str(),logtxt.size());
        close(fd);

    }
    void printClassFile(int level, const std::string &logtxt)
    {
        std::string filename = logFile;
        filename += ".";
        filename += levelToString(level);
        printOneFile(filename, logtxt);
    }
    ~log()
    {
    }
private:
    int PrintMethod;
    std::string path;
};

Init.hpp文件:

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <unordered_map>
#include <stdbool.h>
#include "Log.hpp"

extern log lg;

const std::string dictname = "./dict.txt";
const std::string sep = ":";

static bool Split(std::string &s, std::string *English, std::string *Chinese)
{
    auto pos = s.find(sep);
    if (pos == std::string::npos)
    {
        return false;
    }
    else
    {
        *English = s.substr(0, pos);
        *Chinese = s.substr(pos + 1);
    }
    return true;
}

class Init
{
public:
    Init()
    {
        std::ifstream in(dictname);
        if (!in.is_open())
        {
            lg.logmessage(Fatal, "dict file open error");
            exit(1);
        }

        std::string line;
        while (std::getline(in, line))
        {
            // 切割
            std::string English;
            std::string Chinese;
            Split(line, &English, &Chinese);
            dict.insert({English, Chinese});
        }

        in.close();
    }

    std::string translation(const std::string &key)
    {
        auto iter = dict.find(key);
        if (iter == dict.end())
        {
            return "Unknow";
        }
        else
        {
            return iter->second;
        }
    }

private:
    std::unordered_map<std::string, std::string> dict;
};

守护进程(Daemon.hpp文件):

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdlib>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>

// Linux系统提供的垃圾文件
const std::string nullfile = "/dev/null";

void Daemon(const std::string &cwd = " ")
{
    // 忽略其他异常信号
    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 进来是父进程,出去就是子进程了(让自己不要成为组长)
    if (fork() > 0)
    {
        exit(0);
    }

    // 让自己成为新的会话
    setsid();

    // 更改守护进程的目录
    if (!cwd.empty())
    {
        chdir(cwd.c_str());
    }

    // 关标准输入标准输出和标准错误全部重定向至/dev/null中
    int fd = open(nullfile.c_str(), O_RDWR);
    if (fd > 0)
    {
        dup2(fd, 1);
        dup2(fd, 0);
        dup2(fd, 3);
        close(fd);
    }
}

main.cc文件:

cpp 复制代码
#include "tcpServer.hpp"

void Usage(std::string process)
{
    std::cout << "\n\rUsage:" << process << " port[1024+]" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(usage_error);
    }
    uint16_t port = std::stoi(argv[1]);

    // 开启日志功能
    lg.Enable(Classfile);

    std::unique_ptr<TcpServer> ts(new TcpServer(port));

    ts->InitServer();
    ts->RunServer();
    return 0;
}

文本文件(dict.txt)文件:

bash 复制代码
apple:苹果
love:爱
sort:排序
compare:比较
miss:想念
kiss:接吻
like:喜欢
mother:母亲
father:父亲
parent:父母
banana:香蕉

6.3运行结果以及总结:

相关推荐
我不是懒洋洋2 小时前
【数据结构】二叉树链式结构的实现(二叉树的遍历、使用二叉树的基本方法、二叉树的创建和销毁)
c语言·数据结构·c++·经验分享·算法·链表·visual studio
Suckerbin2 小时前
vulnyx-Reset靶场渗透
网络·web安全·网络安全
上海合宙LuatOS2 小时前
LuatOS 课程-011 讲:GNSS应用开发
网络·物联网·lua·luatos
heiqizero2 小时前
spark01-创建RDD
linux·前端·python
共享家95272 小时前
轻量级日志模块实现:策略模式 + RAII 的工程化实践
linux·运维·服务器
day day day ...2 小时前
Maven 项目中导入依赖的各种场景、方法、常见问题及解决办法
java·php·maven
水木流年追梦2 小时前
CodeTop Top 300 热门题目8-字符串解码
linux·运维·服务器·前端·算法·leetcode
杨云龙UP2 小时前
Docker MySQL 5.7 全库备份到异地服务器实践记录_20260427
linux·运维·服务器·数据库·mysql·docker·容器
玖別ԅ(¯﹃¯ԅ)2 小时前
C++ Qt + OpenCV 实现本地人脸识别系统:摄像头采集、ONNX模型加载、人脸库比对完整流程
c++·qt