Linux网络编程--Udp套接字+实战 (万字详解,超详细!!)
目录
套接字协议:
协议(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); // 销毁_apmsg._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);// bindstruct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = ::htons(_port); // 本地序列转网络序列// bind任意ipaddr.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.ccg++ -o $@ $^ -std=c++14
client:client.ccg++ -o $@ $^ -std=c++14 .PHONY:clean
clean:rm -f server client
make编译即可;
Udp服务器设计--V2 引入进程池 待更新
-----------------------------------------------------分割线-----------------------------------------------------2025.2.10
相关文章:
Linux网络编程--Udp套接字+实战 (万字详解,超详细!!)
目录 套接字协议: 协议(protocol): 创建套接字(Create Socket): 绑定服务器地址 开始通信 Udp服务器设计--V1 Udp服务器设计--V2 引入进程池 待更新 套接字协议: 协议(protocol): 如果2个距离很远的人想要进行交流ÿ…...
vscode无法ssh连接远程机器解决方案
远程服务器配置问题 原因:远程服务器的 SSH 服务配置可能禁止了 TCP 端口转发功能,或者 VS Code Server 在远程服务器上崩溃。 解决办法 检查 SSH 服务配置:登录到远程服务器,打开 /etc/ssh/sshd_config 文件,确保以下…...
玩转工厂模式
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 什么是工厂模式?工厂方法模式适合应用场景实现方式工厂方法模式优缺点什么是工厂模式? 工厂方法模式是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。…...
数据库视图的使用场景详细讲解
什么是数据库视图? 数据库视图是基于一个或多个数据库表的查询结果集,可以看作是一个虚拟表。视图本身不存储数据,数据仍然存储在基表中。视图通过查询动态生成数据,用户可以通过视图访问数据,而不必直接操作基表。 …...
开箱即用:一个易用的开源表单工具!
随着互联网的普及,表单应用场景越来越广泛,从网站注册、调查问卷到考试测评,无处不在。传统的表单制作方式需要一定的代码基础,对于不懂编程的小伙伴来说,无疑是一道门槛。 今天,给大家分享一款开源的表单…...
基于微信小程序的博物馆预约系统的设计与实现
hello hello~ ,这里是 code袁~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 🦁作者简介:一名喜欢分享和记录学习的在校大学生…...
Svelte前端框架
Svelte 简介 Svelte 是一个现代的前端框架,用于构建高效、响应式的用户界面。与 React、Vue 和 Angular 等传统框架不同,Svelte 在构建时将组件编译为高效的纯 JavaScript 代码,而不是在浏览器中运行一个庞大的运行时库。这使得 Svelte 应用具…...
力扣-栈与队列-347 前k个高频元素
思路 利用优先队列进行排序,然后利用multiset对count进行排序,最后收集k个高频元素就行 代码 class Solution { public:vector<int> topKFrequent(vector<int>& nums, int k) {priority_queue<int> pq;for(int i 0; i < nu…...
fullcalendar全局日历深度定制(自适应、月、周视图)
首先看效果: 把日程通过蓝色标记点标记出来,可展开收起日历。展开为月视图,收起为月-周视图,且把日程展示在日历下面。 涉及功能点有: 日历头部自定义头部星期格式修改主体样式修改日程自定义展开收起展示不同视图(月…...
TextWebSocketHandler 和 @ServerEndpoint 各自实现 WebSocket 服务器
TextWebSocketHandler 和 ServerEndpoint 都可以用于实现 WebSocket 服务器,但它们属于不同的技术栈,使用方式和功能有一些区别。以下是它们的对比: 1. 技术栈对比 特性TextWebSocketHandler (Spring)ServerEndpoint (Java EE/JSR-356)所属框…...
计算机毕业设计——Springboot的简历系统
📘 博主小档案: 花花,一名来自世界500强的资深程序猿,毕业于国内知名985高校。 🔧 技术专长: 花花在深度学习任务中展现出卓越的能力,包括但不限于java、python等技术。近年来,花花更…...
关于浏览器缓存的思考
问题情境 开发中要实现一个非原生pdf预览功能,pdf链接放在一个固定的后台地址,当重新上传pdf后,预览pdf仍然是上一次的pdf内容,没有更新为最新的内容。 查看接口返回状态码为 200 OK(from disk cache), 表示此次pdf返回…...
变量声明 vs 变量定义
变量声明(Declaration) vs 变量定义(Definition) 在 C 中,变量声明和变量定义是两个不同的概念。理解它们的区别对于链接错误的调试、多文件编程、外部变量的使用非常重要。 3. 变量声明 vs 变量定义 区别变量声明&…...
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该系统进程是开机自启动,并且被打开了,可以使用。 crontab -e :编辑cr…...
ARM Cortex-M3/M4 权威指南 笔记【一】技术综述
一、Cortex-M3/M4 处理器的一般信息 1.1 处理器类型 ARM Cortex-M 为 32 位 RISC(精简指令集)处理器,其具有: 32位寄存器32位内部数据通路32位总线接口 除了 32 位数据,Cortex-M 处理器(以及其他任何 A…...
常用的AI算法介绍
常用的AI算法介绍 自然语言生成(NLG):让机器写作,写诗 语言识别:语音模型的识别 虚拟现实:VR、增强现实(AR) 机器学习平台:针对AI优化的硬件和芯片(人脸识…...
Spring常用注解和组件
引言 了解Spring常用注解的使用方式可以帮助我们更快速理解这个框架和其中的深度 注解 Configuration:表示该类是一个配置类,用于定义 Spring Bean。 EnableAutoConfiguration:启用 Spring Boot 的自动配置功能,让 Spring Boo…...
android的ViewModel这个类就是业务逻辑层吗
android的ViewModel这个类就是业务逻辑层吗? 相似:业务逻辑代码应该放在ViewModel这个类吗? 嗯,我现在在学习Android架构组件,特别是ViewModel。用户问ViewModel是否就是业务逻辑层,我需要仔细思考这个问题…...
3、kubectl 命令详解
kubectl 命令详解 kubectl 简介kubectl 常用操作基本信息查看命名空间的增删改查操作查看 pod 信息 项目的生命周期创建发布更新回滚删除金丝雀发布(Canary Release) 声明式管理方法 kubectl 简介 kubernetes 集群管理集群资源的唯一入口是通过相应的方…...
Qt Pro、Pri、Prf
一、概述 1、在Qt中,通常使用.pro(project)、pri(private include)、prf(project file)三种文件扩展名来组织项目。对于模块化编程,Qt提供了Pro和Pri,Pro管理项目,Pri管理模块。 2、pro文件是Qt项目的核心文件,包含了…...
fps动作系统9:动画音频
文章目录 动画音频创建音频蓝图cue音量乘数 音效衰减衰减空间 绑定到动画动画序列轨道 动画音频 创建音频蓝图 cue 音量乘数 音量大小 音效衰减 空间音效 衰减 空间 绑定到动画 动画序列 轨道 横着的方向是有不同的轨道的,阴影的就是。...
1064 - You have an error in your SQL syntax;
在创建数据库表建立外键是遇到了如下报错 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near position(position_id) ) at line 8 数据库表sql如下: --职位表 CR…...
Java在大数据处理中的应用:从MapReduce到Spark
Java在大数据处理中的应用:从MapReduce到Spark 大数据时代的到来让数据的存储、处理和分析变得前所未有的重要。随着数据量的剧增,传统的单机计算方式已经无法满足处理需求。为了解决这个问题,许多分布式计算框架应运而生,其中Ma…...
SwiftUI 中 .overlay 两种写法的区别及拓展
SwiftUI 中 .overlay 两种写法的区别及拓展 一、.overlay 简介功能语法 二、写法 1:.overlay(Circle().stroke(Color.blue, lineWidth: 2))代码示例解释优点适用场景 三、写法 2:.overlay { Circle().stroke(.white, lineWidth: 4) }代码示例解释优点适用…...
深入理解QT的View-Model-Delegate机制和用法
文章目录 Model-View-Delegate机制Model(数据模型)设置模型属性访问元素操作元素数据排序封装好的模型View(视图)显示数据数据选择Delegate(代理)数据选择易用封装类QListWidgetQTreeWidgetQTableWidget元素拖拽代理模型参考示例Model-View-Delegate机制 Qt的View/Model/Deleg…...
C# ASP.NET 介绍
.NET学习资料 .NET学习资料 .NET学习资料 一、概述 ASP.NET是由微软创建的一个开源 Web 框架,用于使用.NET 构建现代化的 Web 应用程序和服务。它为开发者提供了一套丰富的工具、库和编程模型,使得创建功能强大、高效且安全的 Web 应用变得更加容易。…...
深入理解指针初阶:从概念到实践
一、引言 在 C 语言的学习旅程中,指针无疑是一座必须翻越的高峰。它强大而灵活,掌握指针,能让我们更高效地操作内存,编写出更优化的代码。但指针也常常让初学者望而生畏,觉得它复杂难懂。别担心,本文将用通…...
《手札·行业篇》开源Odoo MES系统与SKF Observer Phoenix API在化工行业的双向对接方案
一、项目背景 化工行业生产过程复杂,设备运行条件恶劣,对设备状态监测、生产数据采集和质量控制的要求极高。通过开源Odoo MES系统与SKF Observer Phoenix API的双向对接,可以实现设备状态的实时监测、生产数据的自动化采集以及质量数据的同步…...
oracle11g搭建主从集群
安装oracle11g参考:centos安装oracle11g数据库-CSDN博客 一、主库操作 sqlplus / as sysdba 1、开启归档模式和强制日志模式 shutdown immediate startup mount #开启归档模式和强制日志模式 alter database archivelog; alter database force logging; #开启补…...
