【Linux】Socket编程UDP

V1版本 - Echo server

UdpServer.hpp

cpp 复制代码
#pragma once

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

using namespace LogModule;

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

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _port(port), _sockfd(defaultfd), _is_running(false), _func(func)
    {

    }

    void Init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket failed";
            return;
        }
        LOG(LogLevel::INFO) << "create socket success, sockfd: " << _sockfd;

        // 2. 绑定socket信息,ip和端口
        // 2.1 填充socdaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;

        // 2.1.1 转换ip地址为网络字节序
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        // 2.1.1 绑定到任何一个本地地址
        local.sin_addr.s_addr = INADDR_ANY;
        // 2.1.2 转换端口为网络字节序
        local.sin_port = htons(_port);

        // 2.2 绑定socket
        // 为什么服务器端需要显示的bind呢?IP和端口必须是众所周知且不能轻易改变的。
        int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "bind socket failed";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind socket success" << " port: " << _port;
    }

    void Start()
    {
        _is_running = true;
        // 大部分的软件都是死循环
        while(_is_running)
        {
            char buffer[1024] = {0};
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 接收数据
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                int peer_port = ntohs(peer.sin_port); // 从网络中拿到的
                std::string peer_ip = inet_ntoa(peer.sin_addr); // 4字节网络风格 -> 点分十进制的字符串风格的IP

                buffer[s] = 0;

                std::string result = _func(buffer);
                LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的?

                // std::string echo_string = "server echo@ ";
                // echo_string += buffer;
                // 2. 发送数据
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;
    bool _is_running;
    func_t _func; // 服务器的回调函数,对数据进行处理
};

UdpServer.cc

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

std::string defaulthandler(const std::string &message)
{
    std::string hello = "hello, ";
    hello += message;
    return hello;
}

// 翻译系统,字符串当成英文单词

// ./udpserver ip port
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    Enable_Console_Log_Strategy();


    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, defaulthandler);
    usvr->Init();
    usvr->Start();
    return 0;
}

UdpClient.cc

cpp 复制代码
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
// client 要访问目标服务器,需要知道什么?
// 目标服务器的IP地址和端口号
// 但是我怎么知道服务器的ip和端口?
// 客服端和服务器是一家公司写的,客户端内置了服务器的IP地址和端口号,所以客户端可以直接访问服务器。

// ./udpserver ip port
int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "Create socket failed." << std::endl;
        return 2;
    }

    // 2. 本地的ip和端口是什么?要不要和上面的"文件"关联?
    // 问题:client要不要bind?需要bind
    //       client要不要显示的bind?首次发送消息,os会自动给client进行bind,os知道ip,端口号采用随机端口号的方式
    // 为什么?一个端口号只能被一个进程bind,为了避免client端口冲突,os会自动给client分配一个随机端口号
    // client端的端口号是几,不重要,只要是唯一的就行
    // 填写服务端信息
    struct sockaddr_in server;
    memset(&server, 0, 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 input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);
        int n = sendto(sockfd, input.c_str(), input.size(), 0,  (struct sockaddr*)&server, sizeof(server));
        if(n < 0)
        {
            std::cerr << "Send failed." << std::endl;
            return 3;
        }

        char buffer[1024] = {0};
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
        if(m < 0)
        {
            std::cerr << "Recv failed." << std::endl;
            return 4;
        }
        buffer[m] = '\0';
        std::cout << buffer << std::endl;
    }
    
    return 0;
}

V2 版本 - DictServer

dictionary.txt

cpp 复制代码
book: 书
pen: 钢笔
apple: 苹果
cat: 猫
dog: 狗
happy: 快乐的
sad: 悲伤的
love: 爱
friend: 朋友
family: 家庭
english: 英语
chinese: 中文

Dict.hpp

cpp 复制代码
#pragma once

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

using namespace LogModule;
const std::string defaultdict = "./dictionary.txt";
const std::string sep = ": ";

class Dict
{
public: 
    Dict(const std::string &path = defaultdict) :_dict_path(path)
    {}

    bool LoadDict()
    {
        std::ifstream in(_dict_path);
        if (!in.is_open())
        {
            LOG(LogLevel::ERROR) << "字典文件打开失败 " << _dict_path;
            return false;
        }
        std::string line;
        while (std::getline(in, line))
        {
            auto pos = line.find(sep);
            if(pos == std::string::npos)
            {
                LOG(LogLevel::WARNING) << "解析: " << line << "失败";
                continue;
            }
            std::string english = line.substr(0, pos);
            std::string chinese = line.substr(pos + sep.size());
            _dict[english] = chinese;
        }
        return true;
    }
    
    std::string Translate(const std::string& word, const InerAddr& client)
    {
        auto it = _dict.find(word);
        if(it == _dict.end())
        {
            return "None";
        }
        LOG(LogLevel::DEBUG) << "进入了翻译模块, " << client.Ip() << " : " << client.Port() << " : " << word << " -> " << it->second;

        return it->second;
    }

    ~Dict()
    {

    }
private:
    std::string _dict_path;
    std::unordered_map<std::string, std::string> _dict;
};

InetAddr.hpp

cpp 复制代码
#pragma once

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

class InerAddr
{
public:
    InerAddr(struct sockaddr_in &addr) : _addr(addr)
    {
        _port = ntohs(addr.sin_port);           // 从网络中拿到的
        _ip = inet_ntoa(addr.sin_addr); // 4字节网络风格 -> 点分十进制的字符串风格的IP
    }

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

    ~InerAddr()
    {

    }
private:
    struct sockaddr_in _addr;
    std::string _ip;
    uint16_t _port;
};

UdpServer.hpp

cpp 复制代码
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
#include "InerAddr.hpp"

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

const int defaultfd = -1;

@@ -72,13 +72,12 @@ public:
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                InerAddr client(peer);

                buffer[s] = 0;

                std::string result = _func(buffer, client);
                // LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的?

                // std::string echo_string = "server echo@ ";
                // echo_string += buffer;
                // 2. 发送数据
                sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;
    bool _is_running;
    func_t _func; // 服务器的回调函数,对数据进行处理
};

UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "Dict.hpp"
#include "UdpServer.hpp"

std::string defaulthandler(const std::string &message)
{
    std::string hello = "hello, ";
    hello += message;
    return hello;
}

// 翻译系统,字符串当成英文单词

// ./udpserver ip port
int main(int argc, char* argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    Enable_Console_Log_Strategy();

    // 1. 字典对象提供翻译功能
    Dict dict;
    dict.LoadDict();

    // 2. 网络服务器对象提供通信功能
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&dict](const std::string &word, const InerAddr& cli) -> std::string {
        return dict.Translate(word, cli);
    });
    usvr->Init();
    usvr->Start();
    return 0;
}

V3版本 - 简单聊天室

Route.hpp

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

using namespace LogModule;
using namespace MutexModule;

class Route
{
private:
    bool IsExist(const InerAddr &peer)
    {
        for (auto &user : _online_user)
        {
            if (user == peer)
                return true;
        }
        return false;
    }
    void AddUser(const InerAddr &peer)
    {
        LOG(LogLevel::INFO) << "新增一个在线用户:" << peer.StringAddr();
        _online_user.push_back(peer);
    }

    void DeleteUser(InerAddr &peer)
    {
        for (auto iter = _online_user.begin(); iter != _online_user.end(); ++iter)
        {
            if (*iter == peer)
            {
                _online_user.erase(iter);
                break;
            }
        }
    }

public:
    Route()
    {
    }
    void MessageRoute(int sockfd, const std::string &message, InerAddr &peer)
    {
        LockGuard lock(_mutex);
        if (!IsExist(peer))
        {
            AddUser(peer);
        }

        std::string send_message = peer.StringAddr() + "# " + message;

        for (auto &user : _online_user)
        {
            sendto(sockfd, send_message.c_str(), send_message.size(), 0, (const struct sockaddr *)&(user.NetAddr()), sizeof(user.NetAddr()));
        }

        // 这个用户已经在线
        if (message == "QUIT")
        {
            LOG(LogLevel::INFO) << "用户" << peer.StringAddr() << "下线";
            DeleteUser(peer);
        }
    }
    ~Route()
    {
    }

private:
    // 首次给我发消息,等同于登录
    std::vector<InerAddr> _online_user;
    Mutex _mutex;
};

UdpServer.hpp

cpp 复制代码
#pragma once

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

using namespace ThreadPoolModule;

using func_t = std::function<void(int sockfd, const std::string&, InerAddr&)>;

const int defaultfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port, func_t func)
        : _port(port), _sockfd(defaultfd), _is_running(false), _func(func)
    {

    }

    void Init()
    {
        // 创建套接字
        _sockfd = socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "create socket failed";
            return;
        }
        LOG(LogLevel::INFO) << "create socket success, sockfd: " << _sockfd;

        // 2. 绑定socket信息,ip和端口
        // 2.1 填充socdaddr_in结构体
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;

        // 2.1.1 转换ip地址为网络字节序
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        // 2.1.1 绑定到任何一个本地地址
        local.sin_addr.s_addr = INADDR_ANY;
        // 2.1.2 转换端口为网络字节序
        local.sin_port = htons(_port);

        // InerAddr addr("0", _port);
        // addr.NetAddr();

        // 2.2 绑定socket
        // 为什么服务器端需要显示的bind呢?IP和端口必须是众所周知且不能轻易改变的。
        int n = bind(_sockfd, (struct sockaddr*)&local, sizeof(local));
        if(n < 0)
        {
            LOG(LogLevel::FATAL) << "bind socket failed";
            exit(2);
        }
        LOG(LogLevel::INFO) << "bind socket success" << " port: " << _port;
    }

    void Start()
    {
        _is_running = true;
        // 大部分的软件都是死循环
        while(_is_running)
        {
            char buffer[1024] = {0};
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 1. 接收数据
            ssize_t s = recvfrom(_sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&peer, &len);
            if(s > 0)
            {
                InerAddr client(peer);

                buffer[s] = 0;

                _func(_sockfd, buffer, client);
                // LOG(LogLevel::DEBUG) << "[" << peer_ip << ":" << peer_port << "]# " << buffer; // 1. 消息内容 2. 谁发的?

                // std::string echo_string = "server echo@ ";
                // echo_string += buffer;
                // 2. 发送数据
                // sendto(_sockfd, result.c_str(), result.size(), 0, (struct sockaddr*)&peer, len);
            }
        }
    }

    ~UdpServer()
    {
    }

private:
    int _sockfd;
    uint16_t _port;
    // std::string _ip;
    bool _is_running;
    func_t _func; // 服务器的回调函数,对数据进行处理
};

InetAddr.hpp

cpp 复制代码
#pragma once

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

class InerAddr
{
public:
    InerAddr(struct sockaddr_in &addr) : _addr(addr)
    {
        _port = ntohs(addr.sin_port);           // 从网络中拿到的
        // _ip = inet_ntoa(addr.sin_addr); // 4字节网络风格 -> 点分十进制的字符串风格的IP
        char buffer[64];
        inet_ntop(AF_INET, &addr.sin_addr, buffer, sizeof(buffer));
        _ip = buffer;
    }

    InerAddr(const std::string &ip, uint16_t port): _ip(ip), _port(port)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_port);
    }

    std::string Ip() const { return _ip; }
    uint16_t Port() const { return _port; }
    const struct sockaddr_in &NetAddr() { return _addr; }

    bool operator==(const InerAddr& addr) const
    {
        return addr._ip == _ip && addr._port == _port;
    }
    std::string StringAddr() const
    {
        return _ip + ":" + std::to_string(_port);
    }
    ~InerAddr()
    {

    }
private:
    struct sockaddr_in _addr;
    
    std::string _ip;
    uint16_t _port;
};

UdpServer.cc

cpp 复制代码
#include <iostream>
#include <memory>
#include "Dict.hpp"
#include "UdpServer.hpp"
#include "Route.hpp"

std::string defaulthandler(const std::string &message)
{
    std::string hello = "hello, ";
    hello += message;
    return hello;
}

// 翻译系统,字符串当成英文单词

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

// ./udpserver ip port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        return 1;
    }
    // std::string ip = argv[1];
    uint16_t port = std::stoi(argv[1]);

    Enable_Console_Log_Strategy();

    // 1. 路由服务
    Route r;

    // 2. 线程池
    auto tp = ThreadPool<task_t>::GetIntance();
    std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r, &tp](int sockfd, const std::string &message, InerAddr& peer) {
        auto t = std::bind(&Route::MessageRoute, &r, sockfd, message, peer);
        tp->Enqueue(t);
    });

    // 3. 网络服务器对象提供通信功能
    // std::unique_ptr<UdpServer> usvr = std::make_unique<UdpServer>(port, [&r](int sockfd, const std::string &message, InerAddr& peer){
    //     r.MessageRoute(sockfd, message, peer);
    // });
    usvr->Init();
    usvr->Start();
    return 0;
}

UdpClient.cc

cpp 复制代码
#include <iostream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <cstring>
#include "Thread.hpp"
// client 要访问目标服务器,需要知道什么?
// 目标服务器的IP地址和端口号
// 但是我怎么知道服务器的ip和端口?
// 客服端和服务器是一家公司写的,客户端内置了服务器的IP地址和端口号,所以客户端可以直接访问服务器。

int sockfd = 0;
std::string server_ip;
uint16_t server_port = 0;
pthread_t id = 0;

using namespace ThreadModlue;

void Recv()
{
    while (true)
    {

        char buffer[1024] = {0};
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int m = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr *)&peer, &len);
        if (m < 0)
        {
            std::cerr << "Recv failed." << std::endl;
            break;
        }
        buffer[m] = '\0';
        std::cerr << buffer << std::endl;
    }
}

void Send()
{
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    const std::string online = "inline";
    sendto(sockfd, online.c_str(), online.size(), 0, (struct sockaddr *)&server, sizeof(server));

    while (true)
    {
        std::string input;
        std::cout << "Please Enter# ";
        std::getline(std::cin, input);
        int n = sendto(sockfd, input.c_str(), input.size(), 0, (struct sockaddr *)&server, sizeof(server));
        if (n < 0)
        {
            std::cerr << "Send failed." << std::endl;
            break;
        }

        if (input == "QUIT")
        {
            pthread_cancel(id);
            break;
        }
    }
}

// ./udpserver ip port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }
    server_ip = argv[1];
    server_port = std::stoi(argv[2]);

    // 1. 创建套接字
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "Create socket failed." << std::endl;
        return 2;
    }

    // 创建线程
    Thread recver(Recv);
    Thread sender(Send);

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

    id = recver.Id();

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

    // 2. 本地的ip和端口是什么?要不要和上面的"文件"关联?
    // 问题:client要不要bind?需要bind
    //       client要不要显示的bind?首次发送消息,os会自动给client进行bind,os知道ip,端口号采用随机端口号的方式
    // 为什么?一个端口号只能被一个进程bind,为了避免client端口冲突,os会自动给client分配一个随机端口号
    // client端的端口号是几,不重要,只要是唯一的就行
    // 填写服务端信息

    return 0;
}
相关推荐
CS_浮鱼2 小时前
【Linux】进程概念
linux·运维·服务器
ITVV3 小时前
hadoop-3.4.1 单机伪部署
大数据·linux·hadoop
Elias不吃糖3 小时前
epoll 事件全集、每个事件的含义、哪些事件在实际服务器中最常见、哪些会组合出现
linux·c++·event
雪芽蓝域zzs3 小时前
uni-app 将 base64 图片编码转为 Blob 本地文件路径
网络协议·udp·uni-app
人工智能训练4 小时前
Ubuntu中如何进入root用户
linux·运维·服务器·人工智能·ubuntu·ai编程·root
tianshiyeben4 小时前
WGCLOUD监控系统使用指南 - 告警消息整理完整版
linux·运维·服务器·系统安全·zabbix
Unlyrical4 小时前
splice, io_uring_prep_splice 调用(无效参数)
linux·服务器·c++·unix
---学无止境---5 小时前
Linux内核用户身份管理全链路深度剖析:setuid系统调用完整架构
linux
CS_浮鱼5 小时前
【Linux】进程控制
linux·运维·网络