Linux网络编程--Udp套接字+实战 (万字详解,超详细!!)

news2025/2/12 8:30:33

目录

套接字协议:

        协议(protocol):

创建套接字(Create Socket):

绑定服务器地址

开始通信

Udp服务器设计--V1

Udp服务器设计--V2 引入进程池 待更新


套接字协议:

        协议(protocol):

        如果2个距离很远的人想要进行交流,首先要决定的就是"我们该用什么工具来进行通话?" :写信?手机?还是电脑?首先明确一点:能成功通信的双方一定是达成了某种共识的!如:2个人都是用网络进行通信,或者都是用书信交流,不可能存在一方使用书信,另一方使用手机这种“荒谬”的事情发生;换句话说,协议就是为了完成数据交换而定好的约定!

创建套接字(Create Socket):

        在Linux中一切皆文件,使用系统调用socket函数创建返回一个套接字文件描述符(如果成功)

创建失败返回-1,错误码被设置。

函数原型:

使用socket函数需要包含对应头文件;

参数详解:

  •   domian:套接字中使用的协议族,什么是协议族?说人话就是:番茄炒蛋和番茄炒米(参考卢老爷名言)都是属于用番茄做的菜一类,在套接字中,番茄就是协议族,番茄炒蛋就是协议族的一种类型,在套接字中,协议族常用的就2种:AF_INET和AF_INET6,分别对应ipv4和ipv6
  • type:套接字遵守的协议:udp还是tcp,设置为SOCK_DGRAM对应遵守UDP协议,设置为SOCK_STREAM对应遵守TCP协议
  • protocol:设置套接字的类型,非阻塞还是阻塞,是否能被子进程继承,设置为SOCK_NONBLOCK表示设置描述符非阻塞,设置SOCK_CLOEXEC表示不能被子进程继承

绑定服务器地址

        套接字创建成功后,需要绑定自己的地址信息,使用系统调用的bind函数

函数原型:

参数详解:

  • sockfd:之前使用的创建套接字返回的描述符fd
  • addr: 需要手动填充的服务器的地址信息,sockaddr本质上c语言的“拟基类”,我们要填充的其实不是sockaddr,而是它的"子类":struct sockaddr_in,结构体原型:sin_addr中还有一个成员:s_addr,用于填充要绑定的ip,注意!在云服务器上只能绑定所有地址,即"0.0.0.0"; sin_family:地址族;sin_port:端口号,注意要从本地序列(uint16_t)转换成网络序列,可以使用函数::htons进行转换;sin_zero一般都填充为0即可,所以可以在刚创建出sockaddr_in时使用memset将结构体清零
  • addrlen:第二个参数的大小,使用sizeof计算即可

开始通信

UDP套接字较为简单,不需要其他操作,只要bind成功就可以直接进行通信

使用recvfron接收信息,sendto发送信息

Udp服务器设计--V1

        目标:实现一个简单的Echo服务器,客户端发送什么info就返回什么样的info

先引入日志Log.hpp:

/*Log.hpp*/

#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <fstream>
#include <mutex>
#include <pthread.h>
#define _SCREEN_TYPE_ 1
#define _FILE_TYPE 2
namespace ns_log
{
    const std::string DEFAULTSTR = "Log.txt";

    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    struct Logmessage
    {
        std::string _level; // 等级
        pid_t _pid;
        std::string _filename;   // 文件名
        int _filenumber;         // 行号
        std::string _curr_time;  // 获取日志信息出现的时间
        std::string _logmessage; // 信息
    };

    std::string LevelToString(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
            break;
        case INFO:
            return "INFO";
            break;
        case WARNING:
            return "WARNING";
            break;
        case ERROR:
            return "ERROR";
            break;
        case FATAL:
            return "FATAL";
            break;
        default:
            return "UNKNOW";
            break;
        }
    }
    std::string GetCurrTime()
    {
        time_t now_time = time(nullptr);
        struct tm *curr_time = localtime(&now_time);
        // 转换为string格式
        char buf[128];
        snprintf(buf, sizeof(buf), "%d-%02d-%02d %02d:%02d:%02d",
                 curr_time->tm_year + 1900, curr_time->tm_mon + 1, curr_time->tm_mday, curr_time->tm_hour, curr_time->tm_min, curr_time->tm_sec);
        return buf;
    }
    class Log
    {
        void LogMsgToScreen(const Logmessage &logmsg)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                   logmsg._level.c_str(), logmsg._pid, logmsg._filename.c_str(), logmsg._filenumber, logmsg._curr_time.c_str(), logmsg._logmessage.c_str());
        }
        void LogMsgToFile(const Logmessage &logmsg)
        {
            char buf_info[2048] = "\0";
            snprintf(buf_info, sizeof(buf_info), "[%s][%d][%s][%d][%s] %s",
                     logmsg._level.c_str(), logmsg._pid, logmsg._filename.c_str(), logmsg._filenumber, logmsg._curr_time.c_str(), logmsg._logmessage.c_str());
            /*--系统调用版本--*/
            // int fd = open(_logfile.c_str(),O_CREAT | O_WRONLY | O_APPEND,0666);
            // if(fd < 0)
            // {
            //     perror("open");
            //     return;
            // }
            // write(fd,buf_info,sizeof(buf_info));

            /*--c++提供的fstream--*/
            std::ofstream out;
            out.open(_logfile, std::ios::out | std::ios::app | std::ios::binary);
            if (!out.is_open())
                return;
            out.write(buf_info, sizeof(buf_info));
            out.close();
        }
        void FlushLogMsg(int level, const Logmessage &logmsg)
        {
            // c++11的锁
            //  _mutex.lock();
            // RAII类型的锁
            std::unique_lock<std::mutex> lock(_mtx);
            if (_isopen && level == DEBUG)
                return;
            switch (_type)
            {
            case _SCREEN_TYPE_:
                LogMsgToScreen(logmsg);
                break;
            case _FILE_TYPE:
                LogMsgToFile(logmsg);
                break;
            }
        }

    public:
        Log(const std::string &logfile = DEFAULTSTR) : _logfile(logfile), _type(_SCREEN_TYPE_)
        {
        }
        void ModPrintFormat(int type)
        {
            std::unique_lock<std::mutex> lock(_mtx);
            _type = type;
        }
        void LogMessage(int level, std::string filename, int filenumber, const char *format, ...) // 注意-->可变函数参数
        {
            Logmessage msg;
            msg._level = LevelToString(level);
            msg._filename = filename;
            msg._pid = getpid();
            msg._filenumber = filenumber;
            msg._curr_time = GetCurrTime();
            // 注意:取出可变参数的固定写法
            va_list _ap;           // 创建变量,本质是一个指针
            va_start(_ap, format); // 将参数列表中离...最近的确定的参数传入
            char log_info[512];
            vsnprintf(log_info, sizeof(log_info), format, _ap);
            va_end(_ap); // 销毁_ap
            msg._logmessage = log_info;
            FlushLogMsg(level, msg);
        }
        ~Log()
        {
        }
        void EnableFiltration(bool flag)
        {
            _isopen = flag;
        }

    private:
        int _type;
        std::string _logfile;
        std::mutex _mtx;
        bool _isopen = false;
    };

    Log lg;
// 打开过滤器
#define EnabelFILTRATION()         \
    do                             \
    {                              \
        lg.EnableFiltration(true); \
    } while (0)
// 关闭过滤器
#define ClOSEFILTRATION             \
    do                              \
    {                               \
        lg.EnableFiltration(false); \
    } while (0)
#define LOG(level, format, ...)                                          \
    do                                                                   \
    {                                                                    \
        lg.LogMessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__); \
    } while (0)

#define LOGTOSCREEN(level, format, ...)                                  \
    do                                                                   \
    {                                                                    \
        lg.ModPrintFormat(_SCREEN_TYPE_);                                \
        lg.LogMessage(level, __FILE__, __LINE__, format, ##__VA_ARGS__); \
    } while (0)

#define LOGTOFILE(level)               \
    do                                 \
    {                                  \
        lg.ModPrintFormat(_FILE_TYPE); \
    } while (0)

}

  引入日志后,正式开始编写V1版本echo服务器:

 V1--Udp_Server:

根据上文的udp套接字接口介绍,下面就直接上代码:

#pragma once
#include <iostream>
#include <string>
#include <cstdlib>
#include <cstring>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Log.hpp"
using namespace ns_log;
#define MAX_BUFFER_SIZE 4096
// Udp--无连接|不可靠|高性能|广播
// 云服务器bind--->0.0.0.0
/*这是一个echo服务器*/
class UdpServer
{
private:
    int _sockfd;
    uint16_t _port;
    bool _isrunning;
public:
    UdpServer(uint16_t port) : _port(port), _isrunning(false)
    {
        _sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
        if (_sockfd < 0)
        {
            LOG(FATAL, "create socketfd failed!\n");
            abort();
        }
        LOG(DEBUG, "---create sockfd succsee,sockfd = %d---\n", _sockfd);
        // bind
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_port = ::htons(_port); // 本地序列转网络序列
        // bind任意ip
        addr.sin_addr.s_addr = INADDR_ANY;//INADDR_ANY--表示bind任意地址"0.0.0.0"
        int res = ::bind(_sockfd, (struct sockaddr *)&addr, sizeof(addr));
        if (res < 0)
        {
            LOG(FATAL, "server bind error!\n");
            abort();
        }
        LOG(DEBUG, "server bind success!\n");
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            char buff[MAX_BUFFER_SIZE];
            // 接收信息
            struct sockaddr_in peer;
      
            memset(&peer, 0, sizeof(peer));
            socklen_t len = sizeof(peer);
            ssize_t n = ::recvfrom(_sockfd, buff, sizeof(buff) - 1, 0, (struct sockaddr *)&peer, &len);
            if (n > 0)
            {
                buff[n] = 0;
                LOG(INFO, "client info#: %s\n", buff);
                // 发送
                std::string info = "[server][info]# ";
                info += buff;
                ssize_t n = ::sendto(_sockfd, info.c_str(), info.size(), 0,(struct sockaddr *)&peer, len);
            }
            else 
            {
                if(n == 0) continue;
                if(n < 0)
                {
                    LOG(ERROR,"recv error!\n");
                    break;
                }
            }
        }
    }
    void Close()
    {
        if(_sockfd > 0) ::close(_sockfd);
    }
    void Stop()
    {
        _isrunning = false;
    }
    ~UdpServer()
    {
    }
};

编写客户端代码:client.cc

对于客户端来说,需要bind要连接的服务器的ip地址和端口号,不能bind任意地址了

实现:

#include "Udp_Server.hpp"

bool Usage(int argc, char *argv[])
{
    if (argc != 3)
    {
        LOG(ERROR, "Usage:<./exe> <ip> <port>\n");
        return false;
    }
    return true;
}
int main(int argc, char *argv[])
{
    if (!Usage(argc, argv))
        return -1;
    std::string ip = argv[1];
    uint16_t port = atoi(argv[2]);
    int sockfd = ::socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        LOG(FATAL, "Create socket error!\n");
        return -2;
    }
    struct sockaddr_in server;
    // 将结构体清零
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    ::inet_pton(AF_INET, ip.c_str(), &server.sin_addr.s_addr);
    server.sin_port = ::htons(port);
    
    while(true)
    {
        std::string buffer;
        std::getline(std::cin,buffer);
        ssize_t n = ::sendto(sockfd,buffer.c_str(),buffer.size(),0,(struct sockaddr*)&server,sizeof(server));
        //接收信息
        char recvbuff[1024];
        struct sockaddr_in client;
        memset(&client,0,sizeof(client));
        socklen_t len = sizeof(client);
        n = ::recvfrom(sockfd,recvbuff,sizeof(recvbuff)-1,0,(struct sockaddr*)&client,&len);
        if(n > 0)
        {
            recvbuff[n] = 0;
            LOG(INFO,"%s\n",recvbuff);
            continue;
        }
        else
        {
            if(n == 0) continue;
            if(n < 0)
            {
                LOG(ERROR,"client recv error!\n");
                break;
            }
        }
    }
    return 0;
}

编写serevr.cc

#include"Udp_Server.hpp"
#include<memory>
bool Usage(int argc, char *argv[])
{
    if (argc != 2)
    {
        LOG(ERROR, "Usage:<./exe> <port>\n");
        return false;
    }
    return true;
}
int main(int argc, char *argv[])
{
    if (!Usage(argc, argv))
        return -1;
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<UdpServer> server = std::make_unique<UdpServer>(port);
    server->Start();
    server->Stop();
    server->Close();
}

编写Makefile:

.PHONY:ALL
ALL:server client
server:server.cc
	g++ -o $@ $^ -std=c++14 
client:client.cc
	g++ -o $@ $^ -std=c++14	

.PHONY:clean
clean:
	rm -f server client

make编译即可;

Udp服务器设计--V2 引入进程池 待更新

-----------------------------------------------------分割线-----------------------------------------------------2025.2.10

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2296741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

玩转工厂模式

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 什么是工厂模式?工厂方法模式适合应用场景实现方式工厂方法模式优缺点什么是工厂模式? 工厂方法模式是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。…

开箱即用:一个易用的开源表单工具!

随着互联网的普及&#xff0c;表单应用场景越来越广泛&#xff0c;从网站注册、调查问卷到考试测评&#xff0c;无处不在。传统的表单制作方式需要一定的代码基础&#xff0c;对于不懂编程的小伙伴来说&#xff0c;无疑是一道门槛。 今天&#xff0c;给大家分享一款开源的表单…

基于微信小程序的博物馆预约系统的设计与实现

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…

fullcalendar全局日历深度定制(自适应、月、周视图)

首先看效果&#xff1a; 把日程通过蓝色标记点标记出来&#xff0c;可展开收起日历。展开为月视图&#xff0c;收起为月-周视图&#xff0c;且把日程展示在日历下面。 涉及功能点有&#xff1a; 日历头部自定义头部星期格式修改主体样式修改日程自定义展开收起展示不同视图(月…

关于浏览器缓存的思考

问题情境 开发中要实现一个非原生pdf预览功能&#xff0c;pdf链接放在一个固定的后台地址&#xff0c;当重新上传pdf后&#xff0c;预览pdf仍然是上一次的pdf内容&#xff0c;没有更新为最新的内容。 查看接口返回状态码为 200 OK(from disk cache)&#xff0c; 表示此次pdf返回…

16vue3实战-----动态路由

16vue3实战-----动态路由 1.思路2.实现2.1创建所有的vue组件2.2创建所有的路由对象文件(与上述中的vue文件一一对应)2.3动态加载所有的路由对象文件2.4根据菜单动态映射正确的路由2.5解决main页面刷新的问题2.6解决main的第一个页面匹配显示的问题2.7根据path匹配menu 1.思路 …

Linux常见命令——系统定时任务

文章目录 crontab 服务管理crontab -e :编辑crontab 定时任务crontab -l 查看crontab 任务crontab -r 删除当前用户所有的crontab 任务 crontab 服务管理 systemctl status crond该系统进程是开机自启动&#xff0c;并且被打开了&#xff0c;可以使用。 crontab -e :编辑cr…

ARM Cortex-M3/M4 权威指南 笔记【一】技术综述

一、Cortex-M3/M4 处理器的一般信息 1.1 处理器类型 ARM Cortex-M 为 32 位 RISC&#xff08;精简指令集&#xff09;处理器&#xff0c;其具有&#xff1a; 32位寄存器32位内部数据通路32位总线接口 除了 32 位数据&#xff0c;Cortex-M 处理器&#xff08;以及其他任何 A…

常用的AI算法介绍

常用的AI算法介绍 自然语言生成&#xff08;NLG&#xff09;&#xff1a;让机器写作&#xff0c;写诗 语言识别&#xff1a;语音模型的识别 虚拟现实&#xff1a;VR、增强现实&#xff08;AR&#xff09; 机器学习平台&#xff1a;针对AI优化的硬件和芯片&#xff08;人脸识…

android的ViewModel这个类就是业务逻辑层吗

android的ViewModel这个类就是业务逻辑层吗&#xff1f; 相似&#xff1a;业务逻辑代码应该放在ViewModel这个类吗&#xff1f; 嗯&#xff0c;我现在在学习Android架构组件&#xff0c;特别是ViewModel。用户问ViewModel是否就是业务逻辑层&#xff0c;我需要仔细思考这个问题…

Java在大数据处理中的应用:从MapReduce到Spark

Java在大数据处理中的应用&#xff1a;从MapReduce到Spark 大数据时代的到来让数据的存储、处理和分析变得前所未有的重要。随着数据量的剧增&#xff0c;传统的单机计算方式已经无法满足处理需求。为了解决这个问题&#xff0c;许多分布式计算框架应运而生&#xff0c;其中Ma…

深入理解QT的View-Model-Delegate机制和用法

文章目录 Model-View-Delegate机制Model(数据模型)设置模型属性访问元素操作元素数据排序封装好的模型View(视图)显示数据数据选择Delegate(代理)数据选择易用封装类QListWidgetQTreeWidgetQTableWidget元素拖拽代理模型参考示例Model-View-Delegate机制 Qt的View/Model/Deleg…

深入理解指针初阶:从概念到实践

一、引言 在 C 语言的学习旅程中&#xff0c;指针无疑是一座必须翻越的高峰。它强大而灵活&#xff0c;掌握指针&#xff0c;能让我们更高效地操作内存&#xff0c;编写出更优化的代码。但指针也常常让初学者望而生畏&#xff0c;觉得它复杂难懂。别担心&#xff0c;本文将用通…

重庆西站公路桥梁自动化监测

1.项目概述 重庆西站属于渝黔铁路的配套工程&#xff0c;是承担兰渝、川黔、渝昆等多条铁路的特级客运站&#xff0c;未来重庆铁路三大客运站之一。作为我国西部地区规模最大的火车站、重庆西站于2014年在沙坪坝区上桥开工建设,该站东临内环高速&#xff0c;西靠中梁山&#x…

头条百度批量采集软件说明文档

旧版说明文档《头条号文章批量采集软件4.0版本说明文档&#xff01;头条/微头条文章批量采集》 头条的采集软件已经更新了好多个版本了&#xff0c;一直没有做详细的介绍文档&#xff0c;最近更新了一些功能进去&#xff0c;一块来写一下说明文档。 1、主界面 2、头条作者采集…

【面试】面试常见的智力题

引言 在技术面试中&#xff0c;除了考察编程能力和算法知识外&#xff0c;智力题也是常见的考察方式。智力题不仅能够测试候选人的逻辑思维能力&#xff0c;还能反映其解决问题的创造力和应变能力。本文将整理一些常见的面试智力题&#xff0c;并详细分析解题思路&#xff0c;…

【动态规划】风扫枯杨,满地堆黄叶 - 9. 完全背包问题

本篇博客给大家带来的是完全背包问题之动态规划解法技巧. &#x1f40e;文章专栏: 动态规划 &#x1f680;若有问题 评论区见 ❤ 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条. 你们的支持是我不断创作的动力 . 王子,公主请阅&#x1f680; 要开心要快乐顺…

BGP基础协议详解

BGP基础协议详解 一、BGP在企业中的应用二、BGP概述2.1 BGP的特点2.2 基本配置演示2.3 抓包观察2.4 BGP的特征三、BGP对等体关系四、bgp报文4.1 BGP五种报文类型(重点)4.2 BGP报文格式-报文头格式4.3 Open报文格式4.4 Update报文格式4.5 Notification报文格式4.6 Route-refre…

LeetCode刷题---数组---840

矩阵中的幻方 https://leetcode.cn/problems/magic-squares-in-grid/submissions/598584907/ 题目&#xff1a; 3 x 3 的幻方是一个填充有 从 1 到 9 的不同数字的 3 x 3 矩阵&#xff0c;其中每行&#xff0c;每列以及两条对角线上的各数之和都相等。 给定一个由整数组成…

Visual Studio踩过的坑

统计Unity项目代码行数 编辑-查找和替换-在文件中查找 查找内容输入 b*[^:b#/].*$ 勾选“使用正则表达式” 文件类型留空 也有网友做了指定&#xff0c;供参考 !*\bin\*;!*\obj\*;!*\.*\*!*.meta;!*.prefab;!*.unity 打开Unity的项目 注意&#xff1a;只是看&#xff0…