UDP/TCP --- Socket编程

news2024/11/17 17:50:00

        本篇将使用 Linux 中的系统调用来实现模拟 TCP 和 UDP 的通信过程,其中只对 UDP 和 TCP 进行了简单的介绍,本篇主要实现的是代码,至于 UDP 和 TCP 的详细讲解将会在之后的文章中给出。

        本篇给出的 tcp 和 udp 的代码中的 echo 都是测试连接是否成功的代码,之后的代码都是在 echo 代码的基础上修改实现了不同功能的代码。目录如下:

目录

网络字节序/Socket编程接口

1. socket 常见 API

2. sockaddr 结构

UDP Socket编程

1. echo server

2. Dict server

3. chat_server

TCP Socket编程

1. echo server

2. command server

网络字节序/Socket编程接口

        在内存中的多字节数据相对于内存地址有着大小端之分,磁盘文件中的多字节数据相对于文件中的偏移地址也有着大小端之分。但是对于不同的主机有的是小端存储有的是大端存储,然而在网络间通信是从一个主机传输到另一个主机,我们应该怎样区分传送过来的数据是小端数据还是大端数据呢?

        所以 TCP/IP 为了区分这两种存储方式,规定了在网络间传输数据必须都按照大端数据流进行传输(小端机器在传输前需要将数据转换成大端数据流)。因此,网络间先发出的数据是低地址,后发出的数据在高地址。

        转换函数如下:

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
// 32 位主机序列转入网络序列
uint16_t htons(uint16_t hostshort);
// 16 位主机序列转入网络序列
uint32_t ntohl(uint32_t netlong);
// 32 位网络序列转入主机序列
uint16_t ntohs(uint16_t netshort);
// 16 位网络序列转入主机序列

以上的转换函数:
h (host)代表主机
n (network)代表网络

若主机是小端字节序,这些函数将参数做相应的大小端转换然后返回;
若主机是大端字节序,这些函数不做转换,将参数原封不动的返回。

1. socket 常见 API

        一下为 socket 编程常用的接口函数,如下:

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);

// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address, socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

2. sockaddr 结构

        上文中的 socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,如 IPv4、IPv6,但是我们发现其中大部分接口都有一个结构体:struct sockaddr ,这个结构体其实有分为两个不同的结构体,struct sockaddr_in、struct sockaddr_un,前者用于网络间通信,后者用于主机内通信,如下:

        当我们将 struct sockaddr_in、struct sockaddr_un 这两种类型的数据传入接口中,只需要使用指针强转为 struct sockaddr 即可。

UDP Socket编程

        UDP 协议是在传输层中常用的一种通信协议,其主要特点如下:

        1. 传输层协议;

        2. 无连接

        3. 不可靠传输;

        4. 面向数据报;

        以上的不可靠传输我们并不能将其认定为 UDP 协议的缺点,只将其认定为 UDP 协议的一种的特性,虽然是不可靠的传输,但是 UDP 的效率相对 TCP 很高。

        注:本篇的代码量比较多,其中大部分相同的代码都放在了 echo server 中,之后的代码只会给出一些不同的代码文件。

1. echo server

        在这一小节将写一个 UDP 协议的一个测试代码,主要实现的功能为,我们向服务器发送什么信息,服务器就像我们返回什么信息。

        UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"

using namespace log_ns;

// 客户端在未来一定要知道服务器的ip port
int main(int argc, char* args[]) {
    if (argc != 3) {
        LOG(ERROR, "please input the -> ./client、ip and port\n");
        return 0;
    }
    // 获取 ip 和 port 以及 socket
    uint16_t server_port = std::stoi(args[2]);
    std::string server_ip = args[1];

    int local_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (local_socket < 0) {
        LOG(FATAL, "Create socket fail\n");
        exit(0);
    }
    LOG(INFO, "Create socket success\n");

    // 对于client的端口号,一般不让用户自己设定,而是让client OS随机选择
    // client需要bind自己的ip和port,但是client不需要显示的bind自己的ip、port
    // client在首次向服务器发送数据的时候,OS会自动给client bind上它自己的ip、port


    // 绑定服务器信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    // 客户端的ip和port不需要指定,自己会给出
    // 在这里就可以发送消息了
    while (true) {
        std::string info;
        std::cout << "Please input the info: ";
        std::getline(std::cin, info);
        if (info.size() > 0) {
            // 将消息发送出去
            int num = sendto(local_socket, info.c_str(), info.size(), 0, (struct sockaddr*)&server, len);
            if (num > 0) {
                // 收消息
                struct sockaddr_in temp;

                char buff[1024];
                int recvlen = recvfrom(local_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&temp, &len);
                if (recvlen > 0) {
                    buff[recvlen] = 0;
                    std::cout << buff << std::endl;
                } else {
                    break;
                }
            }
        } else {
            break;
        }
    }

    close(local_socket);
    return 0;
}

        UdpServer.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"

// 在这里也可以设计为 main(argc, args)多参数,指定ip和port
int main() {
    // 将日志内容打印到屏幕上
    EnableToScreen();
    // 服务器ip一般指定为0,服务器可以收到来自任意ip的信息(只要求端口对应)
    UdpServer* usvr = new UdpServer("127.0.0.1", 8899);
    usvr->Init();
    usvr->Start();
    return 0;
}

        UdpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

enum {
    SOCKET_ERROR = 1,
    BIND_ERROR
};

const int gsockfd = -1;
const uint16_t glocalport = 8888;

// 网络中的很多东西不建议直接进行拷贝,所以设计我们的类的时候
// 将其设置为不可拷贝的类
// 一般服务器主要用来进行网络数据读取和写入、IO的
// 我们可以将服务器的IO逻辑和业务逻辑解耦
class UdpServer : public nocopy {
private:
    std::string ServerEcho(struct sockaddr_in& peer) {
        // 获取发送方的ip port
        InetAdrr addr(peer);
        std::string echo("[");
        echo += addr.Ip();
        echo += " ";
        echo += std::to_string(addr.Port());
        echo += "]> ";
        return echo;
    }

public:
    // 构造函数传入ip和port
    UdpServer(const std::string& ip, uint16_t port = glocalport) 
        : _sockfd(gsockfd),
          _localip(ip),
          _localport(port),
          _isrunning(false)
    {}

    void Init() {
        // 先创建 sockfd 文件,使用如下接口的最后一个参数设置为0,
        // 会自动推测是哪个协议
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) {
            LOG(FATAL, "Create sockfd fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "Create Socket success\n");

        // 绑定我们的信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        // 将ip地址设置为0,可以进行任意ip绑定  INADDR_ANY值为0
        local.sin_addr.s_addr = INADDR_ANY; 
        // 将指定的ip转化为网络序列
        // local.sin_addr.s_addr = inet_addr(_localip.c_str());
        local.sin_port = htons(_localport);
        socklen_t len = sizeof(local);

        int n = ::bind(_sockfd, (struct sockaddr*)&local, len);
        if (n < 0) {
            LOG(FATAL, "Bind socket fail, %s\n", strerror(errno));
            exit(BIND_ERROR);
        }
        LOG(INFO, "Bind Socket success\n");
    }

    void Start() {
        _isrunning = true;
        // 需要收消息
        while (_isrunning) {
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // peer.sin_family = AF_INET;
            // peer.sin_addr.s_addr
            char buff[1024];
            int n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);
            if (n > 0) {

                buff[n] = 0;
                std::string echo = ServerEcho(peer);
                echo += buff;
                sendto(_sockfd, echo.c_str(), echo.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
        _isrunning = false;

    }

    ~UdpServer() {
        if (_sockfd > gsockfd)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    // 对于这个ip变量,也可以直接不要这个参数,直接让ip设置为0,可以接收来自任意ip的信息
    std::string _localip; 
    uint16_t _localport;
    bool _isrunning;
};

        nocopy.hpp

#pragma once
#include <iostream>

class nocopy {
public:
    nocopy() {}
    ~nocopy() {}
    nocopy(const nocopy&) = delete;
    const nocopy& operator=(const nocopy&) = delete;
};

        Log.hpp -> 日志Log程序(C++)-CSDN博客

#pragma once
#include <iostream>
#include <string>
#include <cstdarg>
#include <cstring>
#include <fstream>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>

namespace log_ns {
    enum { DEBUG = 1, INFO, WARNING, ERROR, FATAL };

    // 定义日子真正需要记录的信息
    struct LogMessage {
        std::string _level;
        int _id;
        std::string _filename;
        int _filenumber;
        std::string _curtime;
        std::string _log_message;
    };

    #define SCREEN_TYPE 1
    #define FILE_TYPE   2

    const std::string defaultlogfile = "./log.txt";

    pthread_mutex_t log_lock = PTHREAD_MUTEX_INITIALIZER;

    class Log {
    private:
        std::string LevelToString(int level) {
            switch(level) {
                case DEBUG:
                    return "DEBUG";
                case INFO:
                    return "INFO";
                case WARNING:
                    return "WARNING";
                case ERROR:
                    return "ERROR";
                case FATAL:
                    return "FATAL";
                default:
                    return "UNKNOWN";
            }
        }

        std::string CurTime() {
            // 获取当前的时间戳
            time_t curtime = time(nullptr);
            // 将当前时间戳转换成结构体
            struct tm* now = localtime(&curtime);
            char buff[128];
            snprintf(buff, sizeof(buff), "%d-%02d-%02d %02d:%02d:%02d", 
                now->tm_year + 1900,
                now->tm_mon + 1,
                now->tm_mday,
                now->tm_hour,
                now->tm_min,
                now->tm_sec
            );
            return buff;
        }

        void Flush(const LogMessage& lg) {
            // 打印日志的时候可能存在线程安全,使用锁lock住
            pthread_mutex_lock(&log_lock);
            switch(_type) {
                case SCREEN_TYPE:
                    FlushToScreen(lg);
                    break;
                case FILE_TYPE:
                    FlushToFile(lg);
                    break;
            }
            pthread_mutex_unlock(&log_lock);
        }

        void FlushToFile(const LogMessage& lg) {
            std::ofstream out;
            out.open(_logfile, std::ios::app); // 文件的操作使用追加
            if (!out.is_open()) return;
            
            char buff[2024];
            snprintf(buff ,sizeof(buff), "[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curtime.c_str(),
                lg._log_message.c_str()
            );            

            out.write(buff, strlen(buff));

            out.close();
        }

        void FlushToScreen(const LogMessage& lg) {
            printf("[%s][%d][%s][%d][%s] %s",
                lg._level.c_str(),
                lg._id,
                lg._filename.c_str(),
                lg._filenumber,
                lg._curtime.c_str(),
                lg._log_message.c_str()
            );
        }

    public:
        Log(std::string logfile = defaultlogfile)
            : _type(SCREEN_TYPE),
              _logfile(logfile)
        {}

        void Enable(int type) {
            _type = type;
        }

        void LoadMessage(std::string filename, int filenumber, int level, const char* format, ...) {
            LogMessage lg;
            lg._level = LevelToString(level);
            lg._filename = filename;
            lg._filenumber = filenumber;
            // 获取当前时间
            lg._curtime = CurTime();
            // std::cout << lg._curtime << std::endl;
            lg._id = getpid();

            // 获取可变参数
            va_list ap;
            va_start(ap, format);
            char buff[2048];
            vsnprintf(buff, sizeof(buff), format, ap);
            va_end(ap);
            lg._log_message = buff;
            // std::cout << lg._log_message;
            Flush(lg);
        }

        void ClearOurFile() {
            std::ofstream out;
            out.open(_logfile);
            out.close();
        }

        ~Log() {}
    private:
        int _type;
        std::string _logfile;
    };

    Log lg;

// LOG 宏
#define LOG(level, format, ...)                                           \
    do                                                                    \
    {                                                                     \
        lg.LoadMessage(__FILE__, __LINE__, level, format, ##__VA_ARGS__); \
    } while (0)

#define EnableToScreen()        \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)

#define EnableToFile()        \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)

// 清理文件
#define ClearFile()        \
    do                     \
    {                      \
        lg.ClearOurFile(); \
    } while (0)
}

        InetAddr.hpp

#pragma once
#include <iostream>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>

class InetAdrr {
    void ToHost(const struct sockaddr_in& addr) {
        // inet_ntoa 函数不是线程安全的函数,推荐使用 inet_ntop 函数
        // _ip = inet_ntoa(addr.sin_addr);
        char ip_buff[32];

        // 该函数是网络序列转主机序列 :network to process
        inet_ntop(AF_INET, &addr.sin_addr, ip_buff, sizeof(ip_buff));
        // 若想要将主机序列转换成网络序列使用函数 :
        // inet_pton(AF_INET, _ip.c_str(), (void*)&addr.sin_addr.s_addr); 
        _ip = ip_buff;
        _port = ntohs(addr.sin_port);
    }
public:
    InetAdrr(const struct sockaddr_in& addr) : _addr(addr)
    {
        ToHost(_addr);
    }

    std::string Ip() const {
        return _ip;
    }

    bool operator==(const InetAdrr& addr) {
        return (_port == addr._port && _ip == addr._ip);
    }

    struct sockaddr_in Addr() const {
        return _addr;
    }

    std::string AddrString() const {
        return _ip + ":" + std::to_string(_port);
    }

    uint16_t Port() const {
        return _port;
    }

    ~InetAdrr() {}
private:
    uint16_t _port;
    std::string _ip;
    struct sockaddr_in _addr;
};

        makefile

.PHONY:all
all:server client

server:UdpServer.cc
	g++ -o $@ $^ -std=c++11
client:UdpClient.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f server client

        测试结果:

2. Dict server

        这里实现的是一个字典翻译程序,连接上服务器后只需要输入想要翻译的单词,就可以翻译出来,不过单词库需要我们提前填充

        UdpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

enum {
    SOCKET_ERROR = 1,
    BIND_ERROR
};

using func_t = std::function<std::string(const std::string&)>;

const int gsockfd = -1;
const uint16_t glocalport = 8888;

// 网络中的很多东西不建议直接进行拷贝,所以设计我们的类的时候
// 将其设置为不可拷贝的类
// 一般服务器主要用来进行网络数据读取和写入、IO的
// 我们可以将服务器的IO逻辑和业务逻辑解耦
class UdpServer : public nocopy {
private:
    std::string ServerEcho(struct sockaddr_in& peer) {
        // 获取发送方的ip port
        InetAdrr addr(peer);
        std::string echo("[");
        echo += addr.Ip();
        echo += " ";
        echo += std::to_string(addr.Port());
        echo += "]> ";
        return echo;
    }

public:
    // 构造函数传入ip和port
    UdpServer(func_t func, const std::string& ip, uint16_t port = glocalport) 
        : _func(func), 
          _sockfd(gsockfd),
          _localip(ip),
          _localport(port),
          _isrunning(false)
    {}

    void Init() {
        // 先创建 sockfd 文件,使用如下接口的最后一个参数设置为0,
        // 会自动推测是哪个协议
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) {
            LOG(FATAL, "Create sockfd fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "Create Socket success\n");

        // 绑定我们的信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY; // 将ip地址设置为0,可以进行任意ip绑定
        // local.sin_addr.s_addr = inet_addr(_localip.c_str());
        local.sin_port = htons(_localport);
        socklen_t len = sizeof(local);

        int n = ::bind(_sockfd, (struct sockaddr*)&local, len);
        if (n < 0) {
            LOG(FATAL, "Bind socket fail, %s\n", strerror(errno));
            exit(BIND_ERROR);
        }
        LOG(INFO, "Bind Socket success\n");
    }

    void Start() {
        _isrunning = true;
        // 需要收消息
        while (_isrunning) {
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // peer.sin_family = AF_INET;
            // peer.sin_addr.s_addr
            char buff[1024];
            int n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);
            if (n > 0) {

                buff[n] = 0;
                std::string echo = ServerEcho(peer);
                echo += _func(buff);
                sendto(_sockfd, echo.c_str(), echo.size(), 0, (struct sockaddr*)&peer, len);
            } 
        }
        _isrunning = false;
    }

    ~UdpServer() {
        if (_sockfd > 0)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    std::string _localip;
    uint16_t _localport;
    bool _isrunning;

    func_t _func;
};

        UdpServer.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Dict.hpp"

const std::string dict_path = "./dict.txt";

int main() {
    EnableToScreen();
    // 服务器ip一般指定为0,服务器可以收到来自任意ip的信息(只要求端口对应)
    Dict dict(dict_path);
    func_t translate = std::bind(&Dict::Translate, &dict, std::placeholders::_1);
    UdpServer* usvr = new UdpServer(translate, "127.0.0.1", 8899);
    usvr->Init();
    usvr->Start();
    
    return 0;
}

        UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"

using namespace log_ns;

// 客户端在未来一定要知道服务器的ip port
int main(int argc, char* args[]) {
    if (argc != 3) {
        LOG(ERROR, "please input the -> ./client、ip and port\n");
        return 0;
    }
    // 获取 ip 和 port 以及 socket
    uint16_t server_port = std::stoi(args[2]);
    std::string server_ip = args[1];
    int local_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (local_socket < 0) {
        LOG(FATAL, "Create socket fail\n");
        exit(0);
    }
    LOG(INFO, "Create socket success\n");
    // 绑定服务器信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());


    // 客户端的ip和port不需要指定,自己会给出
    // 在这里就可以发送消息了
    while (true) {
        std::string info;
        std::cout << "Please input the info: ";
        std::getline(std::cin, info);
        if (info.size() > 0) {
            // 将消息发送出去
            int num = sendto(local_socket, info.c_str(), info.size(), 0, (struct sockaddr*)&server, len);
            if (num > 0) {
                // 收消息
                struct sockaddr_in temp;

                char buff[1024];
                int recvlen = recvfrom(local_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&temp, &len);
                if (recvlen > 0) {
                    buff[recvlen] = 0;
                    std::cout << buff << std::endl;
                }
            }
        } else {
            break;
        }
    }

    close(local_socket);
    return 0;
}

        Dict.hpp

#pragma once
#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include "Log.hpp"

using namespace log_ns;

const std::string sep = ": ";

class Dict {
    void LoadDict(const std::string& path) {
        std::ifstream in(path);
        if (!in.is_open()) {
            LOG(FATAL, "load the dictionary failed\n");
            exit(0);
        }
        LOG(DEBUG, "open the dictionary success\n");
        // 将数据加载到map中
        std::string info;
        while (std::getline(in, info)) {
            if (info.empty()) continue;
            size_t pos = info.find(sep);
            if (pos == std::string::npos) continue;
            // 现在将获取出来的字符串分隔开
            std::string key = info.substr(0, pos);
            std::string value = info.substr(pos + sep.size());
            if (key.empty() || value.empty()) continue;

            // 走到这里就是正常可以加载的数据
            LOG(DEBUG, "load the info: %s\n", info.c_str());
            _dict.insert(std::make_pair(key, value));
        }

        LOG(DEBUG, "load the dictionary success\n");
        
        in.close();
    }
public:
    Dict(const std::string& path) : _dict_path(path)
    {
        LoadDict(_dict_path);
    }

    std::string Translate(const std::string& word) {
        if (word.empty() || !_dict.count(word))
            return "None";
        return _dict[word];
    }

    ~Dict() {}
private:
    std::string _dict_path;
    std::unordered_map<std::string, std::string> _dict;
};

        dict.txt 这个文件可以自己填充

apple: 苹果
banana: 香蕉
cat: 猫
dog: 狗
book: 书
pen: 笔
happy: 快乐的
sad: 悲伤的
run: 跑
jump: 跳
teacher: 老师
student: 学生
car: 汽车
bus: 公交车
love: 爱
hate: 恨
hello: 你好
goodbye: 再见
summer: 夏天
winter: 冬天

        测试结果:

3. chat_server

        接下来的代码为一个聊天室的代码,只需要连接到我们的服务器端,就可以发送消息了,只要连接该服务器的客户端都可以收到来自其他客服端发送的消息。(使用多线程的单例模式实现的)

        UdpClient.cc

#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Thread.hpp"

using namespace log_ns;

int GetSockfd() {
    int local_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (local_socket < 0) {
        LOG(FATAL, "Create socket fail\n");
        exit(0);
    }
    LOG(INFO, "Create socket success\n");
    return local_socket;
}

void SendInfo(int local_socket, uint16_t server_port, const std::string& server_ip, const std::string& name) {
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    while (true) {
        std::string info;
        std::cout << "Please input the info: ";
        std::getline(std::cin, info);
        if (info.size() > 0)
            // 将消息发送出去
            int num = sendto(local_socket, info.c_str(), info.size(), 0, (struct sockaddr *)&server, len);
        else
            break;
    }
}

void ReceiveInfo(int local_socket, const std::string& name) {
    while (true) {
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        char buff[1024];
        int recvlen = recvfrom(local_socket, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&temp, &len);
        // 加锁的话还需要使用条件变量

        if (recvlen > 0)
        {
            buff[recvlen] = 0;
            std::cout << buff << std::endl;
        }
    }
}

// 客户端在未来一定要知道服务器的ip port
int main(int argc, char* args[]) {
    if (argc != 3) {
        LOG(ERROR, "please input the -> ./client、ip and port\n");
        return 0;
    }
    // 获取 ip 和 port 以及 socket
    uint16_t server_port = std::stoi(args[2]);
    std::string server_ip = args[1];

    int local_socket = GetSockfd();

    // 现在需要将发送消息和接收消息的函数绑定
    func_t send_func = std::bind(&SendInfo, local_socket, server_port, server_ip, std::placeholders::_1);
    func_t recev_func = std::bind(&ReceiveInfo, local_socket, std::placeholders::_1);

    Thread receive_thread(recev_func, "client receive");
    Thread send_thread(send_func, "client send");
    // 绑定服务器信息

    receive_thread.Start();
    send_thread.Start();

    receive_thread.Join();
    send_thread.Join();

    close(local_socket);
    return 0;
}

        UdpServer.cc

#include <iostream>
#include <memory>
#include "UdpServer.hpp"
#include "Route.hpp"

int main() {
    EnableToScreen();
    // 服务器ip一般指定为0,服务器可以收到来自任意ip的信息(只要求端口对应)
    Route route_message;
    server_task_t forword_message = std::bind(&Route::ForwordMessage, 
        &route_message, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
    UdpServer* usvr = new UdpServer(forword_message, "127.0.0.1", 8899);
    usvr->Init();
    usvr->Start();
    
    return 0;
}

        UdpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <functional>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include "Log.hpp"
#include "nocopy.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

enum {
    SOCKET_ERROR = 1,
    BIND_ERROR
};

const int gsockfd = -1;
const uint16_t glocalport = 8888;

using server_task_t = std::function<void(int, const std::string&, InetAdrr&)>;

// 网络中的很多东西不建议直接进行拷贝,所以设计我们的类的时候
// 将其设置为不可拷贝的类
// 一般服务器主要用来进行网络数据读取和写入、IO的
// 我们可以将服务器的IO逻辑和业务逻辑解耦
class UdpServer : public nocopy {
private:
    std::string ServerEcho(struct sockaddr_in& peer) {
        // 获取发送方的ip port
        InetAdrr addr(peer);
        std::string echo("[");
        echo += addr.Ip();
        echo += " ";
        echo += std::to_string(addr.Port());
        echo += "]> ";
        return echo;
    }

public:
    // 构造函数传入ip和port
    UdpServer(server_task_t task, const std::string& ip, uint16_t port = glocalport) 
        : _task(task),
          _sockfd(gsockfd),
          _localip(ip),
          _localport(port),
          _isrunning(false)
    {}

    void Init() {
        // 先创建 sockfd 文件,使用如下接口的最后一个参数设置为0,
        // 会自动推测是哪个协议
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0) {
            LOG(FATAL, "Create sockfd fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "Create Socket success\n");

        // 绑定我们的信息
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY; // 将ip地址设置为0,可以进行任意ip绑定
        // local.sin_addr.s_addr = inet_addr(_localip.c_str());
        local.sin_port = htons(_localport);
        socklen_t len = sizeof(local);

        int n = ::bind(_sockfd, (struct sockaddr*)&local, len);
        if (n < 0) {
            LOG(FATAL, "Bind socket fail, %s\n", strerror(errno));
            exit(BIND_ERROR);
        }
        LOG(INFO, "Bind Socket success\n");
    }

    void Start() {
        _isrunning = true;
        // 需要收消息
        while (_isrunning) {
            struct sockaddr_in peer;
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            // peer.sin_family = AF_INET;
            // peer.sin_addr.s_addr
            char buff[1024];
            int n = recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr*)&peer, &len);
            if (n > 0) {
                InetAdrr addr(peer);
                buff[n] = 0;
                std::string messeage = buff;
                // 将消息转发出去
                LOG(INFO, "begin to forword\n");
                _task(_sockfd, messeage, addr);
                LOG(INFO, "return server\n");
            }
        }
        _isrunning = false;

    }

    ~UdpServer() {
        if (_sockfd > 0)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    std::string _localip;
    uint16_t _localport;
    bool _isrunning;

    server_task_t _task;
};

        ThreadPool.hpp

#pragma once
#include <iostream>
#include <queue>
#include <vector>
#include <string>
#include <pthread.h>
#include "Thread.hpp"

const int default_thread_num = 5;

using namespace log_ns;

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

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

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

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

    bool IsEmptyQueue() {
        return _task_queue.empty();
    }

    void HandlerTask(std::string name) {
        while (true) {
            LockQueue();
            while (IsEmptyQueue() && _isrunning) {
                // 只有当队列为空以及在运行的状态才会继续向下运行
                LOG(DEBUG, "%s sleep\n", name.c_str());
                _sleep_thread_num++;
                SleepThread();
                _sleep_thread_num--;
                LOG(DEBUG, "%s wakeup\n", name.c_str());
            }
            // 当队列为空且不运行时自动退出
            if (IsEmptyQueue() && !_isrunning) {
                // std::cout << name << " quit..." << std::endl;
                LOG(DEBUG, "%s quit...\n", name.c_str());
                UnLockQueue();
                break;
            }
            // 运行到这个位置任务队列中一定有元素,且愿意运行下去
            T t = _task_queue.front();
            _task_queue.pop();

            t();
            // t 执行任务
            // std::cout << name << " -> " << t.result() << std::endl;
            // LOG(DEBUG, "%s -> %s\n", name.c_str(), t.result().c_str());
            UnLockQueue();
        }
    }

    ThreadPool(int threadnum = default_thread_num)
        : _thread_num(default_thread_num),
          _sleep_thread_num(0),
          _isrunning(false)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

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

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

    void Init() {
        // 将线程池内中的handler任务绑定this,让其可以传入线程中运行
        func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
        for (int i = 0; i < _thread_num; i++) {
            std::string name = "thread-" + std::to_string(i + 1);
            _threads.emplace_back(func, name);
            LOG(DEBUG, "%s init\n", name.c_str());
        }
    }

    void Start() {
        // 将线程池的状态设置为运行状态
        _isrunning = true;
        for (auto& thread : _threads)
            thread.Start();
    }

public:

    static ThreadPool<T>* GetInstance() {
        if (_tp == nullptr) {
            // 创建线程池可能存在线程安全的问题
            pthread_mutex_lock(&_sig_mutex);
            if (_tp == nullptr) {
                _tp = new ThreadPool();
                _tp->Init();
                _tp->Start();
                LOG(INFO, "create thread pool\n");
            }
            pthread_mutex_unlock(&_sig_mutex);
        } else {
            LOG(INFO, "get thread pool\n");
        }

        return _tp;
    }

    void Stop() {
        LockQueue();
        _isrunning = false;
        // 唤醒所有线程,让线程退出
        pthread_cond_broadcast(&_cond);
        UnLockQueue();
        LOG(DEBUG, "thread pool stop\n");
    }

    void Push(const T& in) {
        LockQueue();
        // 只有在运行状态我们才往任务队列中放入任务
        if (_isrunning) {
            _task_queue.push(in);
            if (_sleep_thread_num > 0)
                WakeUpThread();
        }
        UnLockQueue();
    }

    ~ThreadPool() {
        for (auto& t : _threads)
            t.Join();
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

private:
    int _thread_num;
    int _sleep_thread_num;
    std::vector<Thread> _threads;
    std::queue<T> _task_queue;

    bool _isrunning;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;


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

// 单例(懒汉)模式
template <typename T>
ThreadPool<T>* ThreadPool<T>::_tp = nullptr;

template <typename T>
pthread_mutex_t ThreadPool<T>::_sig_mutex = PTHREAD_MUTEX_INITIALIZER;

        Thread.hpp

#pragma once
#include <iostream>
#include <functional>
#include <string>
#include <unistd.h>
#include <pthread.h>
#include <cstring>
#include <cerrno>
#include "Log.hpp"

// using func_t = std::function<void(const std::string& name, pthread_mutex_t* lock)>;
using func_t = std::function<void(const std::string& name)>;
using namespace log_ns;
// typedef void*(*func_t)(void*);

const pthread_t ctid = -1;

class Thread {
private:
    void excute() {
        // std::cout << _name << " begin to run" << std::endl;
        // LOG(INFO, "%s begin to run\n", _name.c_str());
        _isrunning = true;
        _func(_name);
        _isrunning = false;
    }

    static void* ThreadRoutine(void* args) {
        Thread* self = static_cast<Thread*>(args);
        self->excute();
        return nullptr;
    }
public:
    Thread(func_t func, const std::string& name) 
        : _func(func),
          _isrunning(false),
          _tid(ctid),
          _name(name)
    {}

    ~Thread() {}

    void Start() {
        // 创建之后就开始运行了
        int n = pthread_create(&_tid, nullptr, ThreadRoutine, (void*)this);
        if (n != 0) {
            std::cout << "thread create failed!!!" << std::endl;
            exit(1);
        }
    }

    void Stop() {
        // 将线程暂停,使用
        if (_isrunning == false) return;
        // std::cout << _name << " stop " << std::endl;

        int n = ::pthread_cancel(_tid);
        if (n != 0)  {
            std::cout << "thread stop failed" << std::endl;
        }
        _isrunning = false;
    }

    void Join() {
        // 线程等待,
        if (_isrunning) return;
        int n = pthread_join(_tid, nullptr);
        if (n != 0) {
            std::cout << "thread wait failed!!!" << strerror(errno) << std::endl;
        }
        // std::cout << _name << " join " << std::endl;

    }

    std::string Status() {
        if (_isrunning) return "running";
        else return "sleep";
    }
private:
    pthread_t _tid;
    func_t _func;
    bool _isrunning;
    std::string _name;
};

        Route.hpp

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "LockGuard.hpp"
#include "ThreadPool.hpp"
using namespace log_ns;

using route_task_t = std::function<void()>;

class Route {
private:
    void CheckUserinList(const InetAdrr& who) {
        LockGuard lockguard(&_mutex);
        for (auto& user : _user_online) {
            if (user == who) return;
        }
        
        LOG(DEBUG, "%s is not exist, add it now\n", who.AddrString().c_str());
        _user_online.push_back(who);
    }

    void Lineoff(InetAdrr& who) {
        LockGuard lockguard(&_mutex);
        auto it = _user_online.begin();
        while (it != _user_online.end()) {
            if (*it == who) {
                _user_online.erase(it);
                LOG(DEBUG, "%s line off\n", who.AddrString().c_str());
                return;
            }
        }
    }

    void ForwordHelper(int sockfd, const std::string& message, InetAdrr& who) {
        std::string send_message = "[" + who.AddrString() + "]> ";
        send_message += message;
        // 现在将messeage转发出去
        for (auto& user : _user_online) {
            struct sockaddr_in peer = user.Addr();
            socklen_t len = sizeof(peer);
            LOG(INFO, "%s forword to %s\n", send_message.c_str(), user.AddrString().c_str());
            sendto(sockfd, send_message.c_str(), send_message.size(), 0, (struct sockaddr*)&peer, len);
        }
    }

public:
    Route() {
        pthread_mutex_init(&_mutex, nullptr);
    }

    void ForwordMessage(int sockfd, const std::string& message, InetAdrr& who) {
        // 先检查当前用户列表中是否存在who
        CheckUserinList(who);
        
        if (message == "Q" || message == "QUIT") {
            Lineoff(who);
        }

        // 开始转发信息
        // ForwordHelper(sockfd, message, who);
        // 现在开始绑定我们的函数
        route_task_t t = std::bind(&Route::ForwordHelper, this, sockfd, message, who);
        ThreadPool<route_task_t>::GetInstance()->Push(t);
    }

    ~Route() {
        pthread_mutex_destroy(&_mutex);
    }
private:
    // 用户列表
    std::vector<InetAdrr> _user_online;
    pthread_mutex_t _mutex;
};

        LockGuard.hpp

#pragma once
#include <iostream>
#include <pthread.h>

class LockGuard {
public:
    LockGuard(pthread_mutex_t* mtx)
        : _mtx(mtx)
    {
        pthread_mutex_lock(_mtx);
    }

    ~LockGuard() {
        pthread_mutex_unlock(_mtx);
    }
private:
    pthread_mutex_t* _mtx;
};

        测试结果:

TCP Socket编程

        Tcp(传输层控制协议) 协议是传输层协议中很常用很重要的一个协议,主要特点如下:

        1. 有连接

        2. 可靠的传输数据

        3. 面向字节流

        在传输层中关于 Tcp 协议的选择和 Udp 协议的选择看主要的应用场景,tcp 可靠但是效率相对较低,udp 不可靠的但是效率相对较高

1. echo server

        这部分的代码功能和 udp 的 echo 的代码功能十分相似,因为该程序主要是为了试探我们写的代码对不对,测试客户端和服务端是否连接上,如下:

        TcpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace log_ns;

enum {
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR
};

const int glistensockfd = -1;
const int gblcklog = 8;

using tcp_task_t = std::function<void()>;

class TcpServer {
private:
    void Service(int sockfd, InetAdrr& who) {
        while (true) {
            // 开始读和写
            char buff[1024];
            int n = read(sockfd, buff, sizeof(buff) - 1);
            if (n > 0) {
                buff[n] = 0;
                LOG(DEBUG, "%s send a message: %s\n", who.AddrString().c_str(), buff);
                std::string echo = "[" + who.AddrString() + "]> ";
                echo += buff;
                write(sockfd, echo.c_str(), echo.size());
            } else if (n == 0) {
                LOG(INFO, "client %s quit\n", who.AddrString().c_str());
                break;
            } else {
                LOG(FATAL, "read error\n");
                break;
            }
        }
        close(sockfd);
    }
    
public:
    TcpServer(uint16_t port)
        : _port(port),
          _listensocked(glistensockfd),
          _isrunning(false) 
    {}

    void Init() {
        // 先获取listensocked
        _listensocked = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensocked < 0) {
            LOG(FATAL, "create listensockfd fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "get listensockfd success, listensockfd: %d\n", _listensocked);

        // 现在开始绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        
        int bind_n = bind(_listensocked, (struct sockaddr*)&local, sizeof(local));
        if (bind_n < 0) {
            LOG(FATAL, "bind listensockfd fail\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");

        int n = listen(_listensocked, gblcklog);
        if (n < 0) {
            LOG(FATAL, "listen socket fail\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO, "listen sucess\n");
    }

    // 创建一个内部类
    struct ThreadData {
        int _sockfd;
        InetAdrr _addr;
        TcpServer* _tcp_point;

        ThreadData(const int sockfd, const InetAdrr& addr, TcpServer* tcp)
            : _sockfd(sockfd),
              _addr(addr),
              _tcp_point(tcp)
        {}
    };

    static void* runServer(void* args) {
        // 将线程分离,防止线程
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        td->_tcp_point->Service(td->_sockfd, td->_addr);

        delete td;
        return nullptr;
    }

    void Start() {
        _isrunning = true;
        while (_isrunning) {
            // 现在开始收消息和发消息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensocked, (struct sockaddr*)&peer, &len);
            InetAdrr addr(peer);
            if (sockfd < 0) {
                LOG(ERROR, "%s get sockfd fail, the reason is %s\n", addr.AddrString().c_str(), strerror(errno));
                continue;
            }
            LOG(INFO, "get sockfd success, sockfd: %d\n", sockfd);
            // Service(sockfd, addr);
            // 串行运行长服务并不能满足多个用户访问服务器,需要使用并行运行才能满足
            // 1. 多进程 2、多线程 3、线程池
            // // 1. 多进程版本
            // pid_t id = fork();
            // if (id == 0) {
            //     // 让子进程关闭listensocked文件描述符
            //     close(_listensocked);
                
            //     // 创建孙子进程,直接让子进程退出,孙子进程会被bash接管
            //     // 这样长服务就会被孙子进程运行,孙子进程退出直接退出
            //     // 子进程也不会阻塞,可以让父进程继续等下去
            //     // 当然最好的方法是使用信号 signal(SIGCHLD, SIG_IGN) 操作
            //     if (fork() > 0) exit(0);
            //     Service(sockfd, addr);
            //     exit(0);
            // }
            // // 让父进程关闭sockfd文件描述符,防止文件描述符太多导致文件描述符泄露
            // close(sockfd);
            // pid_t n = waitpid(id, nullptr, 0);
            // if (n > 0) {
            //     LOG(INFO, "wait child process success\n");
            // }

            // // 2. 多线程
            // pthread_t tid;
            // ThreadData* data = new ThreadData(sockfd, addr, this);
            // pthread_create(&tid, nullptr, runServer, (void*)data);

            // 3. 线程池
            tcp_task_t task = std::bind(&TcpServer::Service, this, sockfd, addr);
            ThreadPool<tcp_task_t>::GetInstance()->Push(task);
        }


        _isrunning = false;
    }

    ~TcpServer() {
        if (_listensocked > 0) 
            close(_listensocked);
    }
private:
    uint16_t _port;
    int _listensocked;
    bool _isrunning;
};

        TcpServer.cc

#include "TcpServer.hpp"


int main() {
    TcpServer* tcvr = new TcpServer(8888);
    tcvr->Init();
    tcvr->Start();
    return 0;
}

        TcpClient.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"

using namespace log_ns;

int GetSockfd() {
    int local_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (local_socket < 0) {
        LOG(FATAL, "Create socket fail\n");
        exit(0);
    }
    LOG(INFO, "Create socket success\n");
    return local_socket;
}

int main(int argc, char* args[]) {
    if (argc != 3) {
        LOG(ERROR, "please input the -> ./client、ip and port\n");
        return 0;
    }
    // 获取 ip 和 port 以及 socket
    uint16_t server_port = std::stoi(args[2]);
    std::string server_ip = args[1];

    // 绑定
    int local_socket = GetSockfd();

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    // server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);

    int n = connect(local_socket, (struct sockaddr*)&server, sizeof(server));
    if (n < 0) {
        LOG(FATAL, "get sockfd fail\n");
        exit(1);
    }

    while (true) {
        std::string info;
        std::cout << "Please enter > ";
        std::getline(std::cin, info);
        // 现在将数据写入
        int n = write(local_socket, info.c_str(), info.size());
        if (n > 0) {
            char buff[1024];
            int readlen = read(local_socket, buff, sizeof(buff) - 1);
            if (readlen > 0) {
                buff[readlen] = 0;
                std::cout << buff << std::endl;
            }
        } else {
            LOG(INFO, "client quit\n");
            break;
        }
    }

    close(local_socket);

    return 0;
}

        其余的代码文件和 udp 的一样,测试如下:

2. command server

        该代码是在 echo 代码基础上改编的代码,主要实现的功能为:将我们输入的命令执行,相当于一个小型的 shell 程序,如下:

        TcpServer.hpp

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

enum {
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR
};

const int glistensockfd = -1;
const int gblcklog = 8;

using tcp_task_t = std::function<void(int, InetAdrr&)>;

class TcpServer {
private:
    void Service(int sockfd, InetAdrr& who) {
        while (true) {
            // 开始读和写
            char buff[1024];
            int n = read(sockfd, buff, sizeof(buff) - 1);
            if (n > 0) {
                buff[n] = 0;
                LOG(DEBUG, "%s send a message: %s\n", who.AddrString().c_str(), buff);
                std::string echo = "[" + who.AddrString() + "]> ";
                echo += buff;
                write(sockfd, echo.c_str(), echo.size());
            } else if (n == 0) {
                LOG(INFO, "client %s quit\n", who.AddrString().c_str());
                break;
            } else {
                LOG(FATAL, "read error\n");
                break;
            }
        }
        close(sockfd);
    }
    
public:
    TcpServer(tcp_task_t task, uint16_t port)
        : _task(task),
          _port(port),
          _listensocked(glistensockfd),
          _isrunning(false) 
    {}

    void Init() {
        // 先获取listensocked
        _listensocked = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensocked < 0) {
            LOG(FATAL, "create listensockfd fail\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO, "get listensockfd success, listensockfd: %d\n", _listensocked);

        // 现在开始绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = INADDR_ANY;
        
        int bind_n = bind(_listensocked, (struct sockaddr*)&local, sizeof(local));
        if (bind_n < 0) {
            LOG(FATAL, "bind listensockfd fail\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");

        int n = listen(_listensocked, gblcklog);
        if (n < 0) {
            LOG(FATAL, "listen socket fail\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO, "listen sucess\n");
    }

    // 创建一个内部类
    struct ThreadData {
        int _sockfd;
        InetAdrr _addr;
        TcpServer* _tcp_point;

        ThreadData(const int sockfd, const InetAdrr& addr, TcpServer* tcp)
            : _sockfd(sockfd),
              _addr(addr),
              _tcp_point(tcp)
        {}
    };

    static void* runServer(void* args) {
        // 将线程分离,就不用阻塞的join线程
        pthread_detach(pthread_self());
        ThreadData* td = static_cast<ThreadData*>(args);
        // LOG(INFO, "the sockfd: %d\n", td->_sockfd);

        td->_tcp_point->_task(td->_sockfd, td->_addr);
        close(td->_sockfd);
        delete td;
        return nullptr;
    }

    void Start() {
        _isrunning = true;
        while (_isrunning) {
            // 现在开始收消息和发消息
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensocked, (struct sockaddr*)&peer, &len);
            InetAdrr addr(peer);
            if (sockfd < 0) {
                LOG(ERROR, "%s get sockfd fail, the reason is %s\n", addr.AddrString().c_str(), strerror(errno));
                continue;
            }
            LOG(INFO, "get sockfd success, sockfd: %d\n", sockfd);

            // 2. 多线程
            pthread_t tid;
            ThreadData* data = new ThreadData(sockfd, addr, this);
            pthread_create(&tid, nullptr, runServer, (void*)data);

 
        }


        _isrunning = false;
    }

    ~TcpServer() {
        if (_listensocked > 0) 
            close(_listensocked);
    }
private:
    uint16_t _port;
    int _listensocked;
    bool _isrunning;

    tcp_task_t _task;
};

        TcpServer.cc

#include "TcpServer.hpp"
#include "Command.hpp"

int main() {
    Command cmd;
    tcp_task_t task = std::bind(&Command::CommandHandler, &cmd, std::placeholders::_1, std::placeholders::_2);
    TcpServer* tcvr = new TcpServer(task, 8888);
    tcvr->Init();
    tcvr->Start();
    return 0;
}

        TcpClient.cc

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"

using namespace log_ns;

int GetSockfd() {
    int local_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (local_socket < 0) {
        LOG(FATAL, "Create socket fail\n");
        exit(0);
    }
    LOG(INFO, "Create socket success\n");
    return local_socket;
}

int main(int argc, char* args[]) {
    if (argc != 3) {
        LOG(ERROR, "please input the -> ./client、ip and port\n");
        return 0;
    }
    // 获取 ip 和 port 以及 socket
    uint16_t server_port = std::stoi(args[2]);
    std::string server_ip = args[1];

    // 绑定
    int local_socket = GetSockfd();

    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    socklen_t len = sizeof(server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    // server.sin_addr.s_addr = inet_addr(server_ip.c_str());
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);

    int n = connect(local_socket, (struct sockaddr*)&server, sizeof(server));
    if (n < 0) {
        LOG(FATAL, "get sockfd fail\n");
        exit(1);
    }

    while (true) {
        std::string info;
        std::cout << "Please enter > ";
        std::getline(std::cin, info);
        // 现在将数据写入
        int n = write(local_socket, info.c_str(), info.size());
        if (n > 0) {
            char buff[1024];
            int readlen = read(local_socket, buff, sizeof(buff) - 1);
            if (readlen > 0) {
                buff[readlen] = 0;
                std::cout << buff << std::endl;
            }
        } else {
            LOG(INFO, "client quit\n");
            break;
        }
    }

    close(local_socket);

    return 0;
}

        Command.hpp

#pragma once
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include "InetAddr.hpp"
#include "Log.hpp"

using namespace log_ns;

class Command {
    std::string Excute(const std::string& cmd) {
        FILE* fp = popen(cmd.c_str(), "r");
        if (fp) {
            std::string result;
            char info[1024];
            while (fgets(info, sizeof(info), fp)) {
                result += info;
            }
            pclose(fp);
            return result.empty() ? "success" : result;
        } else {
            LOG(WARNING, "open the fp fail\n");
            return "None";
        }
    }

public:
    Command() {}
    
    void CommandHandler(int sockfd, InetAdrr& who) {
        // 接收消息,然后返回消息    
        while (true) {
            // 开始读和写
            char buff[1024];
            // LOG(INFO, "the sockfd: %d\n", sockfd);
            int n = recv(sockfd, buff, sizeof(buff) - 1, 0);
            if (n > 0) {
                buff[n] = 0;
                LOG(DEBUG, "%s send a message: %s\n", who.AddrString().c_str(), buff);
                std::string result = Excute(buff);
                send(sockfd, result.c_str(), result.size(), 0);
            } else if (n == 0) {
                LOG(INFO, "client %s quit\n", who.AddrString().c_str());
                break;
            } else {
                LOG(FATAL, "read error, %s\n", strerror(errno));
                break;
            }
        }
    }
    
    ~Command() {}
};

        测试如下:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066425.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

电脑死机之后强制关机重启,只能进入到Bios,不能进入到系统?

前言 最近遇到好几件比较有意思的事情&#xff0c;粉丝过来求助咨询&#xff1a;电脑不知怎的就黑屏死机了&#xff0c;重启之后&#xff0c;电脑只能进入到Bios&#xff0c;无论怎么重启都没用。 把电脑拆出来看了看&#xff0c;线路一切正常。感觉上可能是内存条的问题&…

NRK3301语音识别芯片在汽车内饰氛围灯上的应用方案解析

随着智能汽车的快速发展&#xff0c;车载语音交互技术逐渐成为提升驾驶体验的关键技术之一。传统的汽车内饰氛围灯语音识别系统往往依赖于手动操作&#xff0c;不仅操作繁琐&#xff0c;而且在驾驶过程中容易分散驾驶员的注意力。因此&#xff0c;开发一种高效、便捷的汽车内饰…

OpenAI gym: when is reset required?

题意&#xff1a;“OpenAI Gym: 什么时候需要重置&#xff1f;” 问题背景&#xff1a; Although I can manage to get the examples and my own code to run, I am more curious about the real semantics / expectations behind OpenAI gym API, in particular Env.reset() …

基于网格尺度的上海市人口分布空间聚集特征分析与冷热点识别

在上篇文章提到了同一研究空间在不同尺度下的观察可能会带来不同的见解和发现&#xff0c;这次我们把尺度缩放到网格&#xff0c;来看网格尺度下的空间自相关性、高/低聚类&#xff0c;这些&#xff0c;因为尺度缩放到网格尺度了&#xff0c;全国这个行政区范围就显的太大了&am…

Python采集网页数据:八招全解

在信息时代&#xff0c;海量的数据日益成为企业和个人获取商业价值的重要手段。而获取这些数据的方式之一就是通过网络爬虫技术采集网络上的各种信息&#xff0c;对于 Python 程序员来说&#xff0c;这项工作并不困难。本文将从八个方面&#xff0c;带你了解如何使用 Python 采…

查找技术与平衡查找树

目录 引言 查找技术的重要性 顺序查找 顺序查找的优缺点对比 二分查找 二分查找的步骤总结 哈希查找 哈希函数设计与冲突解决 平衡查找树 二叉搜索树、AVL树与红黑树 平衡查找树的插入与删除操作 平衡查找树的应用场景 总结与应用 综合实例分析 引言 查找是计算机…

算法训练营三刷(Java) | 第六天~第十一天

算法训练营三刷&#xff08;Java&#xff09; | 第六天~第十一天 第六天 LeetCode 242 有效的字母异位词 解题思路&#xff1a; 数组哈希记录每个字幕出现的次数&#xff0c;然后进行比较。Java中字符串取下标i处字符可以使用charAt成员函数也可以转化为字符数组之后用数组的…

三菱PLC数据 转IEC61850项目案例

目录 1 案例说明 2 VFBOX网关工作原理 3 准备工作 4 网关采集三菱PLC数据 5 用IEC61850协议转发数据 6 网关使用多个逻辑设备和逻辑节点的方法 7 其他说明 8 案例总结 1 案例说明 设置vfbox网关采集三菱PLC数据把采集的数据转成IEC61850协议转发给其他系统。 2 VFBOX网关工…

【Python大语言模型系列】如何在LangChain中使用ReAct构建AI Agent(案例+源码)

一、引言 当前&#xff0c;在各个大厂纷纷卷LLM的情况下&#xff0c;各自都借助自己的LLM推出了自己的AI Agent&#xff0c;比如字节的Coze&#xff0c;百度的千帆等&#xff0c;还有开源的Dify。你是否想知道其中的原理&#xff1f;是否想过自己如何实现一套AI Agent&#xff…

联想凌拓发布多款新一代AI数据管理解决方案

联想凌拓发布多款新一代AI数据管理解决方案 联想凌拓正式宣布推出 NetApp AFF C 系列、 NetApp ASA A 系列、 NetApp ASA C 系列、Lenovo ThinkSystem DG系列、Lenovo ThinkSystem DM3010H企业级存储阵列及MagnaScale数据管理平台V4.0全面升级&#xff0c;让企业应用更简便、更…

华为数通方向HCIP-DataCom H12-821题库(更新单选真题:21-30)

第21题 以下关于0SPF中ABR的描述,错误的是哪一项? A、ABR将连接的非骨干区域内的1类和2类1SA转换成3类LSA,发布到骨干区域中 B、ABR不能够产生4类和5类LSA C、ABR上有多个LSDB,ABR为每一个区域维护一个LSDB D、ABR将骨干区域内的1类、2类LSA和3类LSA转换成三类LSA,发布到…

容器篇(JavaSE - 集合)(持续更新迭代)

目录 有意者可加 一、集合 1. 出现的背景 2. 带大家具体了解下集合 3. 集合带来了哪些好处 4. 集合的特点 5. 集合和数组对比 6. 数组和集合应用场景&#xff08;对比&#xff09; 6.1 数组的应用场景 1. 存储一组数据 2. 图像处理 3. 矩阵运算 4. 缓存 6.2 集合…

OJ-0822

题目 4---| || || | 3 3 | --- ---| | | || | | |2 | | 2 | | 2--- | --- | ---| | | …

告警中心消息转发系统PrometheusAlert

告警中心消息转发系统(PrometheusAlert) 官方站点&#xff1a;Prometheus Alert是开源的运维告警中心消息转发系统 手册&#xff1a;README - PrometheusAlert (gitbook.io) 为什么要用PrometheusAlert? 背景 通过configmap配置文件维护告警媒介辨析度低部分快消息告警媒介需…

【Canvas与艺术】环状合掌纹

【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html; charsetutf-8"/> <head><title>环形合掌纹</title><style type"text/css">.…

45.5【C语言】typedef

目录&#xff1a; *全称 *格式 一般指针 数组指针 函数指针 *细节 *全称 type define 类型&#xff08;重新&#xff09;定义&#xff08;或命名&#xff09;&#xff0c;可简化输入 *格式 1.非指针类型: typedef 类型 简化名称 typedef signed long long k; signed long …

SOMEIP_ETS_059: ResetInterface_wrong_Fire_and_forget_package_get_No_Error_back

测试目的&#xff1a; 验证设备&#xff08;DUT&#xff09;在接收到一个错误的Fire&Forget消息时&#xff0c;不会做出响应或发送错误消息。 描述 本测试用例旨在检查DUT在接收到使用无效接口版本的方法resetInterface的Fire&Forget消息时&#xff0c;是否不会回应…

四川财谷通信息技术有限公司抖音小店,打造新生态

在当今这个数字化浪潮汹涌的时代&#xff0c;电商行业以其独特的魅力和无限潜力&#xff0c;成为了推动经济发展的重要力量。抖音&#xff0c;作为短视频领域的佼佼者&#xff0c;其抖音小店功能的推出&#xff0c;更是为众多商家开辟了一条全新的销售渠道。四川财谷通信息技术…

【计算机操作系统】虚拟内存的基本概念

文章目录 虚拟内存的基本概念传统存储管理方式的特征&缺点局部性原理虚拟内存的定义和特征虚拟内存的定义虚拟内存的特征 虚拟内存技术的实现 虚拟内存的基本概念 前面已经介绍过内存管理相关的重点&#xff0c;传统存储管理方式、覆盖与交换技术、地址转换和存储保护在之…

企业监控大盘Grafana

企业监控大盘Grafana Grafana简述 Grafana 是一个开源的度量分析与可视化工具。提供查询、可视化、报警和指标展示等功能&#xff0c;能灵活创建图表、仪表盘等可视化界面 主要功能&#xff1a; 可视化: 提供多种可选择的不同类型的图形&#xff0c;能够灵活绘制不同样式&…