当前位置: 首页 > news >正文

【Linux】socket网络编程之UDP

在这里插入图片描述

个人主页~


socket网络编程UDP

  • 一、必备接口
    • 1、接收数据
    • 2、发送数据
  • 二、实现UDP网络编程
    • 1、服务器
      • (一)UdpServer.hpp
      • (二)main.cpp
    • 2、客户端
      • UdpClient.cpp

一、必备接口

1、接收数据

recvfrom函数用于从套接字接收数据

#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

返回值:返回正数表示成功接收到的数据字节数,返回0表示对方已经关闭了连接(对于TCP),返回-1表示发生错误
sockfd:指定从哪个套接字接收数据
buf:指向一个缓冲区,用于存储接收到的数据
len:指定buf的最大长度
flags:设置接收数据的标志,无标志一般设为0,在这里可以设置非阻塞模式
src_addr:用于存储发送方的地址信息,输出型参数
addrlen:用于指定src_addr结构体的长度,输入输出型参数,在调用 recvfrom 函数之前,需要将其初始化为 src_addr 结构体的最大长度,函数返回时,addrlen 会被更新为实际存储在 src_addr 中的地址信息的长度

2、发送数据

sendto函数用于在无连接的套接字(如 UDP 套接字)上发送数据

#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

返回值:返回正数表示成功发送的数据字节数,返回-1表示发送过程中出现错误
sockfd:指定通过哪个套接字发送数据
buf:指向要发送的数据的缓冲区
len:表示要发送的数据的长度(以字节为单位),即 buf 缓冲区中实际要发送的数据的字节数
flags:设置发送数据的标志,无标志一般设为0,在这里可以设置非阻塞模式
dest_addr:该结构体包含了目标地址的信息
addrlen:表示dest_addr结构体的长度(以字节为单位)

二、实现UDP网络编程

1、服务器

(一)UdpServer.hpp

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unistd.h>// 下面两行都是代表着定义一个类似函数指针的变量func_t,参数和返回值都是string
// using func_t = std::function<std::string(const std::string&)>;
typedef std::function<std::string(const std::string&)> func_t;
//枚举错误类型
enum{SOCKET_ERR=1,BIND_ERR
};
//定义端口号和ip地址
uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
//size用来定义缓冲区大小
const int size = 1024;class UdpServer{
public:UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false){}void Init(){// 创建udp socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0);if(sockfd_ < 0){exit(SOCKET_ERR);}// 定义一个local存储本地地址struct sockaddr_in local;//bzero的作用就是将local结构体清零bzero(&local, sizeof(local));//将local的前两个字节也就是它的地址族为AF_INETlocal.sin_family = AF_INET;//保证我们的端口号是网络字节序列,因为该端口号是要给对方发送的local.sin_port = htons(port_); //inet_addr函数将ip_转换为in_addr_t类型的IP地址,并存储在local.sin_addr.s_addrlocal.sin_addr.s_addr = inet_addr(ip_.c_str()); //绑定sockfd_到本地地址local上,失败则退出if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0){exit(BIND_ERR);}}void Run(func_t func) // 对代码进行分层{//设置服务器开始运行标志isrunning_ = true;//定义缓冲区存放接收到的数据char inbuffer[size];while(isrunning_){//定义client结构体存储客户端的地址信息struct sockaddr_in client;socklen_t len = sizeof(client);//获取客户端信息ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if(n < 0){continue;}//在最后添加字符串描述符'\0'inbuffer[n] = 0;//将所有的信息通过func函数处理为echo_stringstd::string info = inbuffer;std::string echo_string = func(info);//将处理完后的数据发送到客户端sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);}}//析构,sockfd_本质上是一个文件描述符一样的东西,用close可以关闭~UdpServer(){if(sockfd_>0) close(sockfd_);}
private:int sockfd_;     // 网路文件描述符std::string ip_; // 任意地址bind 0uint16_t port_;  // 表明服务器进程的端口号bool isrunning_; // 服务器运行状态
};

(二)main.cpp

#include "UdpServer.hpp"
#include <memory>
#include <cstdio>// 端口号一般是可以随便定义的,但是[0,1023]范围内的端口号一般都是有固定的应用层协议的
//所以我们都用1024+的端口号//输入小于1024的端口号就提示重输
void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}
//该函数用于处理接收到的消息,将消息添加到固定的前缀 "Server get a message: " 后面
//并将结果输出到控制台
std::string Handler(const std::string &str)
{std::string res = "Server get a message: ";res += str;std::cout << res << std::endl;return res;
}
//该函数用于执行客户端发送的命令,并将命令执行的结果返回给 UDP 服务器
//从而实现服务器与客户端之间的交互,让客户端可以在服务器端执行命令并获取结果
std::string ExcuteCommand(const std::string &cmd)
{FILE *fp = popen(cmd.c_str(), "r");if(nullptr == fp){perror("popen");return "error";}std::string result;char buffer[4096];while(true){char *ok = fgets(buffer, sizeof(buffer), fp);if(ok == nullptr) break;result += buffer;}pclose(fp);return result;
}// ./udpserver port
int main(int argc, char *argv[])
{//在命令行中只带一个参,多带参和少带参都要执行Usageif(argc != 2){Usage(argv[0]);exit(0);}uint16_t port = std::stoi(argv[1]);//智能指针管理 UdpServer 对象的生命周期,确保对象在不再使用时自动释放内存std::unique_ptr<UdpServer> svr(new UdpServer(port));//初始化并启动服务器svr->Init();svr->Run(ExcuteCommand);return 0;
}

2、客户端

UdpClient.cpp

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>using namespace std;void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])
{//命令行参数不为三个就打印提示信息if (argc != 3){Usage(argv[0]);exit(0);}//获取服务器端口号和服务器ipstd::string serverip = argv[1];uint16_t serverport = std::stoi(argv[2]);//下面似曾相识呢,和上面服务器的解释一致struct sockaddr_in server;bzero(&server, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport); server.sin_addr.s_addr = inet_addr(serverip.c_str());socklen_t len = sizeof(server);int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){cout << "socker error" << endl;return 1;}//客户端套接字实际上也需要绑定,但通常不需要用户显式地进行绑定//操作系统会在客户端首次发送数据时自动为其分配一个可用的端口号进行绑定string message;char buffer[1024];while (true){cout << "Please Enter@ ";getline(cin, message);//将消息发送到服务器sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);//接收信息并打印struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);if(s > 0){buffer[s] = 0;cout << buffer << endl;}}close(sockfd);return 0;
}

最终我们实现的就是一个客户端可以通过指令操控服务器,然后将服务器应该输出的结果打印到客户端

在这里插入图片描述


今日分享就到这里了~

在这里插入图片描述

相关文章:

  • 主场景 工具栏 植物卡牌的渲染
  • 使用adb设置wifi相关
  • 《100天精通Python——基础篇 2025 第16天:异常处理与调试机制详解》
  • SpringCloud服务拆分:Nacos服务注册中心 + LoadBalancer服务负载均衡使用
  • LeetCode 热题 100 131. 分割回文串
  • 【QT】: 初识 QWidget 控件 | QWidget 核心属性(API) | qrc 文件
  • 湖北理元理律师事务所:债务优化中的“生活保障”方法论
  • 软件逆向工程核心技术:脱壳原理与实战分析
  • 前端开发中移动端调试的日常工具整理
  • 《React Native性能优化:从卡顿到丝滑的蜕变之旅》
  • 信创生态核心技术栈:数据库与中间件
  • Vue 3.0中Treeshaking特性
  • 迪士尼机器人BD-X 概况
  • # 如何使用 PyQt5 创建一个简单的警报器控制界面
  • Chroma:一个开源的8.9B文生图模型
  • 【LunarVim】CMake LSP配置
  • 人协同的自动化需求分析
  • 【SQLSERVER】Ubuntu 连接远程 SQL Server(MSSQL)
  • 搭建和优化CI/CD流水线
  • [人机交互]设计,原型建立和构造
  • 可量产9MWh超大容量储能系统亮相慕尼黑,宁德时代:大储技术迈入新时代
  • 临港新片区:发布再保险、国际航运、生物医药3个领域数据出境操作指引
  • 追光|铁皮房、土操场,这有一座“筑梦”摔跤馆
  • 如此城市|上海老邬:《爱情神话》就是我生活的一部分
  • 苹果Safari浏览器上的搜索量首次下降
  • 鸿蒙电脑正式亮相,五年布局积累超2700项核心专利