Linux网络编程
网络基础
网络结构模式
C/S(Client - Server)服务器提供服务,客户端作为用户终端进行访问。缺点:更新时所有客户端都需要升级;针对不同的操作系统,需要不同的客户端
B/S(Browser - Server)web浏览器,统一了客户端,浏览器与服务器为请求-响应模式。缺点:通信开销变大;系统与数据的安全性较难保证。
MAC地址:物理地址,具有唯一性,类似身份证号。
IP地址:因特网协议地址。不同物理设备数据链路层的帧不同,ip使之统一为数据包格式。ip分为网络号和主机号,方便寻址,类似地址,划分省市县街道。ip用于标识主机。
子网掩码用于区分出网络号和主机号
端口:分为物理端口和虚拟端口,运输层通信使用的是虚拟端口。端口用于标识应用,实现应用间通信。虚拟端口分为周知端口,注册端口,私有(动态)端口
OSI模型:七层模型,精密的抽象将复杂的问题简单化。方便升级和维护。
TCP/IP四层模型
协议:网络协议是通信计算机双方必须共同遵从的一种约定。三要素是:语义,语法,时序。
应用层常见的协议有:FTP协议(File Transfer Protocol 文件传输协议)、HTTP协议(Hyper Text Transfer Protocol 超文本传输协议)、NFS(Network File System 网络文件系统)。
传输层常见协议有:TCP协议(Transmission Control Protocol 传输控制协议)、UDP协议(User Datagram Protocol 用户数据报协议)。
网络层常见协议有:IP 协议(Internet Protocol 因特网互联协议)、ICMP 协议(Internet Control Message Protocol 因特网控制报文协议)、IGMP 协议(Internet Group Management Protocol 因特 网组管理协议)。
网络接口层常见协议有:ARP协议(Address Resolution Protocol 地址解析协议)、RARP协议 (Reverse Address Resolution Protocol 反向地址解析协议)。
深入了解需要参考计算机网络相关课程及书籍
SOCKET通信基础
socket:IP+端口
字节序:大于一个字节的数据在内存中存放的顺序 小端:高位在高地址;大端:与小端相反
网络字节序为大端。
h - host 主机,主机字节序 to - 转换成什么 n - network 网络字节序 s - short unsigned short l - long unsigned int #include <arpa/inet.h> // 转换端口 uint16_t htons(uint16_t hostshort); // 主机字节序 - 网络字节序 uint16_t ntohs(uint16_t netshort); // 主机字节序 - 网络字节序 // 转IP uint32_t htonl(uint32_t hostlong); // 主机字节序 - 网络字节序 uint32_t ntohl(uint32_t netlong); // 主机字节序 - 网络字节序
socket地址:封装了ip和端口的结构体
TCP/IP协议族有TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4 和 IPv6。使用时要强转为socketaddr。
#include <netinet/in.h> struct sockaddr_in { sa_family_t sin_family; /* __SOCKADDR_COMMON(sin_) */ in_port_t sin_port; /* Port number. */ struct in_addr sin_addr; /* Internet address. */ /* Pad to size of `struct sockaddr'. */ unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)]; };
struct in_addr { in_addr_t s_addr; };
struct sockaddr_in6 { sa_family_t sin6_family; in_port_t sin6_port; /* Transport layer port # */ uint32_t sin6_flowinfo; /* IPv6 flow information */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* IPv6 scope-id */ };
typedef unsigned short uint16_t; typedef unsigned int uint32_t; typedef uint16_t in_port_t; typedef uint32_t in_addr_t; #define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
IP转换的API
#include <arpa/inet.h> // p:点分十进制的IP字符串,n:表示network,网络字节序的整数 int inet_pton(int af, const char *src, void *dst); af:地址族: AF_INET AF_INET6 src:需要转换的点分十进制的IP字符串 dst:转换后的结果保存在这个里面 // 将网络字节序的整数,转换成点分十进制的IP地址字符串 const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); af:地址族: AF_INET AF_INET6 src: 要转换的ip的整数的地址 dst: 转换成IP地址字符串保存的地方 size:第三个参数的大小(数组的大小) 返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
TCP,UDP
TCP
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略 int socket(int domain, int type, int protocol); - 功能:创建一个套接字 - 参数: - domain: 协议族 AF_INET : ipv4 AF_INET6 : ipv6 AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信) - type: 通信过程中使用的协议类型 SOCK_STREAM : 流式协议 SOCK_DGRAM : 报式协议 - protocol : 具体的一个协议。一般写0 - SOCK_STREAM : 流式协议默认使用 TCP - SOCK_DGRAM : 报式协议默认使用 UDP - 返回值: - 成功:返回文件描述符,操作的就是内核缓冲区。 - 失败:-1 int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命 名 - 功能:绑定,将fd 和本地的IP + 端口进行绑定 - 参数: - sockfd : 通过socket函数得到的文件描述符 - addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息 - addrlen : 第二个参数结构体占的内存大小 int listen(int sockfd, int backlog); // /proc/sys/net/core/somaxconn - 功能:监听这个socket上的连接 - 参数: - sockfd : 通过socket()函数得到的文件描述符 - backlog : 未连接的和已经连接的和的最大值, 5 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); - 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接 - 参数: - sockfd : 用于监听的文件描述符 - addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port) - addrlen : 指定第二个参数的对应的内存大小 - 返回值: - 成功 :用于通信的文件描述符 - -1 : 失败 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); - 功能: 客户端连接服务器 - 参数: - sockfd : 用于通信的文件描述符 - addr : 客户端要连接的服务器的地址信息 - addrlen : 第二个参数的内存大小 - 返回值:成功 0, 失败 -1 ssize_t write(int fd, const void *buf, size_t count); // 写数据 ssize_t read(int fd, void *buf, size_t count); // 读数据
三次握手:确保双向连接建立
四次挥手:双向的连接分别断开
滑动窗口:窗口为缓冲区的大小,可以告诉发送方还可以发送多少信息。用于实现流量控制,拥塞控制
并发:多进程或者多线程
tcp状态转换
半关闭:
#include <sys/socket.h> int shutdown(int sockfd, int how); sockfd: 需要关闭的socket的描述符 how: 允许为shutdown操作选择以下几种方式: SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。 该套接字不再接收数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。 SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发 出写操作。 SHUT_RDWR(2):关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以 SHUT_WR。
1. 如果有多个进程共享一个套接字,close 每被调用一次,计数减 1 ,直到计数为 0 时,也就是所用 进程都调用了 close,套接字将被释放。
2. 在多进程中如果一个进程调用了 shutdown(sfd, SHUT_RDWR) 后,其它的进程将无法进行通信。 但如果一个进程 close(sfd) 将不会影响到其它进程。
端口复用:防止服务器重启前绑定的端口没有释放;防止程序突然退出,系统没有释放端口
#include <sys/types.h> #include <sys/socket.h> // 设置套接字的属性(不仅仅能设置端口复用) int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen); 参数: - sockfd : 要操作的文件描述符 - level : 级别 - SOL_SOCKET (端口复用的级别) - optname : 选项的名称 - SO_REUSEADDR - SO_REUSEPORT - optval : 端口复用的值(整形) - 1 : 可以复用 - 0 : 不可以复用 - optlen : optval参数的大小 端口复用,设置的时机是在服务器绑定端口之前。 setsockopt(); bind();
查看端口信息:netstat -anp
UDP
#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); - 参数: - sockfd : 通信的fd - buf : 要发送的数据 - len : 发送数据的长度 - flags : 0 - dest_addr : 通信的另外一端的地址信息 - addrlen : 地址的内存大小 ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); - 参数: - sockfd : 通信的fd - buf : 接收数据的数组 - len : 数组的大小 - flags : 0 - src_addr : 用来保存另外一端的地址信息,不需要可以指定为NULL - addrlen : 地址的内存大小
广播
// 设置广播属性的函数 int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen); - sockfd : 文件描述符 - level : SOL_SOCKET - optname : SO_BROADCAST - optval : int类型的值,为1表示允许广播 - optlen : optval的大小
服务器发送,客户端bind并监听
组播
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen); // 服务器设置多播的信息,外出接口 - level : IPPROTO_IP - optname : IP_MULTICAST_IF - optval : struct in_addr // 客户端加入到多播组: - level : IPPROTO_IP - optname : IP_ADD_MEMBERSHIP - optval : struct ip_mreq struct ip_mreq { /* IP multicast address of group. */ struct in_addr imr_multiaddr; // 组播的IP地址 /* Local IP address of interface. */ struct in_addr imr_interface; // 本地的IP地址 };
typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; };
与广播相比,服务器需要设置组播,客户端需要加入多播组
本地套接字
用于实现本地的进程间通信
// 本地套接字通信的流程 - tcp // 服务器端 1. 创建监听的套接字 int lfd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的套接字文件 -> server端 struct sockaddr_un addr; // 绑定成功之后,指定的sun_path中的套接字文件会自动生成。 bind(lfd, addr, len);
3. 监听 listen(lfd, 100);
4. 等待并接受连接请求 struct sockaddr_un cliaddr; int cfd = accept(lfd, &cliaddr, len);
5. 通信 接收数据:read/recv 发送数据:write/send
6. 关闭连接 close();
// 客户端的流程 1. 创建通信的套接字 int fd = socket(AF_UNIX/AF_LOCAL, SOCK_STREAM, 0);
2. 监听的套接字绑定本地的IP 端口 struct sockaddr_un addr; // 绑定成功之后,指定的sun_path中的套接字文件会自动生成。 bind(lfd, addr, len);
3. 连接服务器 struct sockaddr_un serveraddr; connect(fd, &serveraddr, sizeof(serveraddr));
4. 通信 接收数据:read/recv 发送数据:write/send
5. 关闭连接 close();
// 头文件: sys/un.h #define UNIX_PATH_MAX 108 struct sockaddr_un { sa_family_t sun_family; // 地址族协议 af_local char sun_path[UNIX_PATH_MAX]; // 套接字文件的路径, 这是一个伪文件, 大小永远=0 };
IO多路复用
功能:使程序可以同时监听多个文件描述符,提高性能。
selec | poll | epoll | |
---|---|---|---|
数据结构 | bitmap | 数组 | 红黑树 |
最大连接数 | 1024 | 无上限 | 无上限 |
fd拷贝 | 每次调用selec拷贝 | 每次调用poll拷贝 | fd首次调用epoll_ctl拷贝,每次调用epoll_wait不拷贝 |
工作效率 | 轮询O:(n) | 轮询:O(n) | 回调:O(1) |
select
构造文件描述符表,通过bitmap,将位与文件描述符对应,由内核去监听。
// sizeof(fd_set) = 128 1024 #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); - 参数: - nfds : 委托内核检测的最大文件描述符的值 + 1 - readfds : 要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性 - 一般检测读操作 - 对应的是对方发送过来的数据,因为读是被动的接收数据,检测的就是读缓冲 区 - 是一个传入传出参数 - writefds : 要检测的文件描述符的写的集合,委托内核检测哪些文件描述符的写的属性 - 委托内核检测写缓冲区是不是还可以写数据(不满的就可以写) - exceptfds : 检测发生异常的文件描述符的集合 - timeout : 设置的超时时间 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ }; - NULL : 永久阻塞,直到检测到了文件描述符有变化 - tv_sec = 0 tv_usec = 0, 不阻塞 - tv_sec > 0 tv_usec > 0, 阻塞对应的时间 - 返回值 : - -1 : 失败 - >0(n) : 检测的集合中有n个文件描述符发生了变化 // 将参数文件描述符fd对应的标志位设置为0 void FD_CLR(int fd, fd_set *set); // 判断fd对应的标志位是0还是1, 返回值 : fd对应的标志位的值,0,返回0, 1,返回1 int FD_ISSET(int fd, fd_set *set); // 将参数文件描述符fd 对应的标志位,设置为1 void FD_SET(int fd, fd_set *set); // fd_set一共有1024 bit, 全部初始化为0 void FD_ZERO(fd_set *set);
poll
#include <poll.h>
struct pollfd { int fd; /* 委托内核检测的文件描述符 */ short events; /* 委托内核检测文件描述符的什么事件 */ short revents; /* 文件描述符实际发生的事件 */ };
struct pollfd myfd; myfd.fd = 5; myfd.events = POLLIN | POLLOUT;
int poll(struct pollfd *fds, nfds_t nfds, int timeout); - 参数: - fds : 是一个struct pollfd 结构体数组,这是一个需要检测的文件描述符的集合 - nfds : 这个是第一个参数数组中最后一个有效元素的下标 + 1 - timeout : 阻塞时长 0 : 不阻塞 -1 : 阻塞,当检测到需要检测的文件描述符有变化,解除阻塞 >0 : 阻塞的时长 - 返回值: -1 : 失败 >0(n) : 成功,n表示检测到集合中有n个文件描述符发生变化
epoll
#include <sys/epoll.h> // 创建一个新的epoll实例。在内核中创建了一个数据,这个数据中有两个比较重要的数据,一个是需要检 测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发送改变的文件描述符信息(双向 链表)。 int epoll_create(int size); - 参数: size : 目前没有意义了。随便写一个数,必须大于0 - 返回值: -1 : 失败 > 0 : 文件描述符,操作epoll实例的
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; 常见的Epoll检测事件: - EPOLLIN - EPOLLOUT - EPOLLERR // 对epoll实例进行管理:添加文件描述符信息,删除信息,修改信息 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); - 参数: - epfd : epoll实例对应的文件描述符 - op : 要进行什么操作 EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除 - fd : 要检测的文件描述符 - event : 检测文件描述符什么事情 // 检测函数 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); - 参数: - epfd : epoll实例对应的文件描述符 - events : 传出参数,保存了发送了变化的文件描述符的信息 - maxevents : 第二个参数结构体数组的大小 - timeout : 阻塞时间 - 0 : 不阻塞 - -1 : 阻塞,直到检测到fd数据发生变化,解除阻塞 - > 0 : 阻塞的时长(毫秒) - 返回值: - 成功,返回发送变化的文件描述符的个数 > 0 - 失败 -1
epoll的工作模式
LT(水平触发):每次都重复提醒
ET(边缘触发):不再重复提醒;需要设置非阻塞,防止某个文件描述符的读请求导致任务饿死
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)