首页 > 运维 > linux运维 > 正文

Linux__之__基于UDP的Socket编程网络通信

蓮花仙者
发布: 2025-04-18 14:36:11
原创
284人浏览过

前言

本文旨在通过Linux系统接口实现网络通信,帮助我们更好地掌握socket套接字的使用。通过学习socket网络通信,我们将发现网络通信的本质不过是套路。接下来,让我们直接进入代码编写部分。

  1. 事先准备

今天我们将模拟实现一个echo demo,即客户端向服务器发送信息,服务器接收并回显这些信息。为了提高代码的可读性和调试性,我们将使用日志信息。我将带领大家手动编写日志代码,并将其应用于echo demo中。在日志中,如果需要访问临界资源,我们需要进行加锁和解锁操作。这里我将引导大家基于Linux系统调用封装锁,使得锁的使用更加便捷。

1.1 Mutex.hpp

想要封装锁,我们首先需要了解锁的概念。简而言之,锁是原子性的操作,用于保护在多线程环境下共享资源的安全。锁的定义有两种方式:一种是使用宏进行全局初始化,无需手动释放,由操作系统自动释放;另一种是局部定义并使用init进行初始化。我们将使用init初始化方法,第一个参数是锁,第二个参数为锁的属性,默认为nullptr。销毁时使用destroy系统调用。我们将这些操作封装在一个LockGuard类中,利用对象的特性,离开局部作用域时自动释放,进一步简化锁的使用。

#pragma once
#include <iostream>
#include <pthread.h>
namespace LockMoudle {
    class Mutex {
    public:
        Mutex(const Mutex&) = delete;
        const Mutex& operator=(const Mutex&) = delete;
        Mutex() {
            int n = ::pthread_mutex_init(&_lock, nullptr);
            (void)n;
        }
        ~Mutex() {
            int n = ::pthread_mutex_destroy(&_lock);
            (void)n;
        }
        void Lock() {
            //加锁
            int n = pthread_mutex_lock(&_lock);
            (void)n;
        }
        //获取锁
        pthread_mutex_t *LockPtr() {
            return &_lock;
        }
        //解锁
        void Unlock() {
            int n = ::pthread_mutex_unlock(&_lock);
            (void)n;
        }
    private:
        pthread_mutex_t _lock;
    };
    class LockGuard {
    public:
        LockGuard(Mutex &mtx)
        :_mtx(mtx)
        {
            _mtx.Lock();
        }
        ~LockGuard() {
            _mtx.Unlock();
        }
    private:
        Mutex &_mtx;
    };
}
登录后复制

1.2 Log.hpp

在日志类中,如果使用文件策略,为了防止多线程并发访问和创建多个文件,我们需要进行加锁,确保一次只有一个线程访问。

首先明确日志策略,是刷新到文件缓冲区还是命令行缓冲区。我们定义基类,使用子类继承基类的虚方法实现多态,并使用内部类创建日志消息,然后调用外部类的策略方法进行打印。

Linux__之__基于UDP的Socket编程网络通信Linux__之__基于UDP的Socket编程网络通信

#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <fstream>
#include <sstream>
#include <memory>
#include <filesystem> //C++17
#include <unistd.h>
#include <time.h>
#include "Mutex.hpp"
namespace LogModule {
    using namespace LockMoudle;
    //获取当前系统时间
    std::string CurrentTime() {
        time_t time_stamp = ::time(nullptr);
        struct tm curr;
        localtime_r(&time_stamp, &curr); //时间戳, 获取可读性较强的时间信息
        char buffer[1024];
        snprintf(buffer, sizeof(buffer), "%4d-%02d-%02d %02d:%02d:%02d",
            curr.tm_year + 1900,
            curr.tm_mon + 1,
            curr.tm_mday,
            curr.tm_hour,
            curr.tm_min,
            curr.tm_sec);
        return buffer;
    }
    //构成: 1. 构建日志字符串 2.刷新落盘
    //落盘策略(screen, file)
    //1.日志文件的默认路径和文件名
    const std::string defaultlogpath = "./log/";
    const std::string defaultlogname = "log.txt";
    //2.日志等级
    enum class LogLevel {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    //枚举类型转字符串
    std::string Level2String(LogLevel level) {
        switch (level) {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "None";
        }
    }
    //3.刷新策略
    class LogStrategy {
    public:
        virtual ~LogStrategy() = default; //虚析构函数,多态,能够正确调用对象进行析构, 编译器自动生成
        virtual void SyncLog(const std::string &message) = 0;//纯虚函数,子类必须手动实现
    };
    //3.1控制台策略
    class ConsoleLogStrategy : public LogStrategy {
    public:
        ConsoleLogStrategy() {}
        ~ConsoleLogStrategy() {}
        //向控制台打印日志信息message
        void SyncLog(const std::string &message) {
            LockGuard lockguard(_lock);
            std::cout << message << std::endl;
        }
    private:
        Mutex _lock;
    };
    //3.2文件策略
    class FileLogStrategy : public LogStrategy {
    public:
        FileLogStrategy(const std::string &path = defaultlogpath, const std::string &name = defaultlogname)
        : _path(path), _name(name) {
            _file.open(_path + _name, std::ios::app);
        }
        ~FileLogStrategy() {
            if(_file.is_open()) {
                _file.close();
            }
        }
        //向文件中写入日志信息message
        void SyncLog(const std::string &message) {
            LockGuard lockguard(_lock);
            _file << message << std::endl;
            _file.flush();
        }
    private:
        std::string _path;
        std::string _name;
        std::ofstream _file;
        Mutex _lock;
    };
    //4.日志记录器
    class Logger {
    public:
        Logger() : _strategy(nullptr) {}
        void EnableConsolelog() {
            _strategy = std::make_shared<ConsoleLogStrategy>();
        }
        void EnableFileLog() {
            _strategy = std::make_shared<FileLogStrategy>();
        }
        ~Logger(){}
        //一条完整的信息[2024-08-04 12:27:03] [DEBUG] [202938] [main.cc] [16] + 日志的可变部分(
        class LogMessage {
        public:
            LogMessage(LogLevel level, const std::string &filename, int line, Logger &logger)
            : _currtime(CurrentTime())
            , _level(level)
            , _pid(::getpid())
            , _filename(filename)
            , _line(line)
            , _logger(logger)
            {}
            //重载operator<<, 记录日志信息
            template<typename T>
            LogMessage& operator<<(const T &data) {
                std::ostringstream oss;
                oss << data;
                _loginfo += oss.str();
                return *this;
            }
            //同步日志信息
            ~LogMessage() {
                std::ostringstream oss;
                oss << "[" << _currtime << "] [" << Level2String(_level) << "] [" << _pid << "] [" << _filename << "] [" << _line << "] " << _loginfo;
                _logger.SyncLog(oss.str());
            }
        private:
            std::string _currtime; //当前日志的时间
            LogLevel _level; //日志等级
            pid_t _pid; //进程pid
            std::string _filename; //源文件
            int _line; //行号
            Logger &_logger; //策略
            std::string _loginfo; //日志信息
        };
        //重载operator(), 故意的拷贝
        LogMessage operator()(LogLevel level, const std::string &filename, int line) {
            return LogMessage(level, filename, line, *this);
        }
    private:
        std::shared_ptr<LogStrategy> _strategy;
    };
    Logger logger;
    #define LOG(Level) logger(Level, __FILE__, __LINE__)
    #define ENABLE_CONSOLE_LOG() logger.EnableConsolelog()
    #define ENABLE_FILE_LOG() logger.EnableFileLog()
}
登录后复制
  1. 编写Echo demo代码

2.1 UdpServer.hpp 和 UdpServer.cc

这里我们使用套接字进行通信,套接字可以简单理解为一个文件流。创建套接字后填写网络信息,并与内核绑定。由于我们使用的是云服务器,默认不需要绑定IP,因此我们只需绑定端口号,从命令行获取。

#include "UdpServer.hpp"
int main(int argc, char *argv[]) {
    if(argc != 2) {
        std::cerr << "Usage: " << argv[0] << " <port>" << std::endl;
        Die(USAGE_ERR);
    }
    uint16_t port = static_cast<uint16_t>(std::atoi(argv[1]));
    std::unique_ptr<UdpServer> svr_uptr = std::make_unique<UdpServer>(port);
    svr_uptr->InitServer();
    svr_uptr->Start();
    return 0;
}
登录后复制
#pragma once
#include <iostream>
#include <string>
#include <memory>
#include <cstring>
#include <cerrno>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "InetAddr.hpp"
#include "Log.hpp"
#include "Common.hpp"
using namespace LogModule;
const static int gsockfd = -1;
//const static std::string gdefaultip = "127.0.0.1" //表示本地主机
const static uint16_t gdefaultport = 8080;
class UdpServer {
public:
    //命令行输入ip + 端口号进行绑定, 虚拟机无需绑定ip, 只需指定端口号进行绑定即可
    UdpServer(uint16_t port = gdefaultport)
        : _sockfd(gsockfd)
        , _addr(port)
        , _isrunning(false)
        {}
    //都是套路
    void InitServer() {
        //1.创建套接字
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0); //指定网络通信模式. 面向数据包, 标记为设置为0
        if(_sockfd < 0) {
            LOG(LogLevel::ERROR) << "socket error: " << strerror(errno);
            Die(SOCKET_ERR);
        }
        //2.绑定套接字
        if(::bind(_sockfd, _addr.Netaddr(), _addr.NetAddrlen()) < 0) {
            LOG(LogLevel::ERROR) << "bind error: " << strerror(errno);
            Die(BIND_ERR);
        }
        _isrunning = true;
    }
    void Start() {
        char inbuffer[1024];
        struct sockaddr_in peer;
        socklen_t peerlen = sizeof(peer);
        while(_isrunning) {
            memset(inbuffer, 0, sizeof(inbuffer));
            int n = ::recvfrom(_sockfd, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&peer, &peerlen);
            if(n < 0) {
                LOG(LogLevel::ERROR) << "recvfrom error: " << strerror(errno);
                continue;
            }
            InetAddr cli(peer);
            inbuffer[n] = 0;
            std::string clientinfo = cli.Ip() + ":" + std::to_string(cli.Port()) + '#' + inbuffer;
            LOG(LogLevel::DEBUG) << "recvfrom client: " << clientinfo;
            //回显信息
            n = ::sendto(_sockfd, inbuffer, n, 0, (struct sockaddr*)&peer, peerlen);
            if(n < 0) {
                LOG(LogLevel::ERROR) << "sendto error: " << strerror(errno);
            }
        }
    }
    ~UdpServer() {
        if(_sockfd != gsockfd)
            ::close(_sockfd);
    }
private:
    int _sockfd;
    InetAddr _addr;
    bool _isrunning;
};
登录后复制

2.2 IntAddr.hpp 和 Commm.hpp

这里对IntAddr进行了封装,IntAddr包含了网络信息。网络通信中,我们需要对InetAddr进行强转,实现C语言版本的多态。

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Common.hpp"
class InetAddr {
private:
    void PortNet2Host() {
        _port = ::ntohs(_net_addr.sin_port);
    }
    void IpNet2Host() {
        char ipbuffer[64];
        const char *ip = ::inet_ntop(AF_INET,&_net_addr.sin_addr,ipbuffer, sizeof(ipbuffer));
        (void)ip;
    }
public:
    InetAddr(){}
    //如果传进来的是一个sockaddr_in, 网络转主机
    InetAddr(const struct sockaddr_in &addr) : _net_addr(addr) {
        PortNet2Host();
        IpNet2Host();
    }
    //如果传进来的是端口号, 就转化为网络, 服务器不需要自己绑定ip
    InetAddr(uint16_t port) : _port(port), _ip("") {
        _net_addr.sin_family = AF_INET;
        _net_addr.sin_port = htons(_port);
        _net_addr.sin_addr.s_addr = INADDR_ANY;
    }
    struct sockaddr* Netaddr() {return CONV(&_net_addr); }
    socklen_t NetAddrlen() {return sizeof(_net_addr); }
    std::string Ip() {return _ip; }
    uint16_t Port() {return _port; }
    ~InetAddr(){}
private:
    struct sockaddr_in _net_addr;
    std::string _ip;
    uint16_t _port;
};
登录后复制

Comman.hpp

#pragma once
#include<iostream>
#define Die(code) do {exit(code); } while(0)
#define CONV(v) (struct sockaddr *)(v)
enum{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR
};
登录后复制

2.3 Client.cc

客户端通过标准输入获取信息并发送到服务器,然后接收并打印服务器回显的内容。

#include "UdpClient.hpp"
#include "Common.hpp"
#include <iostream>
#include <cstring>
#include <string>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
    if(argc != 3) {
        std::cerr << "Usage: " << argv[0] << " <ip> <port>" << std::endl;
        Die(USAGE_ERR);
    }
    std::string ip = argv[1];
    uint16_t port = static_cast<uint16_t>(std::atoi(argv[2]));
    UdpClient client(ip, port);
    client.InitClient();
    char buffer[1024];
    while(true) {
        std::cout << "请输入要发送的信息: ";
        std::cin.getline(buffer, sizeof(buffer));
        if(strcmp(buffer, "quit") == 0) {
            break;
        }
        client.Send(buffer);
        int n = client.Recv(buffer, sizeof(buffer) - 1);
        if(n > 0) {
            buffer[n] = 0;
            std::cout << "服务器回显: " << buffer << std::endl;
        }
    }
    return 0;
}
登录后复制
  1. 运行结果

Linux__之__基于UDP的Socket编程网络通信

以上就是Linux__之__基于UDP的Socket编程网络通信的详细内容,更多请关注php中文网其它相关文章!

豆包AI编程
豆包AI编程

智能代码生成与优化,高效提升开发速度与质量!

下载
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习
PHP中文网抖音号
发现有趣的

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号