目录一.网络通信1.创建套接字1. domain协议域/地址族2. type套接字类型3. protocol协议2.绑定套接字返回值参数1: sockfd - 套接字描述符参数2: addr - 地址结构体指针1. sin_family地址族2. sin_port端口号3. sin_addrIP地址4. sin_zero填充可省略bzero参数3: addrlen - 地址结构体长度3.接收信息recvfrom()src_addr - 发送方地址重要addrlen - 地址结构体大小4.发送信息sendto()dest_addr - 目标地址关键参数addrlen - 地址结构体大小5.netstat一.网络通信1.创建套接字套接字是计算机网络编程中的核心概念可以理解为网络通信的端点或网络上的插座。通俗理解想象一下生活中的插座物理插座电器插入插座就能获得电力网络套接字程序插入套接字就能通过网络收发数据更准确的类比套接字就像电话机创建套接字 安装一部电话bind(绑定端口) 分配一个电话号码connect/accept 拨打电话/接听电话send/recv 通话交流close 挂断电话#include sys/socket.h int socket(int domain, int type, int protocol);成功返回一个非负整数套接字文件描述符失败返回 -1并设置errno1. domain协议域/地址族指定通信的协议族决定了地址格式常量说明地址格式示例AF_INETIPv4 互联网协议族192.168.1.1:8080AF_INET6IPv6 互联网协议族[2001:db8::1]:8080AF_UNIX/AF_LOCALUnix 域协议本地通信文件路径/tmp/socketAF_PACKET底层数据包接口需root权限网卡级别AF_BLUETOOTH蓝牙协议蓝牙地址2. type套接字类型指定通信语义常量说明特点SOCK_STREAM流式套接字TCP可靠、有序、面向连接、字节流SOCK_DGRAM数据报套接字UDP不可靠、无连接、数据报SOCK_RAW原始套接字直接访问IP层需rootSOCK_SEQPACKET顺序数据包套接字可靠、有序、面向连接、数据报边界SOCK_RDM可靠数据报可靠但可能乱序很少用3. protocol协议通常设为 0让系统自动选择协议值说明适用场景0自动选择99%的情况都用这个IPPROTO_TCP明确指定TCPtypeSOCK_STREAM时IPPROTO_UDP明确指定UDPtypeSOCK_DGRAM时IPPROTO_ICMPICMP协议原始套接字实现pingIPPROTO_RAW原始IP数据包自定义IP协议对于我们写Udp网络传输来说我么你创建套接字的参数是固定的。2.绑定套接字#include sys/socket.h int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);返回值成功返回 0失败返回 -1设置 errno角色是否需要 bind原因服务器端必须需要固定的地址和端口让客户端知道如何连接客户端可选系统会自动分配临时端口通常不需要手动 bind参数1: sockfd - 套接字描述符由socket()函数返回的文件描述符是一个非负整数代表已创建的套接字类似于文件操作的 fd但用于网络通信错误原因解决EBADFsockfd 无效如已关闭检查 socket 是否成功创建ENOTSOCKsockfd 不是 socket检查是否传错了文件描述符参数2: addr - 地址结构体指针struct sockaddr { sa_family_t sa_family; // 地址族如 AF_INET char sa_data[14]; // 地址数据IP端口 };注意实际编程中不使用这个结构只用于类型转换。通常使用IPV4来转化struct sockaddr_in { sa_family_t sin_family; // 地址族固定为 AF_INET in_port_t sin_port; // 端口号16位 struct in_addr sin_addr; // IPv4 地址32位 char sin_zero[8]; // 填充字节使结构大小相同 }; struct in_addr { in_addr_t s_addr; // 32位 IPv4 地址网络字节序 };1. sin_family地址族struct sockaddr_in addr; addr.sin_family AF_INET; // IPv4 // 其他可能值 // AF_INET6 - IPv6 // AF_UNIX - Unix域 // AF_PACKET - 数据链路层2. sin_port端口号范围0-1023系统保留端口需要 root 权限1024-49151注册端口普通用户可用49152-65535动态/私有端口// 设置端口必须转换到网络字节序 addr.sin_port htons(8080); // HTTP 备用端口 addr.sin_port htons(80); // HTTP 标准端口需要 root addr.sin_port htons(443); // HTTPS需要 root addr.sin_port htons(53); // DNS需要 root addr.sin_port htons(3306); // MySQL // 特殊用法端口为 0系统自动分配 addr.sin_port htons(0);3. sin_addrIP地址// 方式1绑定到所有网卡最常用 addr.sin_addr.s_addr INADDR_ANY; // 等同于 0.0.0.0 // 方式2绑定到本地回环仅本机访问 addr.sin_addr.s_addr inet_addr(127.0.0.1); // 或使用 inet_pton推荐支持 IPv6 inet_pton(AF_INET, 127.0.0.1, addr.sin_addr); // 方式3绑定到特定 IP inet_pton(AF_INET, 192.168.1.100, addr.sin_addr); // 方式4绑定到广播地址 addr.sin_addr.s_addr INADDR_BROADCAST; // 255.255.255.255 // 方式5使用宏 addr.sin_addr.s_addr htonl(INADDR_LOOPBACK); // 127.0.0.1 addr.sin_addr.s_addr htonl(INADDR_ANY); // 0.0.0.0#include arpa/inet.h int inet_pton(int af, const char *src, void *dst); #include sys/socket.h #include netinet/in.h #include arpa/inet.h int inet_aton(const char *cp, struct in_addr *inp); #include sys/socket.h #include netinet/in.h #include arpa/inet.h in_addr_t inet_addr(const char *cp);函数协议支持线程安全可重入推荐程度inet_ptonIPv4/IPv6✅ 安全✅ 是⭐⭐⭐⭐⭐ 最推荐inet_atonIPv4 only✅ 安全✅ 是⭐⭐⭐ 仅 IPv4inet_addrIPv4 only❌ 返回 INADDR_NONE 有歧义✅ 是⭐ 已废弃函数协议支持线程安全可重入推荐程度inet_ntopIPv4/IPv6✅ 安全✅ 是⭐⭐⭐⭐⭐ 最推荐inet_ntoaIPv4 only❌ 使用静态缓冲区❌ 否⭐ 已废弃#include arpa/inet.h const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);参数说明af地址族AF_INETIPv4AF_INET6IPv6src指向二进制地址的指针IPv4struct in_addr*IPv6struct in6_addr*dst输出缓冲区用于存储转换后的字符串size输出缓冲区的大小4. sin_zero填充可省略只是为了确保sockaddr_in和sockaddr大小相同通常用memset()清零现代编程中可以忽略但建议填充注意一般来说使用时会手动清0这时我们介绍一个清零结构体的函数方法bzerobzero是一个用于将内存区域清零的函数名字来源于byte zero字节置零。#include strings.h // 注意不是 string.h是 strings.h void bzero(void *s, size_t n);参数说明s指向要清零的内存区域的指针n要清零的字节数bzero(local,sizeof(local));参数3: addrlen - 地址结构体长度告诉内核地址结构体的实际大小防止缓冲区溢出。不同结构体的大小// 计算大小的方式 size_t len1 sizeof(struct sockaddr_in); // 16 字节 size_t len4 sizeof(struct sockaddr); // 16 字节 // bind 使用 bind(sockfd, (struct sockaddr*)addr, sizeof(addr));常见错误// 错误1长度不足 bind(sockfd, (struct sockaddr*)addr, 10); // 太小 // 错误2长度过大虽然不报错但没必要 bind(sockfd, (struct sockaddr*)addr, 1024); // 错误3使用错误的类型大小 struct sockaddr_in6 addr6; bind(sockfd, (struct sockaddr*)addr6, sizeof(struct sockaddr_in)); // 错误大小不匹配3.接收信息recvfrom()recvfrom()是 UDP 套接字编程中最核心的函数之一用于接收数据并且能够同时获取发送方的地址信息#include sys/types.h #include sys/socket.h ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);参数类型说明sockfdint套接字描述符由 socket() 返回bufvoid*接收数据的缓冲区指针lensize_t缓冲区大小最多接收多少字节flagsint接收选项通常设为 0src_addrstruct sockaddr*输出参数存储发送方的地址信息addrlensocklen_t*输入输出参数地址结构体的大小成功返回实际接收的字节数0 表示接收到空数据报失败返回 -1并设置 errnosrc_addr - 发送方地址重要这是 UDP 的关键特性UDP 是无连接的每次接收数据时都能知道是谁发来的。struct sockaddr_in client_addr; socklen_t addrlen sizeof(client_addr); recvfrom(sockfd, buffer, 1024, 0, (struct sockaddr*)client_addr, addrlen); // 现在可以知道是谁发的数据 char ip[INET_ADDRSTRLEN]; inet_ntop(AF_INET, client_addr.sin_addr, ip, sizeof(ip)); int port ntohs(client_addr.sin_port); printf(Received from %s:%d\n, ip, port);注意如果不关心发送方地址可以设为NULLrecvfrom(sockfd, buffer, 1024, 0, NULL, NULL);addrlen - 地址结构体大小这是一个输入输出参数struct sockaddr_in client_addr; socklen_t addrlen sizeof(client_addr); // 输入告诉内核结构体大小 recvfrom(sockfd, buffer, 1024, 0, (struct sockaddr*)client_addr, addrlen); // 调用后addrlen 会被设置为实际写入的地址长度 // 通常还是 sizeof(struct sockaddr_in)4.发送信息sendto()sendto()是 UDP 套接字编程中用于发送数据的核心函数与recvfrom()相对应。它允许你向指定的目标地址发送数据报。#include sys/types.h #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);参数类型说明sockfdint套接字描述符由 socket() 返回bufconst void*要发送的数据缓冲区指针lensize_t要发送的数据长度字节数flagsint发送选项通常设为 0dest_addrstruct sockaddr*目标地址信息接收方的 IP 和端口addrlensocklen_tdest_addr 结构体的大小成功返回实际发送的字节数通常等于len失败返回 -1并设置 errnodest_addr - 目标地址关键参数这是 UDP 的核心每次发送都要指定目标地址因为 UDP 是无连接的。struct sockaddr_in target_addr; memset(target_addr, 0, sizeof(target_addr)); target_addr.sin_family AF_INET; target_addr.sin_port htons(8080); // 目标端口 inet_pton(AF_INET, 192.168.1.100, target_addr.sin_addr); // 目标 IP sendto(sockfd, buffer, len, 0, (struct sockaddr*)target_addr, sizeof(target_addr));addrlen - 地址结构体大小// 必须是 dest_addr 指向的结构体的实际大小 sendto(sockfd, buffer, len, 0, (struct sockaddr*)target_addr, sizeof(target_addr));#pragma once #include iostream #include string #include memory #include cstring #include cerrno #include strings.h #include sys/types.h #include sys/socket.h // 提供 socket()、bind() 等函数 #include netinet/in.h // 提供 sockaddr_in、in_addr 等 #include arpa/inet.h // 提供 inet_pton()、inet_ntop() 等转换函数可选 #include Log.hpp #include Mutex.hpp using namespace LogMudule; #define Die(code) \ do \ { \ exit(code); \ } while (0) #define CONV(v) (struct sockaddr *)(v) // 将socketaddr_in转化为sockaddr const static int gsocket -1; const static uint16_t gport 8080; const static std::string gip 127.0.0.1; class UdpSever { public: UdpSever(uint16_t port gport, std::string ip gip) : _socket(gsocket), _ip(ip), _port(port), _isruing(false) { } void InintSever() { // 1.创建套接字 // 创建一个套接字第一个参数是选取网络协议流 // 第二个参数是采用Udp数据流传输 // 第三个是协议通常为0系统自己选择 _socket ::socket(AF_INET, SOCK_DGRAM, 0); // 创建失败使用日志报错并直接退出结束进程 if (_socket 0) { Log(LogLeval::FATAL) socket: strerror(errno); Die(1); } Log(LogLeval::INFO) socket success ,sockfd is: _socket; // 2.绑定套接字分配一个端口号相当于一个分配一个电话号码 struct sockaddr_in local; // 清零结构体 bzero(local, sizeof(local)); local.sin_family AF_INET; local.sin_addr.s_addr ::inet_addr(_ip.c_str()); local.sin_port htons(_port); // 传到网络中时需要区分大小端htons统一将端口改为大端 // local.sin_zero; int n ::bind(_socket, CONV(local), sizeof(local)); if (n 0) { Log(LogLeval::FATAL) bind: strerror(errno); Die(2); } Log(LogLeval::INFO) bind success; } void Start() { _isruing true; while (true) { sockaddr_in local; char buf[1024]; socklen_t len sizeof(local); int n recvfrom(_socket, buf, strlen(buf), 0, CONV(local), len); if (n 0) { buf[n] 0; Log(LogLeval::INFO) client say: buf strerror(errno); std::string sbufecho#; sbufbuf; int nsendto(_socket,buf,strlen(buf),0,CONV(local),len); } } } uint16_t GetPort() { return _port; } ~UdpSever() { } private: int _socket; uint16_t _port; // 端口号 std::string _ip; // ip bool _isruing; // 服务器是否正常运行 };这时我们一个简单的服务端就做好了不过我们现在没有客户端只能通过sheel来查看代码的正确性5.netstatnetstat(network statistics) 是一个用于显示网络连接、路由表、接口状态等信息的命令行工具netstat通过组合不同的参数来筛选和展示信息。类别选项说明显示类型-a(all)显示所有连接和监听端口。-l(listening)仅显示正在监听的端口服务端常用。协议筛选-t(tcp)仅显示TCP协议的连接。-u(udp)仅显示UDP协议的连接。信息展示-n(numeric)以数字形式显示IP和端口避免域名解析执行更快。-p(program)显示对应连接的进程IDPID和程序名需root权限获取完整信息。-e(extend)显示更多扩展信息。其他功能-r(route)显示路由表功能类似route命令。-i(interfaces)显示网络接口的流量统计如收发包数、错误数等。-s(statistics)按协议IP、TCP、UDP等显示详细的统计信息。-c(continuous)每隔一秒持续输出用于实时监控。