网络编程套接字 | UDP套接字

前面的文章中我们叙述了网络编程套接字的一些预备知识点,从本文开始我们就将开始UDP套接字的编写。本文中的服务端与客户端都是在阿里云的云服务器进行编写与测试的。

udp_v1

在v1的版本中我们先来使用一下前面讲过得一些接口,简单的构建一个udp服务器:

// udp_server.cc
#include "udp_server.hpp"
#include <memory>using namespace std;
using namespace ns_server;
int main()
{unique_ptr<UdpServer> usvr(new UdpServer("1.1.1.1", 8082)); // 通过智能指针控制服务器的资源管理,并且向程序传入ip与端口号usvr->InitServer(); //服务器的初始化usvr->Start();return 0;
}
// udp_server.hpp
#pragma once#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
namespace ns_server
{// 用于展示返回对应的错误提示enum{SOCKET_ERR = 1,BIND_ERR};const static uint16_t default_port = 8080;class UdpServer{public:UdpServer(std::string ip, uint16_t port = default_port) : ip_(ip), port_(port){std::cout << "server addr: " << ip << " : " << port_ << std::endl;}void InitServer(){// 1. 创建socket接口,打开网络文件sock_ = socket(AF_INET, SOCK_DGRAM, 0);if (sock_ < 0){std::cerr << "create socket error: " << strerror(errno) << std::endl;exit(SOCKET_ERR);}std::cout << "create socket success: " << sock_ << std::endl; // 3// 2. 给服务器指明IP地址和端口号Portstruct sockaddr_in local; // 这个 local 在哪里定义呢?用户空间的特定函数的栈帧上,不在内核中! bzero(&local, sizeof(local)); // 清空上述字段udlocal.sin_family = AF_INET; // PF_INET 初始化socketaddr_in结构local.sin_port = htons(port_); // 本地主机序列构建的port_,需要从主机序列转变成网络序列// inet_addr: 1,2// 1. 字符串风格的IP地址,转换成为4字节int, "1.1.1.1" -> uint32_t -> 能不能强制类型转换呢?不能,这里要转化// 2. 需要将主机序列转化成为网络序列local.sin_addr.s_addr = inet_addr(ip_.c_str()); // sin_addr C++中的结构体在C++中可以进行转化,但是在C语言中不行// 这里需要将字符串转换uint32_t的类型,并且同时进行将主机序列转换成网络序列// inet_addr 函数将包含 IPv4 点十进制地址的字符串转换为IN_ADDR结构的正确地址。而在in_addr结构之中有in_addr_t s_addr的一个数据结构if (bind(sock_, (struct sockaddr*)&local, sizeof(local)) < 0) // 然后是绑定相关的套接字文件,此时需要就将前面在帧栈上定义的local进行绑定{std::cerr << "bind socket error: " << strerror(errno) << std::endl;exit(BIND_ERR);}std::cout << "bind socket success: " << sock_ << std::endl; //3}void Start() {}~UdpServer() {}private:int sock_;uint16_t port_;std::string ip_; // 后面需要去掉这个ip};
}

然后运行上述的程序会出现一个问题就是:

server addr: 1.1.1.1 : 8082
create socket success: 3
bind socket error: Cannot assign requested address

bind socket error: Cannot assign requested address 云服务器不需要bind ip地址,需要让服务器自己制定ip地址
云服务器,或者一款服务器,一般不要指明某一个确定的IP – 服务器可能有多张网卡,可能配有多个IP,我们要让我们的udpserver启动的时候bind本主机上的任意IP,然后我们对上述的v1版本进行修改。

// 需要修改的地方就是:
local.sin_addr.s_addr = INADDR_ANY; // 让我们的udpserver在启动的时候,bind本主机上的任意IP

server addr: 1.1.1.1 : 8082
create socket success: 3
bind socket success: 3

此时就可以正确的进行bind操作。

udp_v2

下面我们将上述的程序进行完善,添加上服务器正常工作的程序(我们想要完成的是客户端发送消息,服务端接收到消息并答应在终端上,同时将消息返回给客户端)

void Start()
{// 服务器的正常工作char buffer[1024];while (true){// 收// ssize_t 实际写入的大小 recvfrom(int sockfd 绑定的套接字, void *buf 接受数据存放的缓冲区, size_t len 缓冲区长度, int flags 读取方式(0), struct sockaddr *src_addr 需要知道client的IP和PORT 输入接收缓冲区, socklen_t *addrlen 实际结构体的大小); 输入输出型参数 需要知道谁发的数据struct sockaddr_in peer;socklen_t len = sizeof(peer); // 这里一定要写清楚,未来传入的缓冲区的大小int n = recvfrom(sock_, buffer, sizeof(buffer)-1/*因为这是以字符作为消息的类型,所以缓冲区预留一部分空间*/, 0, (struct sockaddr*)&peer, &len); // 消息的类型需要程序员来定义if (n > 0) buffer[n] = '\0';else continue;// 提取client信息std::string clientip = inet_ntoa(peer.sin_addr); // 把一个四字节的IP转化为字符串uint16_t clientport = ntohs(peer.sin_port); // 将从网络中获取的端口号转换成主机std::cout << clientip << "-" << clientport << "#" << buffer << std::endl;// 发sendto(sock_, buffer, strlen(buffer), 0, (struct sockaddr*)&peer, len); // 往文件中去写的时候,不需要携带\0}
}

然后对udp_server.cc文件进行修改,我们想要使用./udp_server port的形式来运行程序,在运行程序的时候将端口号进行传入

static void usage(string proc)
{std::cout << "Usage:\n\t" << proc << " port\n" << std::endl;
}
// ./udp_server port
int main(int argc, char* argv[])
{if (argc != 2){usage(argv[0]);exit(USAGE_ERR);}uint16_t port = atoi(argv[1]); // 此处为char*类型的数据若是要传入port需要进行转换unique_ptr<UdpServer> usvr(new UdpServer(port));// bind socket error: Cannot assign requested address 云服务器不需要bind ip地址,需要让服务器自己制定ip地址// 自己本地装的虚拟机或者是物理机器是允许的,usvr->InitServer(); //服务器的初始化usvr->Start();return 0;
}

然后是服务端的代码

// udp_client.cc
#include <string>
#include <cstring>
// 127.0.0.1 本地回环,表示的就是当前的主机,通常用来进行本地通信或者测试
static void usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}
// ./udp_client serverip serverport
int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(USAGE_ERR);}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){std::cerr << "create socket error" << std::endl;exit(SOCKET_ERR);}// client 这里要不要bind? 要的!socket通信的本质[clientip:clientport, serverclient. serverport]// 要不要自己bind?不需要自己bind,os自动给我们进行bind!-- 为什么?client的port要随机让os分配防止client出现启动冲突// server为什么要自己bind -- 1. server的端口不能随意改变, 众所周知且不能随意改变;2. 同一家公司的port需要统一进行管理// 明确server是谁?struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());while (true){// 用户输入std::string message;std::cout << "please Enter# ";std::cin >> message;// 什么时候bind?在我们首次系统调用发送数据的时候,OS会在底层随机选择clientport + 自己的IP 1. bind 2. 构建发送的数据报文// 发送sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));// 接收char buffer[2048];struct sockaddr_in temp;socklen_t len = sizeof(temp);int n = recvfrom(sock, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);if (n > 0){buffer[n] = 0;std::cout << "server echo# " << buffer << std::endl;}}return 0;
}

在这里插入图片描述
这样我们就完成了一个简单的UDP网络通信程序。

udp_v3

下面我们还需要对上述的UDP网络通信进行修改,在上述的网络通信程序中服务端在接收到客户端的信息之后立即进行了处理,然后将信息进行返回,但是我们想要让信息的处理与网络IO进行分离,在第三版中我们就进行对应的修改。我们在工作空间中定义了using func_t = std::function<std::string (std::string)>;这样的参数为string,返回值为string的一个包装器,在udp_server类中新增了一个成员func_t service_;来进行业务处理。在编写构造函数的时候将这个成员初始化,那么我们就可以在外部传入一个执行业务的方法,在外部定义好了这个执行的方法,在外部即udp_server.cc中将业务处理完毕之后再进行网络IO。
下面就是对上述版本的修改

// udp_server.hpp
// 在Start()中发送消息前执行业务处理
void Start()
{// ...// 做业务处理std::string response = service_(buffer);// 发sendto(sock_, response.c_str(), response.size(), 0, (struct sockaddr*)&peer, len); // 往文件中去写的时候,不需要携带\0
}

然后我们就在cpp文件中处理上层业务

// udp_server.cc
std::string transactionString(std::string request)
{std::string result;char c;for(auto & r : request){if (islower(r)) {c = toupper(r);result.push_back(c);}else {result.push_back(r);}}return result;
}static bool isPass(const std::string& command)
{bool pass = true;auto pos = command.find("mv");if (pos != std::string::npos) pass = false;pos = command.find("rm");if (pos != std::string::npos) pass = false;return pass;
}std::string execteCommand(std::string command)
{// FILE *popen(const char *command, const char *type);// 1. 创建管道// 2. 创建子进程// 3. 通过FILE*将结果直接返回,可以让用户以读取文件的访问,获得命令执行的结果// 安全检查if (!isPass(command)) return "bad";// 业务逻辑处理FILE* fp = popen(command.c_str(), "r");if (fp == nullptr) return "None";// 获取结果char line[2048];std::string result;while (fgets(line, sizeof(line), fp) != NULL){result += line;}return result;fclose(fp);
}int main()
{// ...// 通过传入第一个transactionString函数可以将我们客户端输入的小写字符,在服务端转换成大写然后在返回客户端unique_ptr<UdpServer> usvr(new UdpServer(transactionString, port)); // 这个业务方法就是将客户端输入的指令发送到服务端,在服务端执行后再将执行的结果返回给客户端// unique_ptr<UdpServer> usvr(new UdpServer(execteCommand, port));
}

大小写转换业务:
在图片描述
读取客户端指令的业务
在这里插入图片描述

udp_v4

上述第三版的程序还有一些问题就是,程序运行的时候收发的处理都在start()函数中,但是这样一旦阻塞在发送或者阻塞在收取的时候,假如有多个客户端要连接服务端,就会有影响。那么在第四版中我们就将结合前面的讲述过的基于环形队列的生产消费模型来将接受与发送分别使用两个线程来处理。在第四版中我们想要实现的是一个简易的udp多人聊天程序。

// udp_server.hpp
class UdpServer
{
public:UdpServer(uint16_t port = default_port) :port_(port){pthread_mutex_init(&lock, nullptr);p = new Thread(1, std::bind(&UdpServer::Recv, this)); // 这里传入接受与发送的函数时如果直接传入会发生报错,以为Recv与Broadcast都是类的方法,而类的方法是有隐含的this指针的,此时就可以使用bind函数将this指针这个成员先绑定,然后就可以正常的运行。c = new Thread(2, std::bind(&UdpServer::Broadcast, this));}void Start(){// ...c->run();p->run();}void addUser(const std::string& name, const struct sockaddr_in& peer) // 构建一个新用户{LockGuard lockguard(&lock);// onlineuser[name] = peer;auto iter = onlineuser.find(name); // 遍历检测是否存在该用户if (iter != onlineuser.end()) return;onlineuser.insert(std::pair<const std::string, const struct sockaddr_in>(name, peer));}void Recv(){// 服务器的正常工作char buffer[1024];while (true){// 收// ssize_t 实际写入的大小 recvfrom(int sockfd 绑定的套接字, void *buf 接受数据存放的缓冲区, size_t len 缓冲区长度, int flags 读取方式(0), struct sockaddr *src_addr 需struct sockaddr_in peer;socklen_t len = sizeof(peer); // 这里一定要写清楚,未来传入的缓冲区的大小int n = recvfrom(sock_, buffer, sizeof(buffer)-1/*因为这是以字符作为消息的类型,所以缓冲区预留一部分空间*/, 0, (struct sockaddr*)&peer, &len); // 消息的类型需要程序员if (n > 0) buffer[n] = '\0';else continue;std::cout << "recv done ..." << std::endl;// 提取client信息std::string clientip = inet_ntoa(peer.sin_addr); // 把一个四字节的IP转化为字符串uint16_t clientport = ntohs(peer.sin_port); // 将从网络中获取的端口号转换成主机std::cout << clientip << "-" << clientport << "#" << buffer << std::endl;// 构建一个用户并检查std::string name = clientip;name += "-";name += std::to_string(clientport);// 如果不存在就插入,如果存在,什么都不做addUser(name, peer);std::string message = name + " >> " + buffer;rq.push(message); // 将接收到的信息存放入生产消费中}}void Broadcast(){while (true){std::string sendstring;rq.pop(&sendstring); // 从生产消费模型中获取信息std::vector<struct sockaddr_in> v; // 获取所有的用户使用数组进行记录{LockGuard lockguard(&lock); // 使用锁进行保护防止产生冲突for (auto user : onlineuser){v.push_back(user.second);}}for (auto user : v) // 依次将信息发送出去{sendto(sock_, sendstring.c_str(), sendstring.size(), 0, (struct sockaddr *)&user, sizeof(user));}}}~UdpServer(){pthread_mutex_destroy(&lock);c->join();p->join();delete p;delete c;}
private:// ...std::unordered_map<std::string, struct sockaddr_in> onlineuser; // 添加使用用户RingQueue<std::string> rq; // 基于环形队列的生产消费模型pthread_mutex_t lock; // 互斥锁Thread *c, *p; // 创建生产者与消费者线程
};

对于客户端同样可以使用多线程

udp_client.cc
void* recver(void* args)
{pthread_detach(pthread_self());int sock = *static_cast<int*>(args);while (true){// 接收char buffer[2048];struct sockaddr_in temp;socklen_t len = sizeof(temp);int n = recvfrom(sock, buffer, sizeof(buffer)-1, 0, (struct sockaddr *)&temp, &len);if (n > 0){buffer[n] = 0;std::cout << buffer << std::endl;}}
}
int main(int argc, char *argv[])
{// ...pthread_t tid;pthread_create(&tid, nullptr, recver, &sock);while (true){// 用户输入std::string message;std::cerr << "please Enter# "; // 往2号文件发送// std::cin >> message;getline(std::cin, message);// 发送sendto(sock, message.c_str(), message.size(), 0, (struct sockaddr *)&server, sizeof(server));}return 0;
}

在这里插入图片描述
如上图所示就可以看到在右图中两个不同的客户端发送的消息都可以被看到。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

HarmonyOS/OpenHarmony(Stage模型)应用开发组合手势(一)连续识别

组合手势由多种单一手势组合而成&#xff0c;通过在GestureGroup中使用不同的GestureMode来声明该组合手势的类型&#xff0c;支持连续识别、并行识别和互斥识别三种类型。 .GestureGroup(mode:GestureMode, …gesture:GestureType[]) mode&#xff1a;必选参数&#xff0c;为G…

MP中的字段还可以利用函数来查询拼接sql

//根据value查询GetMapping("getTest")public List<HashMap> getTest() {QueryWrapper<TTest> queryWrapper new QueryWrapper<>();queryWrapper.eq("substr(name,1,2)","99999");List<TTest> list1 testService.list…

【网络通信 -- WebRTC】Open WebRTC Toolkit 环境搭建指南

【网络通信 -- WebRTC】Open WebRTC Toolkit -- OWT-Server 编译安装指南 【1】OWT Server 与 Web Demo 视频会议环境搭建 【1.1】编译 OWT Server 安装依赖 ./scripts/installDepsUnattended.sh编译 scripts/build.js -t all --check 注意若不支持硬件加速则采用如下命令 s…

[CISCN 2019华北Day1]Web1

文章目录 涉及知识点解题过程 涉及知识点 phar反序列化文件读取 解题过程 打开题目&#xff0c;注册用户为admin 进去发现有文件上传的功能&#xff0c;我们随便上传个图片 然后就有下载和删除两个功能 我们尝试抓包下载文件的功能 发现参数可控&#xff0c;我们尝试读取一下…

Navicat15工具连接PostgreSQL15失败

1.错误现象及原因 错误现象&#xff1a; 错误原因&#xff1a; postgresql 15版本中 pg_database 系统表把 datlastsysoid 列删除了&#xff0c;所以造成了此错误。 2.解决方法 &#xff08;1&#xff09;将Navicat工具更新到官网最新版本。 &#xff08;2&#xff09;更换…

划片机实现装片、对准、切割、清洗到卸片的自动化操作

划片机是一种用于切割和分离材料的设备&#xff0c;通常用于光学和医疗、IC、QFN、DFN、半导体集成电路、GPP/LED氮化镓等芯片分立器件、LED封装、光通讯器件、声表器件、MEMS等行业。划片机可以实现从装片、对准、切割、清洗到卸片的自动化操作。 以下是划片机实现这些操作的步…

回复:c#的Winform如何让ComboBox不显示下拉框?https://bbs.csdn.net/topics/392565412

组合框.Parent this;组合框.Items.AddRange(new object[] { "111", "222", "333", "444" });组合框.DropDownHeight 1;组合框.SelectedIndex 0;//组合框.DropDownStyle ComboBoxStyle.Simple; ComboBox 组合框 new ComboBox();Li…

Web framework-Gin(二)

目录 一、Gin 1、Ajax 2、文件上传 2.1、form表单中文件上传(单个文件) 2.2、form表单中文件上传(多个文件) 2.3、ajax上传单个文件 2.4、ajax上传多个文件 3、模板语法 4、数据绑定 5、路由组 6、中间件 一、Gin 1、Ajax AJAX 即“Asynchronous Javascript And XM…

web端调用本地摄像头麦克风+WebRTC腾讯云,实现直播功能

目录 关于直播直播流程直播视频格式封装推流和拉流 获取摄像头和麦克风权限navigator.getUserMedia()MediaDevices.getUserMedia() WebRTC腾讯云快直播 关于直播 视频直播技术大全、直播架构、技术原理和实现思路方案整理 直播流程 视频采集端&#xff1a; 1、视频采集&#…

简述视频智能分析EasyCVR视频汇聚平台如何通过“AI+视频融合”技术规避八大特殊作业风险

视频智能分析EasyCVR视频汇聚平台可以根据不同的场景需求&#xff0c;让平台在内网、专网、VPN、广域网、互联网等各种环境下进行音视频的采集、接入与多端分发。在视频能力上&#xff0c;视频云存储平台EasyCVR可实现视频实时直播、云端录像、视频云存储、视频存储磁盘阵列、录…

uniapp 实现滑动元素删除效果

官网地址&#xff1a;uni-app官网 (dcloud.net.cn) 最终效果如下图&#xff1a; 滑动删除需要用到 uni-ui 的 uni-swipe-action 组件和 uni-swipe-action-item 属性名类型可选值默认值是否必填说明left-optionsArray/Object--否左侧选项内容及样式right-optionsArray/Object--…

Vue2+Vue3基础入门到实战项目(七)——智慧商城项目

Vue 核心技术与实战 智慧商城 接口文档&#xff1a;https://apifox.com/apidoc/shared-12ab6b18-adc2-444c-ad11-0e60f5693f66/doc-2221080 演示地址&#xff1a;http://cba.itlike.com/public/mweb/#/ 01. 项目功能演示 1.明确功能模块 启动准备好的代码&#xff0c;演示…

【Unity3D赛车游戏优化篇】【十】汽车粒子特效和引擎咆哮打造极速漂移

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;Uni…

【PHP代码审计】反序列化漏洞实战

文章目录 概述资源下载地址Typecho代码审计-漏洞原理call_user_func()_applyFilter()、get()与__get__toString()__construct()install.php POC利用漏洞利用复现利用链执行phpinfo()GET利用POST利用 getshell生成payload漏洞利用蚁剑连接 总结 概述 序列化&#xff0c;“将对象…

从Matrix-ResourceCanary看内存快照生成-ForkAnalyseProcessor(1)

前文看到AutoDumpProcessor的处理逻辑主要是生成&#xff0c;裁剪hprof文件并回调到PluginListener中&#xff0c;接下来我们来看下ForkAnalyseProcessor的处理逻辑。 ForkAnalyseProcessor public class ForkAnalyseProcessor extends BaseLeakProcessor {private static fi…

Nginx 本地部署服务

nginx 部署服务 一、下载二、解压三、文件替换四、浏览器中输入五、离线部署瓦片服务 一、下载 可以到官网下载&#xff0c;官方网址&#xff1a;https://nginx.org/也可以用我发的包 二、解压 三、文件替换 解压打开后文件&#xff0c;双击 nginx.exe 浏览器输入 localhost…

对极几何与三角化求3D空间坐标

一&#xff0c;使用对极几何约束求R,T 第一步&#xff1a;特征匹配。提取出有效的匹配点 void find_feature_matches(const Mat &img_1, const Mat &img_2,std::vector<KeyPoint> &keypoints_1,std::vector<KeyPoint> &keypoints_2,std::vector&l…

Python超入门(1)__迅速上手操作掌握Python

# 1.第一个代码&#xff1a;输出语句 # 1.第一个代码&#xff1a;输出语句 print("My dogs name is Huppy!") print(o----) print( ||| ) print("*" * 10) """ 输出结果&#xff1a; My dogs name is Huppy! o----||| ********** "&…

RK3588算法盒子maskrom模式下系统烧录

先在firefly官网下载bulidroot文件&#xff0c;然后进行烧录相关文件。 点击upgrade&#xff0c;进行烧录升级&#xff1b; 等待烧录完成后&#xff0c; 重新开机进入loader模式下&#xff0c; 进行烧录Untunt20.04系统的操作就行。

(16)线程的实例认识:Await,Async,ConfigureAwait

继续(15)的例子 一、ConfigureAwait()的作用 private async void BtnAsync_Click(object sender, EventArgs e)//异步{Stopwatch sw Stopwatch.StartNew();TxtInfo.Clear();AppendLine("异步检索开始...");AppendLine($"当前线程Id:{Environment.CurrentManage…