0

0

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

蓮花仙者

蓮花仙者

发布时间:2025-04-18 14:36:11

|

335人浏览过

|

来源于php中文网

原创

前言

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

  1. 事先准备

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

1.1 Mutex.hpp

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

#pragma once
#include 
#include 
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 
#include 
#include 
#include 
#include 
#include 
#include  //C++17
#include 
#include 
#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();
        }
        void EnableFileLog() {
            _strategy = std::make_shared();
        }
        ~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
            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 _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

AVCLabs
AVCLabs

AI移除视频背景,100%自动和免费

下载

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

#include "UdpServer.hpp"
int main(int argc, char *argv[]) {
    if(argc != 2) {
        std::cerr << "Usage: " << argv[0] << " " << std::endl;
        Die(USAGE_ERR);
    }
    uint16_t port = static_cast(std::atoi(argv[1]));
    std::unique_ptr svr_uptr = std::make_unique(port);
    svr_uptr->InitServer();
    svr_uptr->Start();
    return 0;
}
#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#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 
#include 
#include 
#include 
#include 
#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
#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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
int main(int argc, char *argv[]) {
    if(argc != 3) {
        std::cerr << "Usage: " << argv[0] << "  " << std::endl;
        Die(USAGE_ERR);
    }
    std::string ip = argv[1];
    uint16_t port = static_cast(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编程网络通信

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
C语言变量命名
C语言变量命名

c语言变量名规则是:1、变量名以英文字母开头;2、变量名中的字母是区分大小写的;3、变量名不能是关键字;4、变量名中不能包含空格、标点符号和类型说明符。php中文网还提供c语言变量的相关下载、相关课程等内容,供大家免费下载使用。

401

2023.06.20

c语言入门自学零基础
c语言入门自学零基础

C语言是当代人学习及生活中的必备基础知识,应用十分广泛,本专题为大家c语言入门自学零基础的相关文章,以及相关课程,感兴趣的朋友千万不要错过了。

619

2023.07.25

c语言运算符的优先级顺序
c语言运算符的优先级顺序

c语言运算符的优先级顺序是括号运算符 > 一元运算符 > 算术运算符 > 移位运算符 > 关系运算符 > 位运算符 > 逻辑运算符 > 赋值运算符 > 逗号运算符。本专题为大家提供c语言运算符相关的各种文章、以及下载和课程。

354

2023.08.02

c语言数据结构
c语言数据结构

数据结构是指将数据按照一定的方式组织和存储的方法。它是计算机科学中的重要概念,用来描述和解决实际问题中的数据组织和处理问题。数据结构可以分为线性结构和非线性结构。线性结构包括数组、链表、堆栈和队列等,而非线性结构包括树和图等。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

259

2023.08.09

c语言random函数用法
c语言random函数用法

c语言random函数用法:1、random.random,随机生成(0,1)之间的浮点数;2、random.randint,随机生成在范围之内的整数,两个参数分别表示上限和下限;3、random.randrange,在指定范围内,按指定基数递增的集合中获得一个随机数;4、random.choice,从序列中随机抽选一个数;5、random.shuffle,随机排序。

603

2023.09.05

c语言const用法
c语言const用法

const是关键字,可以用于声明常量、函数参数中的const修饰符、const修饰函数返回值、const修饰指针。详细介绍:1、声明常量,const关键字可用于声明常量,常量的值在程序运行期间不可修改,常量可以是基本数据类型,如整数、浮点数、字符等,也可是自定义的数据类型;2、函数参数中的const修饰符,const关键字可用于函数的参数中,表示该参数在函数内部不可修改等等。

530

2023.09.20

c语言get函数的用法
c语言get函数的用法

get函数是一个用于从输入流中获取字符的函数。可以从键盘、文件或其他输入设备中读取字符,并将其存储在指定的变量中。本文介绍了get函数的用法以及一些相关的注意事项。希望这篇文章能够帮助你更好地理解和使用get函数 。

645

2023.09.20

c数组初始化的方法
c数组初始化的方法

c语言数组初始化的方法有直接赋值法、不完全初始化法、省略数组长度法和二维数组初始化法。详细介绍:1、直接赋值法,这种方法可以直接将数组的值进行初始化;2、不完全初始化法,。这种方法可以在一定程度上节省内存空间;3、省略数组长度法,这种方法可以让编译器自动计算数组的长度;4、二维数组初始化法等等。

603

2023.09.22

Python 自然语言处理(NLP)基础与实战
Python 自然语言处理(NLP)基础与实战

本专题系统讲解 Python 在自然语言处理(NLP)领域的基础方法与实战应用,涵盖文本预处理(分词、去停用词)、词性标注、命名实体识别、关键词提取、情感分析,以及常用 NLP 库(NLTK、spaCy)的核心用法。通过真实文本案例,帮助学习者掌握 使用 Python 进行文本分析与语言数据处理的完整流程,适用于内容分析、舆情监测与智能文本应用场景。

10

2026.01.27

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 22.3万人学习

Node.js 教程
Node.js 教程

共57课时 | 9.5万人学习

CSS3 教程
CSS3 教程

共18课时 | 4.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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