【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;
}
相关推荐
A小辣椒13 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒17 小时前
TShark:基础知识
linux
AlfredZhao19 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩2 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言