UDP/TCP --- Socket编程

        本篇将使用 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() {}
};

        测试如下:

  • 9
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值