libuv库学习笔记-networking
Networking
在 libuv 中,网络编程与直接使用 BSD socket 区别不大,有些地方还更简单,概念保持不变的同时,libuv 上所有接口都是非阻塞的。它还提供了很多工具函数,抽象了恼人、啰嗦的底层任务,如使用 BSD socket 结构体设置 socket 、DNS 查找以及调整各种 socket 参数。
在网络I/O中会使用到uv_tcp_t
和uv_udp_t
。
note
本章中的代码片段仅用于展示 libuv API ,并不是优质代码的范例,常有内存泄露和未关闭的连接。
TCP
TCP是面向连接的,字节流协议,因此基于libuv的stream实现。
server
服务器端的建立流程如下:
1.uv_tcp_init
建立tcp句柄。
2.uv_tcp_bind
绑定。
3.uv_listen
建立监听,当有新的连接到来时,激活调用回调函数。
4.uv_accept
接收链接。
5.使用stream操作来和客户端通信。
tcp-echo-server/main.c - The listen socket
int main() {loop = uv_default_loop();uv_tcp_t server;uv_tcp_init(loop, &server);uv_ip4_addr("0.0.0.0", DEFAULT_PORT, &addr);uv_tcp_bind(&server, (const struct sockaddr*)&addr, 0);int r = uv_listen((uv_stream_t*) &server, DEFAULT_BACKLOG, on_new_connection);if (r) {fprintf(stderr, "Listen error %s\n", uv_strerror(r));return 1;}return uv_run(loop, UV_RUN_DEFAULT);
}
你可以调用uv_ip4_addr()
函数来将ip地址和端口号转换为sockaddr_in结构,这样就可以被BSD的socket使用了。要想完成逆转换的话可以调用uv_ip4_name()
。
note
对应ipv6有类似的uv_ip6_*
大多数的设置函数是同步的,因为它们毕竟不是io操作。到了uv_listen
这句,我们再次回到回调函数的风格上来。第二个参数是待处理的连接请求队列-最大长度的请求连接队列。
当客户端开始建立连接的时候,回调函数on_new_connection
需要使用uv_accept
去建立一个与客户端socket通信的句柄。同时,我们也要开始从流中读取数据。
tcp-echo-server/main.c - Accepting the client
void on_new_connection(uv_stream_t *server, int status) {if (status < 0) {fprintf(stderr, "New connection error %s\n", uv_strerror(status));// error!return;}uv_tcp_t *client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));uv_tcp_init(loop, client);if (uv_accept(server, (uv_stream_t*) client) == 0) {uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);}else {uv_close((uv_handle_t*) client, NULL);}
}
上述的函数集和stream的例子类似,在code文件夹中可以找到更多的例子。记得在socket不需要后,调用uv_close。如果你不需要接受连接,你甚至可以在uv_listen的回调函数中调用uv_close。
client
当你在服务器端完成绑定/监听/接收的操作后,在客户端只要简单地调用uv_tcp_connect
,它的回调函数和上面类似,具体例子如下:
uv_tcp_t* socket = (uv_tcp_t*)malloc(sizeof(uv_tcp_t));
uv_tcp_init(loop, socket);uv_connect_t* connect = (uv_connect_t*)malloc(sizeof(uv_connect_t));struct sockaddr_in dest;
uv_ip4_addr("127.0.0.1", 80, &dest);uv_tcp_connect(connect, socket, dest, on_connect);
当建立连接后,回调函数on_connect
会被调用。回调函数会接收到一个uv_connect_t结构的数据,它的handle
指向通信的socket。
UDP
用户数据报协议(User Datagram Protocol)提供无连接的,不可靠的网络通信。因此,libuv不会提供一个stream实现的形式,而是提供了一个uv_udp_t
句柄(接收端),和一个uv_udp_send_t
句柄(发送端),还有相关的函数。也就是说,实际的读写api与正常的流读取类似。下面的例子展示了一个从DCHP服务器获取ip的例子。
note
你必须以管理员的权限运行udp-dhcp,因为它的端口号低于1024
udp-dhcp/main.c - Setup and send UDP packets
uv_loop_t *loop;
uv_udp_t send_socket;
uv_udp_t recv_socket;int main() {loop = uv_default_loop();uv_udp_init(loop, &recv_socket);struct sockaddr_in recv_addr;uv_ip4_addr("0.0.0.0", 68, &recv_addr);uv_udp_bind(&recv_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);uv_udp_recv_start(&recv_socket, alloc_buffer, on_read);uv_udp_init(loop, &send_socket);struct sockaddr_in broadcast_addr;uv_ip4_addr("0.0.0.0", 0, &broadcast_addr);uv_udp_bind(&send_socket, (const struct sockaddr *)&broadcast_addr, 0);uv_udp_set_broadcast(&send_socket, 1);uv_udp_send_t send_req;uv_buf_t discover_msg = make_discover_msg();struct sockaddr_in send_addr;uv_ip4_addr("255.255.255.255", 67, &send_addr);uv_udp_send(&send_req, &send_socket, &discover_msg, 1, (const struct sockaddr *)&send_addr, on_send);return uv_run(loop, UV_RUN_DEFAULT);
}
note
ip地址为0.0.0.0,用来绑定所有的接口。255.255.255.255是一个广播地址,这也意味着数据报将往所有的子网接口中发送。端口号为0代表着由操作系统随机分配一个端口。
首先,我们设置了一个用于接收socket绑定了全部网卡,端口号为68作为DHCP客户端,然后开始从中读取数据。它会接收所有来自DHCP服务器的返回数据。我们设置了UV_UDP_REUSEADDR
标记,用来和其他共享端口的 DHCP客户端和平共处。接着,我们设置了一个类似的发送socket,然后使用uv_udp_send
向DHCP服务器(在67端口)发送广播。
设置广播发送是非常必要的,否则你会接收到EACCES
错误。和此前一样,如果在读写中出错,返回码<0。
因为UDP不会建立连接,因此回调函数会接收到关于发送者的额外的信息。
当没有可读数据后,nread等于0。如果addr
是null
,它代表了没有可读数据(回调函数不会做任何处理)。如果不为null,则说明了从addr中接收到一个空的数据报。如果flag为UV_UDP_PARTIAL
,则代表了内存分配的空间不够存放接收到的数据了,在这种情形下,操作系统会丢弃存不下的数据。
udp-dhcp/main.c - Reading packets
void on_read(uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) {if (nread < 0) {fprintf(stderr, "Read error %s\n", uv_err_name(nread));uv_close((uv_handle_t*) req, NULL);free(buf->base);return;}char sender[17] = { 0 };uv_ip4_name((const struct sockaddr_in*) addr, sender, 16);fprintf(stderr, "Recv from %s\n", sender);// ... DHCP specific codeunsigned int *as_integer = (unsigned int*)buf->base;unsigned int ipbin = ntohl(as_integer[4]);unsigned char ip[4] = {0};int i;for (i = 0; i < 4; i++)ip[i] = (ipbin >> i*8) & 0xff;fprintf(stderr, "Offered IP %d.%d.%d.%d\n", ip[3], ip[2], ip[1], ip[0]);free(buf->base);uv_udp_recv_stop(req);
}
UDP Options
生存时间(Time-to-live)
可以通过
uv_udp_set_ttl
更改生存时间。
只允许IPV6协议栈
在调用
uv_udp_bind
时,设置UV_UDP_IPV6ONLY
标示,可以强制只使用ipv6。
组播
socket也支持组播,可以这么使用:
UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle,const char* multicast_addr,const char* interface_addr,uv_membership membership);
其中membership
可以为UV_JOIN_GROUP
和UV_LEAVE_GROUP
。
这里有一篇很好的关于组播的文章。
可以使用uv_udp_set_multicast_loop
修改本地的组播。
同样可以使用uv_udp_set_multicast_ttl
修改组播数据报的生存时间。(设定生存时间可以防止数据报由于环路的原因,会出现无限循环的问题)。
Querying DNS
libuv提供了一个异步的DNS解决方案。它提供了自己的getaddrinfo
。在回调函数中你可以像使用正常的socket操作一样。让我们来看一下例子:
dns/main.c
int main() {loop = uv_default_loop();struct addrinfo hints;hints.ai_family = PF_INET;hints.ai_socktype = SOCK_STREAM;hints.ai_protocol = IPPROTO_TCP;hints.ai_flags = 0;uv_getaddrinfo_t resolver;fprintf(stderr, "irc.freenode.net is... ");int r = uv_getaddrinfo(loop, &resolver, on_resolved, "irc.freenode.net", "6667", &hints);if (r) {fprintf(stderr, "getaddrinfo call error %s\n", uv_err_name(r));return 1;}return uv_run(loop, UV_RUN_DEFAULT);
}
如果uv_getaddrinfo
返回非零值,说明设置错误了,因此也不会激发回调函数。在函数返回后,所有的参数将会被回收和释放。主机地址,请求服务器地址,还有hints的结构都可以在这里找到详细的说明。如果想使用同步请求,可以将回调函数设置为NULL。
在回调函数on_resolved中,你可以从struct addrinfo(s)
链表中获取返回的IP,最后需要调用uv_freeaddrinfo
回收掉链表。下面的例子演示了回调函数的内容。
dns/main.c
void on_resolved(uv_getaddrinfo_t *resolver, int status, struct addrinfo *res) {if (status < 0) {fprintf(stderr, "getaddrinfo callback error %s\n", uv_err_name(status));return;}char addr[17] = {'\0'};uv_ip4_name((struct sockaddr_in*) res->ai_addr, addr, 16);fprintf(stderr, "%s\n", addr);uv_connect_t *connect_req = (uv_connect_t*) malloc(sizeof(uv_connect_t));uv_tcp_t *socket = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));uv_tcp_init(loop, socket);uv_tcp_connect(connect_req, socket, (const struct sockaddr*) res->ai_addr, on_connect);uv_freeaddrinfo(res);
}
libuv同样提供了DNS逆解析的函数uv_getnameinfo。
Network interfaces
可以调用uv_interface_addresses
获得系统的网络接口信息。下面这个简单的例子打印出所有可以获取的信息。这在服务器开始准备绑定IP地址的时候很有用。
interfaces/main.c
#include <stdio.h>
#include <uv.h>int main() {char buf[512];uv_interface_address_t *info;int count, i;uv_interface_addresses(&info, &count);i = count;printf("Number of interfaces: %d\n", count);while (i--) {uv_interface_address_t interface = info[i];printf("Name: %s\n", interface.name);printf("Internal? %s\n", interface.is_internal ? "Yes" : "No");if (interface.address.address4.sin_family == AF_INET) {uv_ip4_name(&interface.address.address4, buf, sizeof(buf));printf("IPv4 address: %s\n", buf);}else if (interface.address.address4.sin_family == AF_INET6) {uv_ip6_name(&interface.address.address6, buf, sizeof(buf));printf("IPv6 address: %s\n", buf);}printf("\n");}uv_free_interface_addresses(info, count);return 0;
}
is_internal
可以用来表示是否是内部的IP。由于一个物理接口会有多个IP地址,所以每一次while循环的时候都会打印一次。
相关文章:
libuv库学习笔记-networking
Networking 在 libuv 中,网络编程与直接使用 BSD socket 区别不大,有些地方还更简单,概念保持不变的同时,libuv 上所有接口都是非阻塞的。它还提供了很多工具函数,抽象了恼人、啰嗦的底层任务,如使用 BSD …...

C++多线程编程(第三章 案例1,使用互斥锁+ list模拟线程通信)
主线程和子线程进行list通信,要用到互斥锁,避免同时操作 1、封装线程基类XThread控制线程启动和停止; 2、模拟消息服务器线程,接收字符串消息,并模拟处理; 3、通过Unique_lock和mutex互斥方位list 消息队列…...

IOS UICollectionView 设置cell大小不生效问题
代码设置flowLayout.itemSize 单元格并没有改变布局大小, 解决办法如下图:把View flow layout 的estimate size 设置为None,上面设置的itemSize 生效了。...

浅谈3D隐式表示(SDF,Occupancy field,NeRF)
本篇文章介绍了符号距离函数Signed Distance Funciton(SDF),占用场Occupancy Field,神经辐射场Neural Radiance Field(NeRF)的概念、联系与区别。 显式表示与隐式表示 三维空间的表示形式可以分为显式和隐式。 比较常用的显式表…...

软件测试技能大赛任务二单元测试试题
任务二 单元测试 执行代码测试 本部分按照要求,执行单元测试,编写java应用程序,按照要求的覆盖方法设计测试数据,使用JUnit框架编写测试类对程序代码进行测试,对测试执行结果进行截图,将相关代码和相关截…...

MybatisPlus拓展篇
文章目录 逻辑删除通用枚举字段类型处理器自动填充功能防全表更新与删除插件MybatisX快速开发插件插件安装逆向工程常见需求代码生成 乐观锁问题引入乐观锁的使用效果测试 代码生成器执行SQL分析打印多数据源 逻辑删除 逻辑删除的操作就是增加一个字段表示这个数据的状态&…...
设置k8s中节点node的ROLES值,K8S集群怎么修改node1的集群ROLES
设置k8s中节点node的ROLES值 1.查看集群 [rootk8s-master ~]# kubectl get nodes NAME STATUS ROLES AGE VERSION k8s-master Ready control-plane,master 54d v1.23.8 k8s-node1 Ready <none> 54d v1.…...

【*1900 图论】CF1328 E
Problem - E - Codeforces 题意: 思路: 注意到题目的性质:满足条件的路径个数是极少的,因为每个点离路径的距离<1 先考虑一条链,那么直接就选最深那个点作为端点即可 为什么,因为我们需要遍历所有点…...
微信开发者工具 miniprogram_npm 未找到
背景 微信开发者工具中,打开集成了vant-weapp的项目,构建npm时,报错\miniprogram_npm\ 未找到。 问题 微信开发者工具,工具----->构建npm时,提示 message:发生错误 Error: D:\some\path\miniprogram…...

计算机视觉(三)未有深度学习之前
文章目录 图像分割基于阈值、基于边缘基于区域、基于图论 人脸检测Haar-like特征级联分类器 行人检测HOGSVMDPM 图像分割 把图像划分成若干互不相交的区域。经典的数字图像分割算法一般是基于灰度值的两个基本特征之一:不连续性和相似性。 基于阈值、基于边缘 基于…...
二十六、媒体查询2
目录: 媒体查询介绍网页常用分界点 一、媒体查询介绍 媒体特性: width 视口的宽度 height 视口的高度 一般设计的时候,高度不考虑,只考虑宽度 //当视口的宽度是500像素的时候,变颜色media (width: 500px) {body{background-colo…...

Themis 国库建设计划启动,开启去中心化新征程
在未来的金融领域,去中心化金融(DeFi)正在成为一种重要的趋势。在这股DeFi热潮中,作为Filecoin 生态下的一颗璀璨明珠,Themis 上线仅2个月,多项数据便稳居Filecoin-FVM榜首,TVL更是牢牢处于File…...

uni-app:模态框的实现(弹窗实现)
效果图 代码 标签 <template><view><!-- 按钮用于触发模态框的显示 --><button click"showModal true">显示模态框</button><!-- 模态框组件 --><view class"modal" v-if"showModal"><view cla…...

第九章:stack类
系列文章目录 文章目录 系列文章目录前言stack的介绍stack的使用成员函数使用stack 总结 前言 stack是容器适配器,底层封装了STL容器。 stack的介绍 stack的文档介绍 stack是一种容器适配器,专门用在具有后进先出操作的上下文环境中,其删除…...

FSM:Full Surround Monodepth from Multiple Cameras
参考代码:None 介绍 深度估计任务作为基础环境感知任务,在基础上构建的3D感知才能更加准确,并且泛化能力更强。单目的自监督深度估计已经有MonoDepth、ManyDepth这些经典深度估计模型了,而这篇文章是对多目自监督深度估计进行探…...

idea 安装 插件jrebel 报错LS client not configured.
这个报错找了好久,有博主说版本不对,我脑子没反应过来以为是随便换一个低版本的就行,没想到只能是2022.4.1 这个版本才行 一定要用jrebel 2022.4.1的插件版本!!!!! 插件下载地址&…...

Raki的读paper小记:RWKV: Reinventing RNNs for the Transformer Era
Abstract&Introduction&Related Work 研究任务 基础模型架构已有方法和相关工作 RNN,CNN,Transformer稀疏注意力(Beltagy等人,2020年;Kitaev等人,2020年;Guo等人,2022年&am…...

PaddleOCR #PP-OCR常见异常扫雷
异常一:ModuleNotFoundError: No module named ‘tools.infer’ 实验案例: PaddleOCR #使用PaddleOCR进行光学字符识别(PP-OCR文本检测识别) 参考代码: 图片文本检测实验时,运行代码出现异常:M…...
Qt加载字体文件
本文记录如何使用 Qt 加载外部字体文件,并遍历字体名称和样式名称。 bool LoadFont(const QString& fontPath) {const int fontId QFontDatabase::addApplicationFont(fontPath);if (fontId -1) {return false;}// 遍历字体名和样式名 #if QT_VERSION > QT…...

3ds MAX绘制简单动画
建立一个长方体和茶壶: 在界面右下角点击时间配置: 这是动画制作的必要步骤 选择【自动】,接下来,我们只要在对应的帧改变窗口中图形的位置,就能自动记录该时刻的模样 这就意味着,我们通过电脑记录某几个…...

XCTF-web-easyupload
试了试php,php7,pht,phtml等,都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接,得到flag...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...

C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
省略号和可变参数模板
本文主要介绍如何展开可变参数的参数包 1.C语言的va_list展开可变参数 #include <iostream> #include <cstdarg>void printNumbers(int count, ...) {// 声明va_list类型的变量va_list args;// 使用va_start将可变参数写入变量argsva_start(args, count);for (in…...

【无标题】湖北理元理律师事务所:债务优化中的生活保障与法律平衡之道
文/法律实务观察组 在债务重组领域,专业机构的核心价值不仅在于减轻债务数字,更在于帮助债务人在履行义务的同时维持基本生活尊严。湖北理元理律师事务所的服务实践表明,合法债务优化需同步实现三重平衡: 法律刚性(债…...

ubuntu中安装conda的后遗症
缘由: 在编译rk3588的sdk时,遇到编译buildroot失败,提示如下: 提示缺失expect,但是实测相关工具是在的,如下显示: 然后查找借助各个ai工具,重新安装相关的工具,依然无解。 解决&am…...
el-amap-bezier-curve运用及线弧度设置
文章目录 简介示例线弧度属性主要弧度相关属性其他相关样式属性完整示例链接简介 el-amap-bezier-curve 是 Vue-Amap 组件库中的一个组件,用于在 高德地图 上绘制贝塞尔曲线。 基本用法属性path定义曲线的路径,可以是多个弧线段的组合。stroke-weight线条的宽度。stroke…...

二维数组 行列混淆区分 js
二维数组定义 行 row:是“横着的一整行” 列 column:是“竖着的一整列” 在 JavaScript 里访问二维数组 grid[i][j] 表示 第i行第j列的元素 let grid [[1, 2, 3], // 第0行[4, 5, 6], // 第1行[7, 8, 9] // 第2行 ];// grid[i][j] 表示 第i行第j列的…...
SE(Secure Element)加密芯片与MCU协同工作的典型流程
以下是SE(Secure Element)加密芯片与MCU协同工作的典型流程,综合安全认证、数据保护及防篡改机制: 一、基础认证流程(参数保护方案) 密钥预置 SE芯片与MCU分别预置相同的3DES密钥(Key1、Key2…...

[学习笔记]使用git rebase做分支差异化同步
在一个.NET 项目中,使用了Volo.Abp库,但出于某种原因,需要源码调试,因此,使用源码方式集成的项目做了一个分支archive-abp-source 其中引用方式变更操作的提交为:7de53907 后续,在master分支中…...