1 2 3 4

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多路复用

功能:使程序可以同时监听多个文件描述符,提高性能。

 

selecpollepoll
数据结构 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(边缘触发):不再重复提醒;需要设置非阻塞,防止某个文件描述符的读请求导致任务饿死

posted @   木木木999  阅读(240)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示