当前位置: 首页 > news >正文

【sylar-webserver】9 网络模块

目录

Address

类图

知识点

常用结构体

常用函数

Socket

类图

主要功能

初始化 socket 流程:

ByteArray

知识点

zigzag 算法

TLV 编码结构

Stream

类图

Stream 流结构,提供字节流读写接口

SocketStream

TcpServer

类图

主要功能


Address

类图

功能:

  1. sylar::Address::GetInterfaceAddresses 查询所有的网卡 / 指定网卡。通过 getifaddrs
  2. sylar::Address::Lookup 进行网络地址解析(包括字符串形式的域名/主机名或是数字格式的IP地址,支持端口和服务名解析)。
  3. sylar::Address::LookupAny 通过host地址返回对应条件的任意Address
  4. sylar::Address::LookupAnyIPAddress,通过host地址返回对应条件的任意IPAddress
  5. sylar::IPAddress::Create 通过域名,IP,服务器名创建IPAddress。通过 getaddrinfo 解析地址是否是 IPv4 / IPv6
  6. sylar::UnixAddress 创建 UnixAddress

知识点

常用结构体

  • ifaddrs
struct ifaddrs {struct ifaddrs  *ifa_next;    // 指向列表中下一个结构的指针。该字段在列表的最后一个结构中为 NULLchar            *ifa_name;   // 接口名称unsigned int     ifa_flags;   // 提供有关接口的一些信息的标志struct sockaddr *ifa_addr;   // 接口地址struct sockaddr *ifa_netmask; // 接口的网络掩码union {struct sockaddr *ifu_broadaddr;		// 接口广播地址struct sockaddr *ifu_dstaddr;	    // 点对点目的地址                 } ifa_ifu;#define              ifa_broadaddr ifa_ifu.ifu_broadaddr#define              ifa_dstaddr   ifa_ifu.ifu_dstaddrvoid            *ifa_data;    	// 特定地址族数据的缓冲区
};
  • sockaddr
struct sockaddr {ushort  sa_family;	 // 网络协议char    sa_data[14];
};
  • sockaddr_in
struct sockaddr_in {short            sin_family;   // 网络协议unsigned short   sin_port;     // 端口struct in_addr   sin_addr;     // ipchar             sin_zero[8];  
};struct in_addr {unsigned long s_addr;  // 使用 inet_aton() 加载
};
  • sockaddr_in6
struct sockaddr_in6 {sa_family_t     sin6_family;   /* 网络协议 */in_port_t       sin6_port;     /* 端口 */uint32_t        sin6_flowinfo; /* IPv6 流信息 */struct in6_addr sin6_addr;     /* ip */uint32_t        sin6_scope_id; 
};struct in6_addr {unsigned char   s6_addr[16];   /* IPv6 address */
};     

我们发现结构 sockaddr 和 sockaddr_in 字节数完全相同,都是16个字节,所以可以直接强转。
但是结构 sockaddr_in6 有28个字节,为什么在使用的时候也是直接将地址强制转化成(sockaddr*)类型呢?

这几个结构在作为参数时基本上都是以指针的形式传入的

我们拿函数bind()为例,这个函数一共接收三个参数:

第一个为监听的文件描述符。

第二个参数是sockaddr*类型。

第三个参数是传入指针原结的内存大小。

所以有了后两个信息,无所谓原结构怎么变化,因为他们的头都是一样的,也就是uint16 sa_family,那么我们也能根据这个头做处理。

  • addrinfo
struct addrinfo {int              ai_flags;     // 地址信息标志int              ai_family;    // 地址族(AF_INET, AF_INET6, AF_UNSPEC)int              ai_socktype;  // 套接字类型(SOCK_STREAM, SOCK_DGRAM)int              ai_protocol;  // 协议号(IPPROTO_TCP, IPPROTO_UDP),或0表示任意协议socklen_t        ai_addrlen;   // 地址长度struct sockaddr *ai_addr;      // 网络地址结构指针char            *ai_canonname; // 规范名字(主机名或服务名)struct addrinfo *ai_next;      // 指向下一个addrinfo结构的指针
};

通过调用getaddrinfo()函数可以填充并返回一个或多个addrinfo结构,其中包含了特定主机名和服务名对应的可用地址信息。

常用函数

  • getifaddrs / freeifaddrs

getifaddrs 是 C 语言中用于获取本地网络接口(如网卡、虚拟接口)信息的函数。它返回一个链表,其中每个节点包含接口的名称、IP 地址、子网掩码、广播地址等信息。与 getaddrinfo(用于解析远程主机地址)不同,getifaddrs 专注于 本地网络接口的配置。⭐

#include <sys/types.h>
#include <ifaddrs.h>int getifaddrs(struct ifaddrs **ifap);
/**
* func:函数存储对ifaddrs结构的链表的引用;
* return:成功返回0,失败返回-1;
*/void freeifaddrs(struct ifaddrs *ifa);
/**
* func:释放对ifaddrs结构的链表的引用;
*/

【注意】: 返回的数据是动态分配的,需要释放;

  • getaddrinfo / freeaddrinfo

getaddrinfo 是 C 语言中用于处理网络地址和服务的函数,能够将 主机名(如 example.com)和 服务名(如 http 或端口号 80)转换为套接字地址结构(如 struct sockaddr_insockaddr_in6)。它是网络编程中替代旧函数 gethostbynamegetservbyname 的现代方法,支持 IPv4/IPv6 和协议无关性。⭐

#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>int getaddrinfo(const char *node,         // 主机名或IP地址字符串(如 "example.com")const char *service,      // 服务名或端口号字符串(如 "80" 或 "http")const struct addrinfo *hints, // 输入提示(期望的地址类型)struct addrinfo **res     // 返回结果的链表
);void freeaddrinfo(struct addrinfo *res); // 释放内存
  • getnameinfo

getnameinfo 是 C 语言中用于将 套接字地址结构(如 struct sockaddr_insockaddr_in6)转换为 可读的主机名服务名 的函数。它是 getaddrinfo 的逆操作,通常用于将二进制网络地址(如 0x7F000001)转换为人类可读的字符串(如 "127.0.0.1""http")。支持 IPv4/IPv6 和协议无关性。

#include <sys/socket.h>
#include <netdb.h>int getnameinfo(const struct sockaddr *sa, socklen_t salen,char *host, size_t hostlen,char *serv, size_t servlen, int flags);
/**
* func:将套接字地址转换为相应的主机和服务并返回;
* @param sa:套接字地址结构,保存输入的IP地址和端口号;
* @param salen:sa的长度;
* @param host:调用者分配缓冲区;
* @param hostlen:host长度;
* @param serv:调用者分配缓冲区;
* @param servlen:serv长度
* @param flags:NI_NAMEREQD:若无法确定主机名,则返回一个错误;NI_DGRAM:基于数据报(UDP)而不是基于流(TCP)的;NI_NOFQDN:只返回本地主机的完全限定域名的主机名部分;NI_NUMERICHOST:返回主机名的数字形式;NI_NUMERICSERV:返回服务地址的数字形式;* return:成功返回0,【节点和服务名称将使用以空字符结尾的字符串填充】;失败设置errno:EAI_AGAIN:			无法解析该名称, 稍后再试;EAI_BADFLAGS:		flags参数的值无效;EAI_FAIL:			不可恢复的错误;EAI_FAMILY:		无法识别地址族,或指定地址族的地址长度无效EAI_MEMORY:		溢出;EAI_NONAME:		名称不能解析所提供的参数EAI_OVERFLOW:		溢出;EAI_SYSTEM:		系统错误;
*/

【注意】:调用者可以通过提供一个NULL host(或serv)参数或一个零hostlen(或servlen)参数来指定不需要主机名(或不需要服务名)。 但是,必须请求至少一个主机名或服务名。

Socket

类图

主要功能

  • 封装 socketfd,以及绑定的的本地地址和远端地址
  • 设置 SendTimeout,RecvTimeout
  • accept,bind,connect,reconnect,listen,close,send,sendTo,recv,recvFrom
  • getRemoteAddress,getLocalAddress
  • 取消 socketfd 绑定的 读事件 / 写事件 / accept(读事件)/ 全部事件。直接触发
  • Socket 延迟初始化,Socket()默认构造 m_sock = -1
    • accept,new Socket() --> ::accept(得到 socketfd) --> init --> initSock / getLocalAddress / getRemoteAddress
    • 初次 bind, newSock() ---> socket(得到 socketfd) ---> initSock / getLocalAddress / getRemoteAddress
    • 初次 connect,同 bind 相同操作

初始化 socket 流程:

sylar::Socket::CreateTCPSocket();  -->   Socket::ptr sock(new Socket(IPv4, TCP, 0));  --->  空的socket,m_sock = -1sylar::Address::LookupAnyIPAddress("www.baidu.com") ---> Address::Lookup  ---> getaddrinfosocket->connect(addr);	--->   延迟初始化 newSock()	---> 	m_sock = socket(m_family, m_type, m_protocol);|                	initSock();		---> 	setOption(SOL_SOCKET, SO_REUSEADDR, val);|                                            // 避免 TCP 粘包|                                            // 如果套接字类型为流式套接字(SOCK_STREAM),则禁用Nagle算法(TCP_NODELAY)v                                            if(m_type == SOCK_STREAM){ setOption(SOL_SOCKET, TCP_NODELAY, val); }( 两种 connect )::connect(m_sock, addr->getAddr(), addr->getAddrLen()                                                                          ::connect_with_timeout(m_sock, addr->getAddr(), addr->getAddrLen(), timeout_ms)     |         v// 客户端 connect 连接成功后,m_sock绑定了本地地址和远端地址getRemoteAddress();				---> 			getpeernamegetLocalAddress();				---> 			getsocknamesocket->send(buff, sizeof(buff));		---> 		::send(m_sock, buffer, length, flags);
socket->recv(&buf[0], buf.size());		---> 		::recv(m_sock, buffer, length, flags);

ByteArray

序列化 / 反序列化 操作

字节数组容器,提供基础类型的序列化与反序列化功能。

ByteArray的底层存储是固定大小的块,以链表形式组织。每次写入数据时,将数据写入到链表最后一个块中,如果最后一个块不足以容纳数据,则分配一个新的块并添加到链表结尾,再写入数据。ByteArray会记录当前的操作位置,每次写入数据时,该操作位置按写入大小往后偏移,如果要读取数据,则必须调用setPosition重新设置当前的操作位置。

ByteArray支持基础类型的序列化与反序列化功能,并且支持将序列化的结果写入文件,以及从文件中读取内容进行反序列化。ByteArray支持以下类型的序列化与反序列化:

  • 固定长度的有符号/无符号8位、16位、32位、64位整数
  • 不固定长度的有符号/无符号32位、64位整数
  • float、double类型
  • 字符串,包含字符串长度,长度范围支持16位、32位、64位。
  • 字符串,不包含长度。

以上所有的类型都支持读写。

ByteArray还支持设置序列化时的大小端顺序。

实际使用时,通常先通过 getWriteBuffers 获取到一段可写缓存,然后写入数据,手动 setPosition 设置实际的缓存偏移。

知识点

zigzag 算法

用于压缩较小的整数,参考:小而巧的数字压缩算法:zigzag_简单的老王-CSDN博客_zigzag编码

ByteArray在序列化不固定长度的有符号/无符号32位、64位整数时使用了zigzag算法。

简单来说,编码过程:负数变成 2 *(-a) - 1 ,转换为正奇数;正数变成 2 * a,转换为正偶数;解码过程,反操作即可。

TLV 编码结构

用于序列化和消息传递,指Tag(类型),Length(长度),Value(值),参考:TLV编码通信协议设计 - Tango 博客 | Tango Blog。

ByteArray在序列化字符串时使用TLV中的Length和Value。

Stream

类图

  • Stream 流结构,提供字节流读写接口

所有的流结构都继承自抽象类Stream,Stream类规定了一个流必须具备read/write接口和readFixSize/writeFixSize接口,继承自Stream的类必须实现read/write接口。Stream 里的 readFixSize/writeFixSize 通过调用重写的 read/write 实现。

SocketStream

对 Socket 进一步管理,使用通用接口 read / write 对 Socket 进行操作

HttpSession 见 HTTP 模块。HttpSession 代表 服务器 accept 的部分,HttpConnection 代表客户端 connect 的部分。

TcpServer

类图

主要功能

TcpServer 对 Socket,IOManager,进行了管理

支持对多个 Address 进行监听:

TcpServer::bind(addrs, fails)	--->	遍历addrs,根据addr的Family创建一个相同Family的TCP Socket |          vsock->bind(addr)		--->	Socket::newSock()初始化socket| vsock->listen()

m_acceptWorker,m_ioWorker 两个 iom,分别管理 accept 事件,accpet 后 client 的处理。

TcpServer::start()  --->   m_acceptWorker->schedule(std::bind(&TcpServer::startAccept, shared_from_this() , i));// m_acceptWorker 处理 所有 Socket 的 Accpet 操作,(读操作)
TcpServer::startAccept(Socket::ptr sock) ---> Socket::ptr client = sock->accept();	--->  m_ioWorker->schedule(std::bind(&TcpServer::handleClient, shared_from_this(), client));// m_ioWorker 处理 所有 accpet 后对 client 的操作,子类负责重写handleClient方法,实现不同操作。

这里的网络模块,比较简单,后续待优化⭐

相关文章:

  • git merge解冲突后,add、continue提交
  • 物联网、云计算技术加持,助推楼宇自控系统实现智能高效管理
  • Git实战演练,模拟日常使用,快速掌握命令
  • Vue 3.0中异步组件defineAsyncComponent
  • 前端 git仓库
  • 深入学习和对比Python 列表与元组
  • Win 系统 conda 如何配置镜像源
  • 为 Scade 6 编译器提供形式化认证工具的考虑 (2010)
  • LCI输出频率配置方法
  • Vue.js教学第十章:自定义命令的创建使用与应用
  • Android-RecyclerView学习总结
  • 新疆工程系列建筑专业职称评审条件
  • 流程引擎选型指南
  • zabbix 常见问题
  • 繁体字与简体中文转换
  • 基于springboot+vue的人口老龄化社区服务与管理平台(源码+数据库+文档)
  • 火语言UI组件--控件事件触发
  • 测试文章1
  • Keil5 MDK LPC1768 RT-Thread KSZ8041NL uIP1.3.1实现UDP网络通讯(服务端接收并发数据)
  • Unity基础学习(六)Mono中的重要内容(2)协同程序
  • 怎么外贸网站推广/营销策划
  • 上网站建设/游戏优化大师有用吗
  • 企业做网页还是网站/关键词seo优化排名公司
  • 如何自己做网页链接/东莞seo网络推广专
  • 深圳公司视频制作/大连seo建站
  • 网站美工切图是如何做的/使用最佳搜索引擎优化工具