(udp)网络编程套接字Linux(整理)
目录
addr,ip地址(32位的),port端口号,family所用的域或者家族(AF_INET)
int,uint16_t,uint32_t的区别及其各自的用法
0-1023不让绑定,最好绑定1024的端口,有的3306也不可以
服务器的端口号要确定是因为,客户端是要访问服务端的,需要知道确定的端口号和ip
源IP地址和目的IP地址
唐僧例子1
在IP数据包头部中, 有两个IP地址, 分别叫做源IP地址, 和目的IP地址.思考: 我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上,但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析.
认识端口号

理解 "端口号" 和 "进程ID"
我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?
10086例子
另外, 一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定;


理解源端口号和目的端口号
唐僧例子2
送快递例子
传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁";
IP:Port()
ip地址4字节 端口号两个字节
跨主机

看到的公共部分 是网络

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



认识TCP协议

认识UDP协议

网络字节序
ip地址4字节 端口号两个字节
我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出;接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存;因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址. TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节.不管这台主机是大端机还是小端机, 都会按照这个TCP/IP规定的网络字节序来发送/接收数据;如果当前发送主机是小端, 就需要先将数据转成大端; 否则就忽略, 直接发送即可;

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


网络字节序和主机字节序的转换
主机转网络
服务端

客户端

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

在 C 或 C++ 中,网络字节序和主机字节序的转换非常重要,特别是在进行网络编程时,因为不同的平台可能有不同的字节序(Endianness)。网络字节序通常是大端字节序(Big Endian),而主机字节序可能是大端或小端(Little Endian)。
网络字节序和主机字节序的转换函数
- **
htons()**:将主机字节序(Host)转换为网络字节序(Network),适用于 16 位数据(short)。 - **
htonl()**:将主机字节序(Host)转换为网络字节序(Network),适用于 32 位数据(long)。 - **
ntohs()**:将网络字节序(Network)转换为主机字节序(Host),适用于 16 位数据(short)。 - **
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.网络套接字编程

udp代码示例
udp服务器

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是面向字节流的,第三个参数不用填,协议类型,创建一个套接字的本质就是打开一个文件,必须要告诉服务器,端口号,然后再绑定套接字端口号

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

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

第三个写0就可以

#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;//网络文件描述符 };把日志功能拷贝进来

创建一个Log对象

std::unique_ptr


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 // 原理:简单粗暴 -- 防拷贝
测试:


绑定bind


绑定端口号
sockaddr结构


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 结构

sockaddr_in 结构

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

in_addr用来表示一个IPv4的IP地址. 其实就是一个32位的整数;
bzero把上面的结构体清空


INET

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

int,uint16_t,uint32_t的区别及其各自的用法
int、uint16_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 位无符号整数的场景,常用于表示较大范围的正整数,如文件大小、内存地址、网络协议等。
总结对比:
数据类型 | 大小 | 范围 | 用途 |
|---|---|---|---|
| 32 位(常见) |
| 通用整数类型,平台相关 |
| 16 位 |
| 用于小范围无符号整数,如协议、传感器数据 |
| 32 位 |
| 用于大范围无符号整数,如文件大小、网络地址 |
选择依据:
- 如果你知道需要存储的数值范围,并且希望节省内存,可以选择
uint16_t或uint32_t。 - 如果不关心存储大小且需要处理负数,
int是默认选择。 - 需要无符号整数时,
uint16_t和uint32_t是更为合适的选择。
在网络中为什么用uint16_t,不用int
在网络通信中,通常使用 uint16_t 而不是 int 主要有以下几个原因:
1. 一致性和标准化
uint16_t是一个 无符号 16 位整数,大小和范围是固定的,明确且可移植。无论在哪个平台或操作系统上,uint16_t<