【Linux | 网络】socket编程 - 使用UDP实现服务端向客户端提供简单的服务

目录

  • 一、UdpServerSever(客户端发送信息,服务端直接返回信息)
    • [1.1 Comm.hpp(公共数据)](#1.1 Comm.hpp(公共数据))
    • [1.2 Log.hpp(日志)](#1.2 Log.hpp(日志))
    • [1.3 InetAddr.hpp(管理sockaddr_in相关信息)](#1.3 InetAddr.hpp(管理sockaddr_in相关信息))
    • [1.4 NoCopy.hpp(防拷贝)](#1.4 NoCopy.hpp(防拷贝))
    • [1.5 UdpServer.hpp(服务端封装)](#1.5 UdpServer.hpp(服务端封装))
    • [1.6 Main.cpp(服务端)](#1.6 Main.cpp(服务端))
    • [1.7 UdpClient.cpp(客户端)](#1.7 UdpClient.cpp(客户端))
  • 二、UdpServerExecute(客户端发送指令服务端执行后返回执行结果)
    • [2.1 Comm.hpp(公共数据)](#2.1 Comm.hpp(公共数据))
    • [2.2 Log.hpp(日志)](#2.2 Log.hpp(日志))
    • [2.3 InetAddr.hpp(管理sockaddr_in相关信息)](#2.3 InetAddr.hpp(管理sockaddr_in相关信息))
    • [2.4 NoCopy.hpp(防拷贝)](#2.4 NoCopy.hpp(防拷贝))
    • [2.5 UdpServer.hpp(服务端封装)](#2.5 UdpServer.hpp(服务端封装))
    • [2.6 Main.cpp(服务端)](#2.6 Main.cpp(服务端))
    • [2.7 UdpClient.cpp(客户端)](#2.7 UdpClient.cpp(客户端))
  • 三、UdpServerChat(实现简单的聊天室)
    • [3.1 Comm.hpp(公共数据)](#3.1 Comm.hpp(公共数据))
    • [3.2 Log.hpp(日志)](#3.2 Log.hpp(日志))
    • [3.3 InetAddr.hpp(管理sockaddr_in相关信息)](#3.3 InetAddr.hpp(管理sockaddr_in相关信息))
    • [3.4 NoCopy.hpp(防拷贝)](#3.4 NoCopy.hpp(防拷贝))
    • [3.5 Lockguard.hpp(自动管理锁)](#3.5 Lockguard.hpp(自动管理锁))
    • [3.6 Thread.hpp(封装线程)](#3.6 Thread.hpp(封装线程))
    • [3.7 ThreadPool.hpp(线程池)](#3.7 ThreadPool.hpp(线程池))
    • [3.8 Daemon.hpp(使进程变为守护进程)](#3.8 Daemon.hpp(使进程变为守护进程))
    • [3.9 UdpServer.hpp(服务端封装)](#3.9 UdpServer.hpp(服务端封装))
    • [3.10 Main.cpp(服务端)](#3.10 Main.cpp(服务端))
    • [3.11 UdpClient.cpp(客户端)](#3.11 UdpClient.cpp(客户端))
  • 结尾

一、UdpServerSever(客户端发送信息,服务端直接返回信息)

1.1 Comm.hpp(公共数据)

cpp 复制代码
#pragma once

enum {
    Socket_err = 1,
    Bind_err,
    Accept_err,
    Recvfrom_err
};

1.2 Log.hpp(日志)

cpp 复制代码
#pragma once

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

using namespace std;

// 日志等级
enum
{
    Debug = 0, // 调试
    Info,      // 正常
    Warning,   // 警告
    Error,     // 错误,但程序并未直接退出
    Fatal      // 程序直接挂掉
};

enum
{
    Screen = 10, // 打印到显示器上
    OneFile,     // 打印到一个文件中
    ClassFile    // 按照日志等级打印到不同的文件中
};

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 "Unknow";
    }
}

const char* default_filename = "log.";
const int default_style = Screen;
const char* defaultdir = "log";

class Log
{
public:
    Log()
        : style(default_style), filename(default_filename)
    {
        // mkdir(defaultdir,0775);
        pthread_mutex_init(&_log_mutex, nullptr);
    }

    void SwitchStyle(int sty)
    {
        style = sty;
    }

    void WriteLogToOneFile(const string& logname, const string& logmessage)
    {
        int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
        if (fd == -1)
            return;

        pthread_mutex_lock(&_log_mutex);
        write(fd, logmessage.c_str(), logmessage.size());
        pthread_mutex_unlock(&_log_mutex);

        close(fd);
    }

    void WriteLogToClassFile(const string& levelstr, const string& logmessage)
    {
        mkdir(defaultdir, 0775);

        string name = defaultdir;
        name += "/";
        name += filename;
        name += levelstr;

        WriteLogToOneFile(name, logmessage);
    }

    void WriteLog(int level, const string& logmessage)
    {
        switch (style)
        {
        case Screen:
        {
            pthread_mutex_lock(&_log_mutex);
            cout << logmessage;
            pthread_mutex_unlock(&_log_mutex);
        }
        break;
        case OneFile:
            WriteLogToClassFile("All", logmessage);
            break;
        case ClassFile:
            WriteLogToClassFile(LevelToString(level), logmessage);
            break;
        default:
            break;
        }
    }

    string GetTime()
    {
        time_t CurrentTime = time(nullptr);

        struct tm* curtime = localtime(&CurrentTime);
        char time[128];

        // localtime 的年是从1900开始的,所以要加1900, 月是从0开始的所以加1
        snprintf(time, sizeof(time), "%d-%d-%d %d:%d:%d",
            curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday,
            curtime->tm_hour, curtime->tm_min, curtime->tm_sec);

        return time;
        return "";
    }

    void LogMessage(int level, const char* format, ...)
    {
        char left[1024];
        string Levelstr = LevelToString(level).c_str();
        string Timestr = GetTime().c_str();
        string Idstr = to_string(getpid());
        snprintf(left, sizeof(left), "[%s][%s][%s] ",
            Levelstr.c_str(), Timestr.c_str(), Idstr.c_str());

        va_list args;
        va_start(args, format);
        char right[1024];
        vsnprintf(right, sizeof(right), format, args);

        string logmessage = left;
        logmessage += right;

        WriteLog(level, logmessage);

        va_end(args);
    }

    ~Log()
    {
        pthread_mutex_destroy(&_log_mutex);
    };

private:
    int style;
    string filename;

    pthread_mutex_t _log_mutex;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.SwitchStyle(Screen);
    }
    ~Conf()
    {
    }
};

Conf conf;

1.3 InetAddr.hpp(管理sockaddr_in相关信息)

cpp 复制代码
#pragma once

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>

class InetAddr
{
public:
    InetAddr(struct sockaddr_in sock)
        :_sock(sock)
    {}

    std::string Ip()
    {
        return inet_ntoa(_sock.sin_addr);
    }

    uint16_t Port()
    {
        return ntohs(_sock.sin_port);
    }

    std::string PrintDebug()
    {
        std::string info = "[";
        info += Ip();
        info += ":";
        info += std::to_string(Port());
        info += "]";
        info += "# ";

        return info;
    }

    ~InetAddr()
    {}

private:
    struct sockaddr_in _sock;
};

1.4 NoCopy.hpp(防拷贝)

cpp 复制代码
#pragma once

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

1.5 UdpServer.hpp(服务端封装)

cpp 复制代码
#pragma once

#include <string>
#include "Comm.hpp"
#include "Nocopy.hpp"
#include "InetAddr.hpp"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include <string.h>
#include <errno.h>

const static int defaultfd = -1;
const static uint16_t defaultport = 8888;
const static int defaultsize = 1024;


// 服务器的IP不应该是固定的,而是任意的
class UdpServer : public Nocopy
{
public:
    UdpServer(uint16_t port = defaultport)
        :_port(port),_sockfd(defaultfd)
    {
    }

    void Init()
    {
        // 创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            lg.LogMessage(Fatal, "create socket fail\n");
            exit(Socket_err);
        }
        lg.LogMessage(Info, "create socket success , sockfd : %d\n", _sockfd);

        // 2、绑定,指定网络信息,协议家族,IP地址,端口号,结构体填充,设置到内核
        struct sockaddr_in ServerSockaddr;
        memset(&ServerSockaddr,0,sizeof(ServerSockaddr));
        ServerSockaddr.sin_family = AF_INET;
        ServerSockaddr.sin_port = htons(_port);
        ServerSockaddr.sin_addr.s_addr = INADDR_ANY; // 就是0
        socklen_t len = sizeof(ServerSockaddr);
        
        int n = ::bind(_sockfd, (struct sockaddr*)&ServerSockaddr, len);
        if (n == -1)
        {
            // cout << inet_ntoa(ServerSockaddr.sin_addr) << ":" << ntohs(ServerSockaddr.sin_port) << endl;
            lg.LogMessage(Fatal, "bind fail , errno : %d , %s\n", errno , strerror(errno));
            exit(Bind_err);
        }
        lg.LogMessage(Info, "bind success , sockfd : %d\n", _sockfd);
    }

    void Start()
    {
        char inbuffer[defaultsize];
        for (;;)
        {
            struct sockaddr_in ClientSockaddr;
            socklen_t ClientLen = sizeof(ClientSockaddr);

            // 接收数据
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&ClientSockaddr, &ClientLen);
            if (n > 0)
            {
                inbuffer[n] = 0;

                InetAddr inetaddr(ClientSockaddr);
                cout << inetaddr.PrintDebug() << inbuffer << endl;

                // 发送数据
                sendto(_sockfd, &inbuffer, sizeof(inbuffer), 0, (struct sockaddr*)&ClientSockaddr, ClientLen);
            }


        }
    }

    ~UdpServer()
    {
        close(_sockfd);
    }

private:
    // std::string _ip;
    uint16_t _port;
    int _sockfd;
};

1.6 Main.cpp(服务端)

cpp 复制代码
#include "UdpServer.hpp"
#include <iostream>
#include <unistd.h>
#include <string>
#include <memory>

using namespace std;

void Usage(const string& proc)
{
    // cout << proc << " localip localport\n" << endl;
    cout << proc << " localport\n" << endl;
}

// ./udpserver localip localport
int main(int argc, char* argv[])
{
    // 服务器不能固定ip
    // if (argc != 3)
    // {
    //     Usage(argv[0]);
    //     exit(1);
    // }
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    // string ip = argv[1];
    // uint16_t port = stoi(argv[2]);
    // unique_ptr<UdpServer> uq = make_unique<UdpServer>(ip,port);

    uint16_t port = stoi(argv[1]);
    unique_ptr<UdpServer> uq = make_unique<UdpServer>(port);

    uq->Init();
    uq->Start();

    return 0;
}

1.7 UdpClient.cpp(客户端)

cpp 复制代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace std;

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

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

    int clientfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientfd < 0)
    {
        cout << "create socket fail\n" << endl;
        exit(1);
    }
    cout << "create socket success : " << clientfd << endl;


    struct sockaddr_in ServerSocket;
    memset(&ServerSocket,0,sizeof(ServerSocket));
    socklen_t Slen = sizeof(ServerSocket);
    ServerSocket.sin_family = AF_INET;
    ServerSocket.sin_addr.s_addr = inet_addr(argv[1]);
    ServerSocket.sin_port = htons(stoi(argv[2]));

    while (1)
    {
        string inbuffer;
        cout << "Please Enter# ";
        getline(cin,inbuffer);

        int n = sendto(clientfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&ServerSocket, Slen);
        if (n < 0)
        {
            cout << "sendto fail , errno : " << errno <<  ", error : " << strerror(errno) << endl;
            break;
        }
        else
        {
            cout << inbuffer << endl;

            struct sockaddr_in temp;
            socklen_t tlen = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(clientfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp, &tlen);
            if(m > 0)
            {
                buffer[m] = 0;
                cout << "Server say#" << buffer << endl;
            }
            else
            {
                break;
            }
        }
    }

    close(clientfd);

    return 0;
}

二、UdpServerExecute(客户端发送指令服务端执行后返回执行结果)

2.1 Comm.hpp(公共数据)

cpp 复制代码
#pragma once

enum {
    Socket_err = 1,
    Bind_err,
    Accept_err,
    Recvfrom_err
};

2.2 Log.hpp(日志)

cpp 复制代码
#pragma once

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

using namespace std;

// 日志等级
enum
{
    Debug = 0, // 调试
    Info,      // 正常
    Warning,   // 警告
    Error,     // 错误,但程序并未直接退出
    Fatal      // 程序直接挂掉
};

enum
{
    Screen = 10, // 打印到显示器上
    OneFile,     // 打印到一个文件中
    ClassFile    // 按照日志等级打印到不同的文件中
};

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 "Unknow";
    }
}

const char* default_filename = "log.";
const int default_style = Screen;
const char* defaultdir = "log";

class Log
{
public:
    Log()
        : style(default_style), filename(default_filename)
    {
        // mkdir(defaultdir,0775);
        pthread_mutex_init(&_log_mutex, nullptr);
    }

    void SwitchStyle(int sty)
    {
        style = sty;
    }

    void WriteLogToOneFile(const string& logname, const string& logmessage)
    {
        int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
        if (fd == -1)
            return;

        pthread_mutex_lock(&_log_mutex);
        write(fd, logmessage.c_str(), logmessage.size());
        pthread_mutex_unlock(&_log_mutex);

        close(fd);
    }

    void WriteLogToClassFile(const string& levelstr, const string& logmessage)
    {
        mkdir(defaultdir, 0775);

        string name = defaultdir;
        name += "/";
        name += filename;
        name += levelstr;

        WriteLogToOneFile(name, logmessage);
    }

    void WriteLog(int level, const string& logmessage)
    {
        switch (style)
        {
        case Screen:
        {
            pthread_mutex_lock(&_log_mutex);
            cout << logmessage;
            pthread_mutex_unlock(&_log_mutex);
        }
        break;
        case OneFile:
            WriteLogToClassFile("All", logmessage);
            break;
        case ClassFile:
            WriteLogToClassFile(LevelToString(level), logmessage);
            break;
        default:
            break;
        }
    }

    string GetTime()
    {
        time_t CurrentTime = time(nullptr);

        struct tm* curtime = localtime(&CurrentTime);
        char time[128];

        // localtime 的年是从1900开始的,所以要加1900, 月是从0开始的所以加1
        snprintf(time, sizeof(time), "%d-%d-%d %d:%d:%d",
            curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday,
            curtime->tm_hour, curtime->tm_min, curtime->tm_sec);

        return time;
        return "";
    }

    void LogMessage(int level, const char* format, ...)
    {
        char left[1024];
        string Levelstr = LevelToString(level).c_str();
        string Timestr = GetTime().c_str();
        string Idstr = to_string(getpid());
        snprintf(left, sizeof(left), "[%s][%s][%s] ",
            Levelstr.c_str(), Timestr.c_str(), Idstr.c_str());

        va_list args;
        va_start(args, format);
        char right[1024];
        vsnprintf(right, sizeof(right), format, args);

        string logmessage = left;
        logmessage += right;

        WriteLog(level, logmessage);

        va_end(args);
    }

    ~Log()
    {
        pthread_mutex_destroy(&_log_mutex);
    };

private:
    int style;
    string filename;

    pthread_mutex_t _log_mutex;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.SwitchStyle(Screen);
    }
    ~Conf()
    {
    }
};

Conf conf;

2.3 InetAddr.hpp(管理sockaddr_in相关信息)

cpp 复制代码
#pragma once

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>

class InetAddr
{
public:
    InetAddr(struct sockaddr_in sock)
        :_sock(sock)
    {}

    std::string Ip()
    {
        return inet_ntoa(_sock.sin_addr);
    }

    uint16_t Port()
    {
        return ntohs(_sock.sin_port);
    }

    std::string PrintDebug()
    {
        std::string info = "[";
        info += Ip();
        info += ":";
        info += std::to_string(Port());
        info += "]";
        info += "# ";

        return info;
    }

    ~InetAddr()
    {}

private:
    struct sockaddr_in _sock;
};

2.4 NoCopy.hpp(防拷贝)

cpp 复制代码
#pragma once

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

2.5 UdpServer.hpp(服务端封装)

cpp 复制代码
#pragma once

#include "Comm.hpp"
#include "Nocopy.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <errno.h>
#include <functional>

const static int defaultfd = -1;
const static uint16_t defaultport = 8888;
const static int defaultsize = 1024;
 
using cb_t = function<string(string)>;

// 服务器的IP不应该是固定的,而是任意的
class UdpServer : public Nocopy
{
public:
    UdpServer(cb_t OnMessage,uint16_t port = defaultport)
        :_port(port),_sockfd(defaultfd),_OnMessage(OnMessage)
    {
    }

    void Init()
    {
        // 创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            lg.LogMessage(Fatal, "create socket fail\n");
            exit(Socket_err);
        }
        lg.LogMessage(Info, "create socket success , sockfd : %d\n", _sockfd);

        // 2、绑定,指定网络信息,协议家族,IP地址,端口号,结构体填充,设置到内核
        struct sockaddr_in ServerSockaddr;
        memset(&ServerSockaddr,0,sizeof(ServerSockaddr));
        ServerSockaddr.sin_family = AF_INET;
        ServerSockaddr.sin_port = htons(_port);
        ServerSockaddr.sin_addr.s_addr = INADDR_ANY; // 就是0
        socklen_t len = sizeof(ServerSockaddr);
        
        int n = ::bind(_sockfd, (struct sockaddr*)&ServerSockaddr, len);
        if (n == -1)
        {
            // cout << inet_ntoa(ServerSockaddr.sin_addr) << ":" << ntohs(ServerSockaddr.sin_port) << endl;
            lg.LogMessage(Fatal, "bind fail , errno : %d , %s\n", errno , strerror(errno));
            exit(Bind_err);
        }
        lg.LogMessage(Info, "bind success , sockfd : %d\n", _sockfd);
    }

    void Start()
    {
        char inbuffer[defaultsize];
        for (;;)
        {
            struct sockaddr_in ClientSockaddr;
            socklen_t ClientLen = sizeof(ClientSockaddr);

            // 接收数据
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&ClientSockaddr, &ClientLen);
            if (n > 0)
            {
                inbuffer[n] = 0;

                InetAddr inetaddr(ClientSockaddr);
                cout << inetaddr.PrintDebug() << inbuffer<< endl;

                // 发送数据
                string response = _OnMessage(inbuffer);
                sendto(_sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&ClientSockaddr, ClientLen);
            }


        }
    }

    ~UdpServer()
    {
        close(_sockfd);
    }

private:
    // std::string _ip;
    uint16_t _port;
    int _sockfd;
    cb_t _OnMessage;
};

2.6 Main.cpp(服务端)

cpp 复制代码
#include "UdpServer.hpp"
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <memory>

using namespace std;

void Usage(const string& proc)
{
    // cout << proc << " localip localport\n" << endl;
    cout << proc << " localport\n" << endl;
}

string DefalutMessage(string message)
{
    return message + " [I've already gotten the information.]";
}

vector<string> black_list{
    "rm" , "cp" , "mv" , "top" , "vim" , "vi" , "nano" , "kill"
};

bool IsSafe(const string &message)
{
    size_t pos = 0;
    for(auto v : black_list)
    {
        pos = message.find(v);
        if(pos != string::npos)
            return false;
    }
    return true;
}

string ExecuteCommand(string message)
{
    if(!IsSafe(message))
    {
        return "Prohibited Command";
    }
    FILE* fp = popen(message.c_str(),"r");
    if(!fp)
        return "Unknown command";

    char buffer[1024];
    string response;
    while(1)
    {
        if(!fgets(buffer,1023,fp))
            break;
        response+=buffer;
    }

    fclose(fp);
    return response;
}

// ./udpserver localip localport
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = stoi(argv[1]);
    // unique_ptr<UdpServer> uq = make_unique<UdpServer>(DefalutMessage,port);
    unique_ptr<UdpServer> uq = make_unique<UdpServer>(ExecuteCommand,port);

    uq->Init();
    uq->Start();

    return 0;
}

2.7 UdpClient.cpp(客户端)

cpp 复制代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

using namespace std;

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

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

    int clientfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientfd < 0)
    {
        cout << "create socket fail\n" << endl;
        exit(1);
    }
    cout << "create socket success : " << clientfd << endl;


    struct sockaddr_in ServerSocket;
    memset(&ServerSocket,0,sizeof(ServerSocket));
    socklen_t Slen = sizeof(ServerSocket);
    ServerSocket.sin_family = AF_INET;
    ServerSocket.sin_addr.s_addr = inet_addr(argv[1]);
    ServerSocket.sin_port = htons(stoi(argv[2]));

    while (1)
    {
        string inbuffer;
        cout << "Please Enter# ";
        getline(cin,inbuffer);

        int n = sendto(clientfd, inbuffer.c_str(), inbuffer.size(), 0, (struct sockaddr*)&ServerSocket, Slen);
        if (n < 0)
        {
            cout << "sendto fail , errno : " << errno <<  ", error : " << strerror(errno) << endl;
            break;
        }
        else
        {
            struct sockaddr_in temp;
            socklen_t tlen = sizeof(temp);
            char buffer[1024];
            int m = recvfrom(clientfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&temp, &tlen);
            if(m > 0)
            {
                buffer[m] = 0;
                cout << "Server say# " << buffer << endl;
            }
            else
            {
                break;
            }
        }
    }

    close(clientfd);

    return 0;
}

三、UdpServerChat(实现简单的聊天室)

3.1 Comm.hpp(公共数据)

cpp 复制代码
#pragma once

enum {
    Socket_err = 1,
    Bind_err,
    Accept_err,
    Recvfrom_err
};

3.2 Log.hpp(日志)

cpp 复制代码
#pragma once

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

using namespace std;

// 日志等级
enum
{
    Debug = 0, // 调试
    Info,      // 正常
    Warning,   // 警告
    Error,     // 错误,但程序并未直接退出
    Fatal      // 程序直接挂掉
};

enum
{
    Screen = 10, // 打印到显示器上
    OneFile,     // 打印到一个文件中
    ClassFile    // 按照日志等级打印到不同的文件中
};

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 "Unknow";
    }
}

const char* default_filename = "log.";
const int default_style = Screen;
const char* defaultdir = "log";

class Log
{
public:
    Log()
        : style(default_style), filename(default_filename)
    {
        // mkdir(defaultdir,0775);
        pthread_mutex_init(&_log_mutex, nullptr);
    }

    void SwitchStyle(int sty)
    {
        style = sty;
    }

    void WriteLogToOneFile(const string& logname, const string& logmessage)
    {
        int fd = open(logname.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);
        if (fd == -1)
            return;

        pthread_mutex_lock(&_log_mutex);
        write(fd, logmessage.c_str(), logmessage.size());
        pthread_mutex_unlock(&_log_mutex);

        close(fd);
    }

    void WriteLogToClassFile(const string& levelstr, const string& logmessage)
    {
        mkdir(defaultdir, 0775);

        string name = defaultdir;
        name += "/";
        name += filename;
        name += levelstr;

        WriteLogToOneFile(name, logmessage);
    }

    void WriteLog(int level, const string& logmessage)
    {
        switch (style)
        {
        case Screen:
        {
            pthread_mutex_lock(&_log_mutex);
            cout << logmessage;
            pthread_mutex_unlock(&_log_mutex);
        }
        break;
        case OneFile:
            WriteLogToClassFile("All", logmessage);
            break;
        case ClassFile:
            WriteLogToClassFile(LevelToString(level), logmessage);
            break;
        default:
            break;
        }
    }

    string GetTime()
    {
        time_t CurrentTime = time(nullptr);

        struct tm* curtime = localtime(&CurrentTime);
        char time[128];

        // localtime 的年是从1900开始的,所以要加1900, 月是从0开始的所以加1
        snprintf(time, sizeof(time), "%d-%d-%d %d:%d:%d",
            curtime->tm_year + 1900, curtime->tm_mon + 1, curtime->tm_mday,
            curtime->tm_hour, curtime->tm_min, curtime->tm_sec);

        return time;
        return "";
    }

    void LogMessage(int level, const char* format, ...)
    {
        char left[1024];
        string Levelstr = LevelToString(level).c_str();
        string Timestr = GetTime().c_str();
        string Idstr = to_string(getpid());
        snprintf(left, sizeof(left), "[%s][%s][%s] ",
            Levelstr.c_str(), Timestr.c_str(), Idstr.c_str());

        va_list args;
        va_start(args, format);
        char right[1024];
        vsnprintf(right, sizeof(right), format, args);

        string logmessage = left;
        logmessage += right;

        WriteLog(level, logmessage);

        va_end(args);
    }

    ~Log()
    {
        pthread_mutex_destroy(&_log_mutex);
    };

private:
    int style;
    string filename;

    pthread_mutex_t _log_mutex;
};

Log lg;

class Conf
{
public:
    Conf()
    {
        lg.SwitchStyle(Screen);
    }
    ~Conf()
    {
    }
};

Conf conf;

3.3 InetAddr.hpp(管理sockaddr_in相关信息)

cpp 复制代码
#pragma once

#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string>

class InetAddr
{
public:
    InetAddr(struct sockaddr_in sock)
        :_sock(sock)
    {}

    std::string Ip()
    {
        return inet_ntoa(_sock.sin_addr);
    }

    uint16_t Port()
    {
        return ntohs(_sock.sin_port);
    }

    std::string PrintDebug()
    {
        std::string info = "[";
        info += Ip();
        info += ":";
        info += std::to_string(Port());
        info += "]";
        info += "# ";

        return info;
    }

    ~InetAddr()
    {}

private:
    struct sockaddr_in _sock;
};

3.4 NoCopy.hpp(防拷贝)

cpp 复制代码
#pragma once

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

3.5 Lockguard.hpp(自动管理锁)

cpp 复制代码
#pragma once

#include <iostream>

class Mutex
{
public:
    Mutex(pthread_mutex_t* lock)
        :pmutex(lock)
    {}

    void Lock()
    {
        pthread_mutex_lock(pmutex);
    }

    void Unlock()
    {
        pthread_mutex_unlock(pmutex);
    }

    ~Mutex()
    {}
public:
    pthread_mutex_t* pmutex;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t* lock)
        :mutex(lock)
    {
        mutex.Lock();
    }

    ~LockGuard()
    {
        mutex.Unlock();
    }
public:
    Mutex mutex;
};

3.6 Thread.hpp(封装线程)

cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

using namespace std;

// typedef function<void()> func_t;
template<class T>
using func_t = function<void(T&)>;

template<class T>
class Thread
{
public:
    Thread(const string &threadname, func_t<T> func ,const T& data)
        : _pid(0), _threadname(threadname), _func(func), isrunning(false) , _data(data)
    {
    }

    // 线程需要执行的函数
    static void *ThreadRoutine(void *arg)
    {
        Thread *pt = (Thread *)arg;

        pt->_func(pt->_data);

        return nullptr;
    }

    // 线程开创建并执行
    bool Start()
    {
        int n = pthread_create(&_pid, nullptr, ThreadRoutine, this);
        if (n == 0)
        {
            isrunning = true;
            // cout << "is strat , is running : " << isrunning << endl;
            return true;
        }
        else
        {
            return false;
        }
    }

    // 线程等待
    bool Join()
    {
        if(!isrunning)  return false;
        return pthread_join(_pid, nullptr);
    }

    bool IsRunning()
    {
        return isrunning;
    }

    string ThreadName()
    {
        return _threadname;
    }

    ~Thread()
    {
    }

private:
    pthread_t _pid;
    string _threadname;
    bool isrunning;
    func_t<T> _func;
    T _data;
};

3.7 ThreadPool.hpp(线程池)

cpp 复制代码
#pragma once

#include <string>
#include <queue>
#include <vector>
#include <pthread.h>
#include <functional>
#include "Thread.hpp"
#include "Log.hpp"
#include "LockGuard.hpp"

using namespace std;

const int default_threadnum = 3;

class ThreadDate
{
public:
    ThreadDate(const string &name)
        : threadname(name)
    {
    }
    ~ThreadDate()
    {
    }

public:
    string threadname;
};

template <class T>
class ThreadPool
{
public:
    static ThreadPool *GetInstance()
    {
        if (_psl == nullptr)
        {
            LockGuard lock(&_sig_lock);
            if (_psl == nullptr)
            {
                lg.LogMessage(Info, "create singleton is success\n");
                _psl = new ThreadPool<T>();
            }
        }
        return _psl;
    }

    static void DelInstance()
	{
		if (_psl)
		{
			LockGuard lock(&_sig_lock);
			if (_psl)
			{
				delete _psl;
				_psl = nullptr;
                
                lg.LogMessage(Info, "destroy singleton is success\n");
			}
		}
	}

    // 启动所有线程
    bool Start()
    {
        for (auto &t : _threads)
        {
            t.Start();
            lg.LogMessage(Info, "%s , is running...\n", t.ThreadName().c_str());
        }

        return true;
    }

    // 等待所有线程终止
    void Join()
    {
        for (auto &t : _threads)
        {
            t.Join();
        }
    }

    // 线程等待当前条件变量
    void Thread_Wait(const ThreadDate &td)
    {
        pthread_cond_wait(&_cond, &_mutex);
        lg.LogMessage(Debug, "no task , %s is sleeping...\n", td.threadname.c_str());
    }

    // 唤醒当前条件变量下的某个线程
    void Thread_Wakeup()
    {
        pthread_cond_signal(&_cond);
    }

    // 添加任务
    bool Push(T &in)
    {
        LockGuard lockguard(&_mutex);
        _q.push(in);
        Thread_Wakeup();

        return true;
    }

    // 线程需要执行的回调函数
    void ThreadRun(const ThreadDate &td)
    {
        while (1)
        {
            T t;

            // 取任务
            {
                LockGuard lockguard(&_mutex);

                while (_q.empty())
                {
                    Thread_Wait(td);
                    lg.LogMessage(Debug, "haven task , %s is wakeup\n", td.threadname.c_str());
                }
                t = _q.front();
                _q.pop();
            }
            // 执行任务
            t();
            // lg.LogMessage(Debug, "%s handler task %s done , result is %s\n",
            //               td.threadname.c_str(), t.PrintTask().c_str(), t.PrintResult().c_str());
        }
    }

private:
    ThreadPool(int num = default_threadnum)
        : _threadnum(num)
    {
        // 初始化锁和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);

        // 创建线程
        for (int i = 0; i < _threadnum; i++)
        {
            string threadname = "thread-" + to_string(i + 1);
            ThreadDate td(threadname);
            // 由于Thread执行的是线程池的类内函数,而Thread调用的函数中并没有this指针
            // 所以这里就使用bind函数,调整一下Thread调用函数的参数,使函数可以多接收一个参数
            _threads.push_back(Thread<ThreadDate>(threadname, bind(&ThreadPool<T>::ThreadRun, this, placeholders::_1), td));
            lg.LogMessage(Info, "%s is create\n", threadname.c_str());
        }
    }

    // 销毁锁和条件变量
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

    ThreadPool(const ThreadPool&) = delete;
    const ThreadPool& operator=(const ThreadPool&) = delete;

private:
    queue<T> _q;
    vector<Thread<ThreadDate>> _threads;
    int _threadnum;
    static ThreadPool<T> *_psl;
    static pthread_mutex_t _sig_lock;

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_psl = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::_sig_lock = PTHREAD_MUTEX_INITIALIZER;

3.8 Daemon.hpp(使进程变为守护进程)

cpp 复制代码
#pragma once

#include <signal.h>
#include <unistd.h>
#include <stdlib.h> 
#include <sys/types.h>
#include <fcntl.h>

const char* root = "/";
const char* dev_null = "/dev/null";

bool Daemon(bool nochdir, bool noclose)
{
    // 1、忽略可能引起程序异常退出的信号 SIGCHLD SIGPIPE
    signal(SIGCHLD,SIG_IGN);
    signal(SIGPIPE,SIG_IGN);
    // 2、创建子进程,让父进程退出,使得子进程不成为组长
    pid_t pid = fork();
    if(pid > 0) exit(0);
    // 3、设置自己成为一个新的会画,setsid
    setsid();
    // 4、每一个进程都有自己的CWD(当前工作路径),是否将当前进程的CWD改为根目录
    // ​	改为根目录以后,进程可以以绝对路径的方式找到操作系统中的文件
    if(nochdir)
        chdir(root);

    // 5、变成守护进程以后,就不需要与用户的输入、输出和错误进行关联了
    // ​	可以将它们全部关闭,但难免服务器中会有输入、输出和错误
    // ​	向关闭的文件描述符中写入可能会导致进程退出
    // ​	所以这里将它们关闭不是最优解,而是将它们重定向到/dev/null中
    // ​	因为写入到/dev/null的数据会被直接丢弃,而从/dev/null读取信息,会默认读取到文件结尾
    if(noclose)
    {
        int fd = open(dev_null,O_RDWR);
        if(fd > 0)
        {
            dup2(fd,0);
            dup2(fd,1);
            dup2(fd,2);
            close(fd);
        }
    }
    else  // 不推荐
    {
        close(0);
        close(1);
        close(2);
    }

    return true;
}

3.9 UdpServer.hpp(服务端封装)

cpp 复制代码
#pragma once

#include "Comm.hpp"
#include "Nocopy.hpp"
#include "Log.hpp"
#include "LockGuard.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <string>
#include <errno.h>
#include <functional>
#include <pthread.h>

const static int defaultfd = -1;
const static uint16_t defaultport = 8888;
const static int defaultsize = 1024;
 
using task_t = function<void(void)>;

// 服务器的IP不应该是固定的,而是任意的
class UdpServer : public Nocopy
{
public:
    UdpServer(uint16_t port = defaultport)
        :_port(port),_sockfd(defaultfd)
    {
    }

    void Init()
    {
        // 创建socket
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            lg.LogMessage(Fatal, "create socket fail\n");
            exit(Socket_err);
        }
        lg.LogMessage(Info, "create socket success , sockfd : %d\n", _sockfd);

        // 2、绑定,指定网络信息,协议家族,IP地址,端口号,结构体填充,设置到内核
        struct sockaddr_in ServerSockaddr;
        memset(&ServerSockaddr,0,sizeof(ServerSockaddr));
        ServerSockaddr.sin_family = AF_INET;
        ServerSockaddr.sin_port = htons(_port);
        ServerSockaddr.sin_addr.s_addr = INADDR_ANY; // 就是0
        socklen_t len = sizeof(ServerSockaddr);
        
        int n = ::bind(_sockfd, (struct sockaddr*)&ServerSockaddr, len);
        if (n == -1)
        {
            // cout << inet_ntoa(ServerSockaddr.sin_addr) << ":" << ntohs(ServerSockaddr.sin_port) << endl;
            lg.LogMessage(Fatal, "bind fail , errno : %d , %s\n", errno , strerror(errno));
            exit(Bind_err);
        }
        lg.LogMessage(Info, "bind success , sockfd : %d\n", _sockfd);

        pthread_mutex_init(&_mutex,nullptr);
        ThreadPool<task_t>::GetInstance()->Start();
    }

    void AddOnlineUser(InetAddr addr)
    {
        LockGuard lock(&_mutex);
        for(auto tmp : _online_user)
        {
            if(tmp == addr)
                return;
        }
        _online_user.push_back(addr);
    }

    void Route(int sockfd ,const string& message)
    {
        LockGuard lock(&_mutex);
        for(auto &tmp : _online_user)
        {
            sendto(_sockfd, message.c_str(), message.size(), 0, (struct sockaddr*)&tmp.GetAddr(), sizeof(tmp));
        }
    }

    void Start()
    {
        char inbuffer[defaultsize];
        for (;;)
        {
            struct sockaddr_in ClientSockaddr;
            socklen_t ClientLen = sizeof(ClientSockaddr);

            // 接收数据
            ssize_t n = recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&ClientSockaddr, &ClientLen);
            if (n > 0)
            {
                inbuffer[n] = 0;

                InetAddr inetaddr(ClientSockaddr);
                AddOnlineUser(inetaddr);
                cout << inetaddr.PrintDebug() << inbuffer<< endl;

                string message = inbuffer;
                task_t task = bind(&UdpServer::Route,this,_sockfd,message);

                ThreadPool<task_t>::GetInstance()->Push(task);
            }
        }
    }

    ~UdpServer()
    {
        pthread_mutex_destroy(&_mutex);
        close(_sockfd);
    }

private:
    // std::string _ip;
    uint16_t _port;
    int _sockfd;
    vector<InetAddr> _online_user;

    pthread_mutex_t _mutex;
};

3.10 Main.cpp(服务端)

cpp 复制代码
#include "UdpServer.hpp"
#include "Daemon.hpp"
#include <iostream>
#include <unistd.h>
#include <stdio.h>
#include <string>
#include <vector>
#include <memory>

using namespace std;

void Usage(const string& proc)
{
    // cout << proc << " localip localport\n" << endl;
    cout << proc << " localport\n" << endl;
}


// ./udpserver localip localport
int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(1);
    }

    uint16_t port = stoi(argv[1]);

    Daemon(true,true);
    lg.SwitchStyle(OneFile);
    // unique_ptr<UdpServer> uq = make_unique<UdpServer>(DefalutMessage,port);
    unique_ptr<UdpServer> uq = make_unique<UdpServer>(port);

    uq->Init();
    uq->Start();

    return 0;
}

3.11 UdpClient.cpp(客户端)

cpp 复制代码
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <string>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "Thread.hpp"
#include "LockGuard.hpp"
#include "InetAddr.hpp"

using namespace std;

class ThreadDate
{
public:
    ThreadDate(int sockfd, InetAddr addr)
        : _sockfd(sockfd), _addr(addr)
    {
    }

    ~ThreadDate()
    {
    }

public:
    int _sockfd;
    struct InetAddr _addr;
};

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

void RecverRoutine(ThreadDate td)
{
    struct sockaddr_in temp;
    socklen_t tlen = sizeof(temp);
    char inbuffer[1024];
    while (1)
    {
        int m = recvfrom(td._sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr *)&temp, &tlen);
        if (m > 0)
        {
            inbuffer[m] = 0;
            cerr << td._addr.PrintDebug() << inbuffer << endl;
        }
    }
}

void SenderRoutine(ThreadDate td)
{
    string buffer;
    while (1)
    {
        cout << "Please Enter# ";
        getline(cin, buffer);

        int n = sendto(td._sockfd, buffer.c_str(), buffer.size(), 0, (struct sockaddr *)&td._addr.GetAddr(), sizeof(td._addr));
        if (n < 0)
            break;
    }
}

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

    int clientfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (clientfd < 0)
    {
        cout << "create socket fail\n"
             << endl;
        exit(1);
    }
    cout << "create socket success : " << clientfd << endl;

    struct sockaddr_in ServerSocket;
    memset(&ServerSocket, 0, sizeof(ServerSocket));
    socklen_t Slen = sizeof(ServerSocket);
    ServerSocket.sin_family = AF_INET;
    ServerSocket.sin_addr.s_addr = inet_addr(argv[1]);
    ServerSocket.sin_port = htons(stoi(argv[2]));

    ThreadDate td(clientfd, ServerSocket);
    Thread<ThreadDate> sender("sender", SenderRoutine, td);
    Thread<ThreadDate> recver("recver", RecverRoutine, td);

    sender.Start();
    recver.Start();

    sender.Join();
    recver.Join();

    close(clientfd);

    return 0;
}

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。

希望大家以后也能和我一起进步!!🌹🌹

如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

相关推荐
宝山哥哥1 小时前
网络信息安全学习笔记1----------网络信息安全概述
网络·笔记·学习·安全·网络安全
AmosTian1 小时前
【系统与工具】Linux——Linux简介、安装、简单使用
linux·运维·服务器
Dsocc3 小时前
TCP 动态选路协议全面研究:OSPF、BGP 与 IS-IS 的比较与应用分析
网络·网络协议·tcp/ip
YC运维3 小时前
RIP实验以及核心原理
运维·网络·智能路由器
阿蒙Amon3 小时前
C#随机数生成全面详解:从基础到高级应用
服务器·网络·c#
这我可不懂4 小时前
Python 项目快速部署到 Linux 服务器基础教程
linux·服务器·python
车车不吃香菇5 小时前
java idea 本地debug linux服务
java·linux·intellij-idea
tan77º5 小时前
【Linux网络编程】Socket - TCP
linux·网络·c++·tcp/ip
kfepiza6 小时前
Linux的`if test`和`if [ ]中括号`的取反语法比较 笔记250709
linux·服务器·笔记·bash
CodeWithMe6 小时前
【Note】《深入理解Linux内核》 第十九章:深入理解 Linux 进程通信机制
linux·运维·php