(udp)网络编程套接字Linux(整理)

(udp)网络编程套接字Linux(整理)

 

目录

 源IP地址和目的IP地址

认识端口号

理解 "端口号" 和 "进程ID"

理解源端口号和目的端口号

IP:Port()

认识TCP协议

认识UDP协议

网络字节序

网络字节序和主机字节序的转换

主机转网络

​编辑

​编辑

 网络转主机(后面ntoa就不建议使用了)​编辑

网络字节序和主机字节序的转换函数

示例代码

解释

输出示例

适用平台

总结

网络转主机序列,主机序列转网络序列

地址转换函数(字符串传4字节IP)

套接字编程的种类:

udp代码示例

socket(创建一个套接字)

​编辑

绑定bind​编辑

绑定端口号

sockaddr结构

bzero把上面的结构体清空

INET

addr,ip地址(32位的),port端口号,family所用的域或者家族(AF_INET)

int,uint16_t,uint32_t的区别及其各自的用法

在网络中为什么用uint16_t,不用int

##的作用

主机序列转成网络序列

main中这样传ip

如何快速的将整数IP<->字符串IP

把字符串风格的ip地址转化为四字节(网络风格的四字节)

以上udp服务器核心的启动代码基本已经完成

思路精华

服务器应该周而复始的运行

recv

收消息

发送回给客户端

查看端口号和IP地址(netstat -naup)​编辑

测试,ip地址是云服务器的,8080端口号

云服务器最好就直接不写ip地址就好了,直接默认的0

关于port的问题(端口号)

0-1023不让绑定,最好绑定1024的端口,有的3306也不可以 

写成命令行./udpServer+port

写一个客户端

创建套接字

记得close网络文件

客户端要bind吗

服务器的端口号要确定是因为,客户端是要访问服务端的,需要知道确定的端口号和ip

客户端写成命令行./udpServer+ip+port

1.数据  2.发给谁(sendto)

整理一下。主机转网络(服务器信息)

接收一下(recvfrom)

代码示例

测试(客户端向服务器发信息)

udpServer.hpp

udpClient.cc

main.cc 

makefile

 多台机器网络信息传递测试

网络处理数据和接收数据耦合度太高了

服务器外面给接收到的数据做处理 

如果发来的是命令呢

本地环回127.0.0.1

在服务器查看发信的客户端的端口号和IP

网络转主机拿到端口号和IP

改变一下处理数据方式,打印出IP和port

只允许一个客户端连接/判断是不是一个新用户(kv)

客户端整改

登录qq时,不发送信息也能收到信息,但目前的客户端没法做到

会在getline阻塞住

客户端改成多线程,一个线程输入一个线程显示

udp的sockfd是可以同时被读写的

是线程安全的

思路和测试结果​编辑

制作简单的聊天室类似群聊

覆盖问题


源IP地址和目的IP地址

唐僧例子1

在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析.

认识端口号

5aa466f7741c43a6a19bacf33aabd297.png

理解 "端口号" 和 "进程ID"

我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?

10086例子

另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;

fe650b7d258a451db453b5bfa3ed4483.png
216566e140c14ef1a5b81f7c0b77b650.png

理解源端口号和目的端口号

唐僧例子2

送快递例子

传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁";

IP:Port()

ip地址4字节  端口号两个字节

跨主机 

7f46fb8563ff448bbee7b5773dd222f6.png

看到的公共部分       是网络

e63b13bff86441209aa88a58a35e44a9.png

在公网上:
IP地址能表示唯一的一台主机,端口号port,用来标识该主机上的唯一的一个进程
IP:Port=标识全网唯一的一个进程

9ed020d12d3c41fea1c69616d95c77b8.png
fe650b7d258a451db453b5bfa3ed4483.png
216566e140c14ef1a5b81f7c0b77b650.png

认识TCP协议

68e77c3741a24c249575602bfb9c54e6.png

认识UDP协议

cb6e6a1c88d24b698fd2049578f21457.png

网络字节序

ip地址4字节  端口号两个字节

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?

发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;
a8346459c5d84253b9f3e6010f9f7c41.png

为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

2c926d5138e3446ba4c12cb6e023c15c.png
480c8a69229b485982c3aef641c8087d.png

网络字节序和主机字节序的转换

主机转网络

服务端

客户端 

 网络转主机(后面ntoa就不建议使用了)

在 C 或 C++ 中,网络字节序和主机字节序的转换非常重要,特别是在进行网络编程时,因为不同的平台可能有不同的字节序(Endianness)。网络字节序通常是大端字节序(Big Endian),而主机字节序可能是大端或小端(Little Endian)。

网络字节序和主机字节序的转换函数

  1. **htons()**:将主机字节序(Host)转换为网络字节序(Network),适用于 16 位数据(short)。
  2. **htonl()**:将主机字节序(Host)转换为网络字节序(Network),适用于 32 位数据(long)。
  3. **ntohs()**:将网络字节序(Network)转换为主机字节序(Host),适用于 16 位数据(short)。
  4. **ntohl()**:将网络字节序(Network)转换为主机字节序(Host),适用于 32 位数据(long)。

示例代码

#include <stdio.h> #include <arpa/inet.h> // 包含字节序转换函数(适用于 Linux 和 UNIX) int main() { uint16_t host_short = 0x1234; // 主机字节序的 16 位数据 uint32_t host_long = 0x12345678; // 主机字节序的 32 位数据 // 主机字节序转网络字节序 uint16_t network_short = htons(host_short); uint32_t network_long = htonl(host_long); // 网络字节序转主机字节序 uint16_t converted_short = ntohs(network_short); uint32_t converted_long = ntohl(network_long); // 输出结果 printf("Host short: 0x%04X, Network short: 0x%04X, Converted back: 0x%04X\n", host_short, network_short, converted_short); printf("Host long: 0x%08X, Network long: 0x%08X, Converted back: 0x%08X\n", host_long, network_long, converted_long); return 0; } 

解释

  • **htons()**:将主机字节序的 16 位数据(host_short)转换为网络字节序。
  • **htonl()**:将主机字节序的 32 位数据(host_long)转换为网络字节序。
  • **ntohs()**:将网络字节序的 16 位数据(network_short)转换为主机字节序。
  • **ntohl()**:将网络字节序的 32 位数据(network_long)转换为主机字节序。

输出示例

Host short: 0x1234, Network short: 0x3412, Converted back: 0x1234 Host long: 0x12345678, Network long: 0x78563412, Converted back: 0x12345678 

适用平台

这些字节序转换函数通常用于 Linux 和 UNIX 系统中,通过包含 arpa/inet.h 头文件来访问。对于 Windows 系统,提供了类似的转换函数,但位于 winsock2.h 中,使用方法相同。

总结

  • htons() 和 **htonl()**:用于将主机字节序转换为网络字节序(适用于 16 位和 32 位数据)。
  • ntohs() 和 **ntohl()**:用于将网络字节序转换为主机字节序。

这些转换函数在进行网络编程时非常重要,可以确保不同平台和字节序的主机之间进行正确的数据传输和处理。

网络转主机序列,主机序列转网络序列

发送到网络里的要转网络序列,从网络里拿的要转主机序列

地址转换函数(字符串传4字节IP)

本节只介绍基于IPv4的socket网络编程,sockaddr_in中的成员struct in_addr sin_addr表示32位 的IP 地址

但是我们通常用点分十进制的字符串表示IP 地址,以下函数可以在字符串表示 和in_addr表示之间转换;

字符串转in_addr的函数:

in_addr转字符串的函数:

其中inet_pton和inet_ntop不仅可以转换IPv4的in_addr,还可以转换IPv6的in6_addr,因此函数接口是void *addrptr。 代码示例:

关于inet_ntoa

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是 否需要调用者手动释放呢

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放.

那么问题来了, 如果我们调用多次这个函数, 会有什么样的效果呢? 参见如下代码:

运行结果如下:

因为inet_ntoa把结果放到自己内部的一个静态存储区, 这样第二次调用时的结果会覆盖掉上一次的结果.

思考: 如果有多个线程调用 inet_ntoa, 是否会出现异常情况呢?在APUE中, 明确提出inet_ntoa不是线程安全的函数;但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁;在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题;

套接字编程的种类:

1.域间套接字编程2.原始套接字编程3.网络套接字编程

d64c03e49b54459a82f8462e3fa411a3.png

udp代码示例

udp服务器

ca4091b5be82448daecdf81c49d6f1dd.png

socket(创建一个套接字)

socket创建套接字

第一个参数是一个域

socket 常见API

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol); // 绑定端口号 (TCP/UDP, 服务器) int bind(int socket, const struct sockaddr *address, socklen_t address_len); // 开始监听socket (TCP, 服务器) int listen(int socket, int backlog); // 接收请求 (TCP, 服务器) int accept(int socket, struct sockaddr* address, socklen_t* address_len); // 建立连接 (TCP, 客户端) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

第一个参数:创建一个套接字的域,什么叫做域呢

可以理解为我们所要的那个套接字他是属于什么AF_INET,将来是使用IPv4还是IPv6网络通信的,还是有叫本地通信也叫作域间通信,第二个参数是socket对应的类型,udp是面向用户数据报(SOCK_DGRAM)tcp是面向字节流的第三个参数不用填,协议类型,创建一个套接字的本质就是打开一个文件,必须要告诉服务器,端口号,然后再绑定套接字端口号
8d653c031bf046d5aa810558107d2b7f.png

不同类型(第一个参数的) 

8716260b5598498d938bbcbd53f4783a.png

第二个:定义的套接字的类型

30f91d16c7da430a8bff696543150763.png

第三个写0就可以

71ccbde87cf1426295bbaf65c70b00e8.png
#pragma once #include<iostream> #include <sys/types.h> #include <sys/socket.h> using namespace std; class UdpSever { public: UdpSever() {} void Init() { sockfd = socket(AF_INET, SOCK_DGRAM, 0); } void Run() {} ~UdpSever() {} private: int sockfd;//网络文件描述符 };

把日志功能拷贝进来

99144e4d1376478fa96baba7ecefd7b8.png

创建一个Log对象

740be8267ae442d49fd83f34c6ec5278.png

 std::unique_ptr

3d9a64f6500d4915b22a6f79297d3eb5.png
c04034a05b5349a4b2afd45c2866be76.png

C++11中开始提供更靠谱的unique_ptr

文档:https://cplusplus.com/reference/memory/unique_ptr/

unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原 理

/ C++11库才更新智能指针实现

// C++11出来之前,boost搞除了更好用的scoped_ptr/shared_ptr/weak_ptr // C++11将boost库中智能指针精华部分吸收了过来

// C++11->unique_ptr/shared_ptr/weak_ptr // unique_ptr/scoped_ptr // 原理:简单粗暴 -- 防拷贝

测试:

3b54eddb23834907af8c46e9fd808acc.png

ada8892b420b4ebc89d44d8d5485ce29.png

绑定bind

c4c721283d374ddf95eddc091e2fabe0.png
ba21e2d038d646c2ad598624e655f350.png

绑定端口号

sockaddr结构

3355b591bfbc4a36ae4e65a4883999d1.png

9f566ed4b506466d93ba183f949e7e0a.png
IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型, 16 位端口号和32位IP地址.IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址, 不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容.socket API可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好 处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

sockaddr 结构 

de0c98c98fdf4063a5893be71629af69.png

sockaddr_in 结构 

e610f6c82eff42288335ea95c0ed089f.png

 虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主 要有三部分信息: 地址类型, 端口号, IP地址.

in_addr结构 

012fc56492ba47a8845ceffec8cb05ff.png

 in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;

bzero把上面的结构体清空

5c80c4518d9f4ade955e831260500fbf.png

f051463c366842ca9deedd4019d2984b.png

INET

6b5b0f5524774deb843fc63d04ab5c15.png

addr,ip地址(32位的),port端口号,family所用的域或者家族(AF_INET)

c0525808bd5142f8855f07c36eb7734a.png

int,uint16_t,uint32_t的区别及其各自的用法

intuint16_t 和 uint32_t 都是表示整数类型的数据类型,它们在表示范围和用途上有所不同。下面是它们的区别和常见的用法:

1. int

  • 定义int 是C/C++中的标准整数类型,通常用于表示整数。它的大小和范围依赖于平台(操作系统和硬件)。
  • 大小和范围
    • 在大多数现代平台上,int 通常是 32 位的(4 字节),但是也有一些平台会使用 16 位(2 字节)或 64 位(8 字节)来表示。
    • 在 32 位系统上,int 的范围通常是:-2,147,483,648 到 2,147,483,647
    • 在 64 位系统上,int 的范围一般相同,但可能会有所不同,具体取决于编译器和操作系统。
  • 用途:适用于一般整数的存储,特别是当你不需要指定特定位数的整数时。通常是默认的整数类型。

2. uint16_t

  • 定义uint16_t 是一个固定宽度的无符号整数类型,表示一个 16 位(2 字节)的无符号整数。它是通过 C99 标准中的 <stdint.h> 头文件定义的。
  • 大小和范围
    • 它的大小是固定的 16 位(2 字节)。
    • 范围是 0 到 65535(即 2^16 - 1)。
  • 用途:用于需要 16 位无符号整数的场景,适用于存储比较小的正整数值,节省内存空间,常用于协议数据、传感器数据、颜色值等场景。

3. uint32_t

  • 定义uint32_t 是一个固定宽度的无符号整数类型,表示一个 32 位(4 字节)的无符号整数。它同样是通过 <stdint.h> 头文件定义的。
  • 大小和范围
    • 它的大小是固定的 32 位(4 字节)。
    • 范围是 0 到 4,294,967,295(即 2^32 - 1)。
  • 用途:用于需要 32 位无符号整数的场景,常用于表示较大范围的正整数,如文件大小、内存地址、网络协议等。

总结对比:

数据类型

大小

范围

用途

int

32 位(常见)

-2,147,483,648 到 2,147,483,647

通用整数类型,平台相关

uint16_t

16 位

0 到 65535

用于小范围无符号整数,如协议、传感器数据

uint32_t

32 位

0 到 4,294,967,295

用于大范围无符号整数,如文件大小、网络地址

选择依据:

  • 如果你知道需要存储的数值范围,并且希望节省内存,可以选择 uint16_t 或 uint32_t
  • 如果不关心存储大小且需要处理负数,int 是默认选择。
  • 需要无符号整数时,uint16_t 和 uint32_t 是更为合适的选择。

在网络中为什么用uint16_t,不用int

在网络通信中,通常使用 uint16_t 而不是 int 主要有以下几个原因:

1. 一致性和标准化

  • uint16_t 是一个 无符号 16 位整数,大小和范围是固定的,明确且可移植。无论在哪个平台或操作系统上,uint16_t<

Read more

OpenClaw 完整搭建指南:从零开始打造你的 AI 助手

OpenClaw 完整搭建指南:从零开始打造你的 AI 助手

OpenClaw 完整搭建指南:从零开始打造你的 AI 助手 本文基于实际部署经验,详细介绍 OpenClaw 的安装、配置 GitHub Copilot / Qwen 模型、接入钉钉、解决常见问题,以及搭建本地模型的完整流程。 目录 1. 什么是 OpenClaw 2. 环境准备与安装 3. 配置模型提供商 4. 接入钉钉机器人 5. 钉钉插件常见问题与解决方案 6. 日常使用技巧 7. 搭建本地模型(llama.cpp) 8. 总结与资源 一、什么是 OpenClaw OpenClaw 是一个开源的 AI 助手框架,可以: * 🤖 接入多种大模型(Claude、GPT、Qwen、本地模型等)

By Ne0inhk
LLM -Awesome OpenClaw Skills:给本地 AI 助手装一个「超级插件市场」

LLM -Awesome OpenClaw Skills:给本地 AI 助手装一个「超级插件市场」

文章目录 * 一、OpenClaw 是什么,它为什么需要「技能」 * 二、这个仓库到底包含什么 * 2.1 仓库定位:精挑细选的技能清单 * 2.2 技能协议:遵守 Anthropic 的 Agent Skill 规范 * 三、OpenClaw 技能怎么安装和使用 * 3.1 官方推荐:用 ClawHub CLI 一键安装 * 3.2 手动安装:适合喜欢掌控一切的开发者 * 3.3 最偷懒的方式:在对话里直接贴技能仓库链接 * 四、这个列表为什么存在:解决「技能过载」问题 * 4.1 ClawHub 的问题:量太大,但质量参差不齐

By Ne0inhk
医疗AI场景下算法编程的深度解析(2026新生培训讲稿)(一)

医疗AI场景下算法编程的深度解析(2026新生培训讲稿)(一)

前言 人工智能正在重塑医疗健康领域的每一个角落。从辅助医生解读医学影像,到为患者提供个性化的健康管理建议,再到优化医疗系统的运营效率,AI技术正以前所未有的深度和广度融入现代医学的肌体之中。 然而,技术的落地从来不是一帆风顺的。医疗AI面临着一系列独特的挑战:数据的高敏感性与隐私保护要求、模型决策的可解释性需求、临床场景中对准确率的严苛标准,以及日益复杂的法规监管环境。这些挑战要求从业者不仅掌握算法原理,更要理解医疗场景的特殊性,懂得如何在真实世界中构建可靠、安全、可落地的AI系统。 2026开学教程旨在为医疗AI领域的算法工程师、数据科学家、医工交叉研究人员提供一份从理论到实践的完整指南。我们将从医疗AI的发展脉络出发,深入解析k-近邻、逻辑回归、决策树、随机森林、支持向量机、Boosting等经典机器学习算法在医疗场景中的应用,并通过大量实战案例展示从数据处理到模型部署的全流程。 特别地,我们将医疗数据的特殊性贯穿全教程:小样本问题、类别不平衡、多模态融合、可解释性要求——这些在通用AI领域或许可以妥协的问题,在医疗领域必须直面并解决。 本书历时一年完成,感谢所有为本书贡

By Ne0inhk
人工智能:预训练语言模型与BERT实战应用

人工智能:预训练语言模型与BERT实战应用

人工智能:预训练语言模型与BERT实战应用 1.1 本章学习目标与重点 💡 学习目标:掌握预训练语言模型的核心思想、BERT模型的架构原理,以及基于BERT的文本分类任务实战流程。 💡 学习重点:理解BERT的双向注意力机制与掩码语言模型预训练任务,学会使用Hugging Face Transformers库调用BERT模型并完成微调。 1.2 预训练语言模型的发展历程与核心思想 1.2.1 为什么需要预训练语言模型 💡 传统的自然语言处理模型(如LSTM+词嵌入)存在两个核心痛点:一是需要大量标注数据才能训练出高性能模型,二是模型对语言上下文的理解能力有限。 预训练语言模型的出现解决了这些问题。它的核心思路是先在大规模无标注文本语料上进行预训练,学习通用的语言知识和语义表示,再针对特定任务进行微调。这种“预训练+微调”的范式,极大降低了对标注数据的依赖,同时显著提升了模型在各类NLP任务上的性能。 预训练语言模型的发展可以分为三个阶段: 1. 单向语言模型阶段:以ELMo为代表,通过双向LSTM分别学习正向和反向的语言表示,再拼接得到词向量。但ELMo本质还

By Ne0inhk