讲师中心 微信公众号

工具推荐

视频效率加速
updream

一键生成高质量推广成片

Seko
Seko

AI短剧一键生成成片

Laper
Laper

剧本策划到成片协作

AniShort
AniShort

AI短剧协同制作平台

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

大墨姑娘_5255

大墨姑娘_5255

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

|

355人浏览过

|

来源于php中文网

原创

前言

本文旨在通过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编程网络通信

Linux Omniscient
Linux Omniscient

>全知全能技能Linux版。

下载
#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编程网络通信

热门AI工具

更多
音述AI
音述AI Hot

一款AI音频处理工具,主要用于音述AI是一个以“用声音述说故事”为核心的 AI 音乐创作与声音分享社区,适合需要提升相关任务效率的用户。

码上飞
码上飞 Hot

一款基于自然语言的AI应用开发平台,可根据用户描述自动生成应用程序,帮助个人和团队快速完成应用原型与开发。

创客贴AI

创客贴AI是一款AI图像与设计工具,AI辅助的智能在线设计工具。

讯飞绘文

讯飞绘文是一款由科大讯飞推出的一站式 AIGC 内容运营平台。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Lovart
Lovart Hot

一款面向视觉设计创作的AI设计平台,可通过智能体和画布工作流辅助制作海报、Logo、网页、PPT及其他视觉内容。

VibeKnow
VibeKnow Hot

一款AI视频创作工具,主要用于全球首个AI知识视频创作平台,文档、文章、网页,一键生成视频,适合需要提升相关任务效率的用户。

相关专题

更多
java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

375

2025.11.27

java多态详细介绍
java多态详细介绍

本专题整合了java多态相关内容,阅读专题下面的文章了解更多详细内容。

375

2025.11.27

硬盘接口类型介绍
硬盘接口类型介绍

硬盘接口类型有IDE、SATA、SCSI、Fibre Channel、USB、eSATA、mSATA、PCIe等等。详细介绍:1、IDE接口是一种并行接口,主要用于连接硬盘和光驱等设备,它主要有两种类型:ATA和ATAPI,IDE接口已经逐渐被SATA接口;2、SATA接口是一种串行接口,相较于IDE接口,它具有更高的传输速度、更低的功耗和更小的体积;3、SCSI接口等等。

3248

2023.10.19

PHP接口编写教程
PHP接口编写教程

本专题整合了PHP接口编写教程,阅读专题下面的文章了解更多详细内容。

5469

2025.10.17

php8.4实现接口限流的教程
php8.4实现接口限流的教程

PHP8.4本身不内置限流功能,需借助Redis(令牌桶)或Swoole(漏桶)实现;文件锁因I/O瓶颈、无跨机共享、秒级精度等缺陷不适用高并发场景。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

3909

2025.12.29

java接口相关教程
java接口相关教程

本专题整合了java接口相关内容,阅读专题下面的文章了解更多详细内容。

506

2026.01.19

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

4838

2023.08.10

Python 多线程与异步编程实战
Python 多线程与异步编程实战

本专题系统讲解 Python 多线程与异步编程的核心概念与实战技巧,包括 threading 模块基础、线程同步机制、GIL 原理、asyncio 异步任务管理、协程与事件循环、任务调度与异常处理。通过实战示例,帮助学习者掌握 如何构建高性能、多任务并发的 Python 应用。

1349

2025.12.24

Go-Zero API开发使用教程
Go-Zero API开发使用教程

本专题聚焦Go-Zero的API开发,拆解.api描述文件的语法结构、路由与分组定义、参数绑定与校验规则,说明统一响应体、JWT鉴权、跨域与接口文档生成的落地方式,并给出网关聚合与接口测试的实践建议。

20

2026.11.12

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
DBeaver生态工具dbvr官方手册
DBeaver生态工具dbvr官方手册

共0课时 | 0人学习

DBeaver SQL编辑器使用手册
DBeaver SQL编辑器使用手册

共0课时 | 0人学习

DBeaver数据库连接指南
DBeaver数据库连接指南

共0课时 | 0人学习

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

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn