Linux--ServerProgramming--(2)socket

news2024/11/24 22:53:52

1. 主机字节序和网络字节序

下面以32位机为前提:
	CPU累加器一次能装载至少 4 字节,即一个整数。

字节序分为:
	1.大端字节序(big endian)
		指一个整数的高位字节(23~32 bit )存储在内存的低地址处,低位字节(0~7 bit)存储在内存的高地址处。
	2.小端字节序(little endian)
		指整数的高位字节存储在内存的高地址处,低位字节存储在内存的低地址处。

现在大多PC采用小端字节序,因此小端字节序又称为主机字节序
大端字节序又称为网络字节序

1.1 检查字节序

#include <stdio.h>

void byteorder()
{
    union 
    {
        short value;
        char union_bytes[sizeof(short)];
    }test;

    test.value = 0x0102;

    if(test.union_bytes[0] == 1 && test.union_bytes[1] == 2)
    {
        printf("big endian.\n");
    }
    else if(  test.union_bytes[0] ==2 && test.union_bytes[1] == 1 )
    {
        printf("little endian.\n");
    }
    else
    {
        printf("unknow...\n");
    }
}

int main()
{
	byteorder();
	return 0;
}

1.2 字节序不统一的影响

若两台使用不同字节序的主机间传递格式化的数据时,接收端必然会错误解读。

1.3 统一字节序

解决字节序不统一的方法:
	发送端总是把要发送的数据转为 网络字节序(大端字节序)后再发送。
	接收端知道对方传送过来的数据是网络字节序(大端字节序),故接收端根据自身决定是否对接到的数据进行字节序转换。

1.4 Linux 字节序转换

Linux提供如下 4 个函数来完成主机字节序(小)和网络字节序(大)的转换:

#include <netinet/in.h>
unsigned long int htonl (unsigned long int hostlong);
unsigned short int htons (unsigned short int hostshort);
unsigned long int ntohl ( unsigned long int netlong );
unsigned short int ntohs (unsigned short int netshort);

//htonl : host to network long,长整型主机字节序数据转化为网络字节序数据。

//长整型函数(htonl、ntohl)常用来转换 IP 地址。
//短整型函数(htons 、ntohs )常用来转换 端口号。

2. socket 地址

2.1 通用 socket 地址(结构体)

注: 这一小节的 socket地址不常用

Linux通用 socket 地址结构体如下:


#include <bits/socket.h>
struct sockaddr
{
	sa_family_t sa_family;	//地址族类型  通常对应协议族类型。
	char sa_data[14];		//用于存放 socket 地址。
}
sa_family 成员是 地址协议族(sa_family_t)的变量。
地址协议族类型 通常与 协议族类型对应 
常见的协议族(protocol family,也称 domain)如下图:

在这里插入图片描述

sa_data 成员用于存放 socket 地址值。

在这里插入图片描述

2.2 专用 socket 地址(结构体)

**注:**这小节才是 实际编程常用的 socket 地址结构体。
通用不好用,所以就引申除了专用的。

2.2.1 UNIX 本地域协议族使用专用 socket 地址结构体

如下:

#include <sys/un.h>
struct sockaddr_un
{
	sa_family_t sin_family;	//地址族:AF_UNIX
	char sun_path[108]; 	//文件路径名
}

2.2.2 TCP/IP 协议族专用 socket 地址结构体

IPv4  专用结构体:
struct sockaddr_in
{
	sa_family_t sin_family;	//地址族:AF_INET
	u_int16_t sin_port;		//端口号,要用网络字节序表示
	struct in_addr sin_addr;//IPv4 地址结构体,见下面
}
struct in_addr
{
	u_int32_t s_addr;		//IPv4地址,用网络字节序表示(大端)
}


IPv6 专用结构体:
struct sockaddr_in6
{
	sa_family_t sin6_family;	//地址族:AF_INET6
	u_int16_t sin6_port;		//端口号,网络字节序表示(大端)
	u_int32_t sin6_flowinfo;	//流信息,应设置为 0
	struct in6_addr sin6_addr;	//IPv6 地址结构体,见下面
	u_int32_t sin6_scope_id;	//scope id,尚处于实验阶段
}
struct in6_addr
{
	unsigned char sa_addr[16];	//IPv6 地址,用网络字节序表示(大端)
}

所有专用 socket 地址(以及类型 sockaddr-storage)类型的变量在实际使用时都需要转化为 通用 socket 地址类型 sockaddr(强制转换即可)。

2.2.2.1 IP地址转换

专用地址结构体中有 IP 地址的使用。

通常,人们用好记的字符串表示IP,如
IPv4 地址: 点分十进制字符串表示
IPv6 地址:十六进制字符串 表示。

但 Linux网络编程的地址结构体(见上一小节)中使用整型类型表示 IPv4地址。
下面3 个函数 点分十进制 IPv4 地址和网络字节序整数 IPv4地址间的转换

#include <arpa/inet.h>
in_addr_t inet_addr(const char * strptr);			//将点分十进制字符串的IPv4地址转为网络字节序整数的 IPv4 地址。失败返回 INADDR_NONE. 
int inet_aton(const char * cp,struct in_addr * inp);//完成和 inet_addr 一样的功能,但是将转化结果存储于 inp 指向的地址结构中
char * inet_ntoa(struct in_addr in);				//将网络字节序整数表示的IPv4地址转换为点分十进制字符串表示的IPv4地址

注意: 
	inet_ntoa 内部 应用的是一个 静态变量存储转化结果,函数返回值指向该静态内存。 
	故inet_ntoa 是不可重入的。

在这里插入图片描述

下面 2 个函数可同时完成 IPv4 和 IPv6的 地址 字符串到 网络字节序类型的转换 :

#include <arpa/inet.h>
//src:点分十进制字符串IPv4或十六进制字符串IPv6地址
//dst:网络字节序整数的内存地址
//af 指定协议族。 AF_INET 、AF_INET6
//功能:将字符串表示的 IP 地址 src,转换为 网络字节序整数的 IP地址,并把转换结果存储到 dst 指向的内存中。
//成功返回 1。失败 0,并设置 errno.
int inet_pton(int af,const char * src,void *dst);	

//执行与inet_pton相反的转换。
//cnt:指定目标存储单元大小,下面两个宏帮助指定大小
//成功返回目标存储单元地址,失败 返回 NULL,并设置errno
const char* inet_ntop( int af,const void *src,char *dst,socklen_t cnt);


#include <netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

3. socket 相关

3.1 创建 socket

//服务端、客户端都要用到
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain,int type,int protocol);

domain: 使用哪个底层协议族
		对TCP/IP协议族该参数设置为:
			IPv4:PF_INET //(Protocol Family of Internet)
			IPv6:PF_INET6	
		对于 UNIX 本地协议族而言,该参数设置为:		
			PF_UNIX
		其他详见 man手册。
		
type:指定服务类型。
		服务类型有:
		1.流服务:SOCK_STREAM
			对应 TCP
		2.数据报服务:SOCK_UGRAM	
			对应 UDP
		扩展:
			Linux 内核版本2.6.17起,type参数可以接收上述服务类型与下面两个重要标志 相与 的值:
			1.SOCK_NONBLOCK
				将新建的 socket 设为非阻塞的
			2.SOCK_CLOEXEC
				用 fork 调用创建子进程时在子进程中关闭该 socket。
			Linux 内核版本2.6.17前,上诉两个重要标志需使用额外的系统调用。

protocol:
		在前两个参数构成的协议集合下,再选择一个具体的协议。
		这个值通常是唯一的(前两个参数已经完全决定了它的值)。
		一般我们把此值设为 0,表示使用默认协议。

成功返回 socket 文件描述符,失败返回 -1 并设置errno.

3.2 命名 (bind)socket

//用于服务端和客户端

#include <sys/types.h>
#include <sys/socket.h>
//将 myaddr 所指的地址绑定到创建的 sockfd 上。
//成功返回 0,失败返回-1 并设置 errno。
int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);

两种常见的 errno:
	1.EACCES
		绑定的地址是受保护地址,仅超级用户能够访问。
		如 普通用户绑定 socket到 知名服务端口(0~1023),即返回此报错。
	2.EADDRINUSE
		被绑定的地址正在使用。
		如将 socket 绑定到一个处于 TIME_WAIT状态的 socket 地址。

3.3 监听(listen)socket

//用于服务端
#inlcude <sys/socket.h>
int listen( int sockfd,int backlog);

sockfd:
	指定被监听的 socket。
backlog:
	提示内核监听队列的最大长度。典型值为 5.
	监听队列的长度如果超过 backlog,服务器将不受理新的客户连接,客户端也将收到 ECONNREFUSED 错误信息。
	扩展:
		内核版本2.2之前:
			backlog参数是指所有处于半连接状态(SYN_RCVD)和完全连接状态(ESTABLISHED)的 socket 上限。
		内核版本2.2之后:
			指处于完全连接状态的 socket 上线。
			处于半连接状态的 socket 上限则由 /proc/sys/net/ipv4/tcp_max_syn_backlog 内核参数定义。

成功时返回 0,失败返回 -1 并设置errno。

3.4 接收(accept)连接请求

//用于服务端

#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);

sockfd:
	执行过 listen 系统调用的监听 socket。
addr:
	用来获取被接受连接的远端 socket 地址。
addrlen:
	指定 addr 的长度。

成功;
	返回一个新的 socket,
	此socket唯一的标识了被接受的这个连接,服务器可通过读写该socket来与被接受连接对应的客户端通信。

失败:
	返回 -1 并设置errno。

扩展:
	accept只是从监听队列汇总取出连接,而不论连接处于何种状态,更不关心任何网络状况的变化。
	例:
		1.服务端开启监听
		2.客户端发起连接,然后 20s 后断开此连接,
		3.服务端 还是会在 accept 中拿到该客户端请求。
			

3.5 发起连接(connect)

//用于客户端
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);

serv_addr:
	服务器监听的 socket 地址
addrlen:
	指定这个地址的长度。

成功: 
	返回 0。
	一旦成功建立连接,sockfd就唯一标识了这个连接,客户端可以通过读写 sockfd 来与服务器通信。
失败:
	返回 -1,并设置 errno:
	常见 errno:
		1.ECONNREFUSED
			目标端口号不存在,连接被拒绝。
		2.ETIMEDOUT
			连接超时。	
		3.EINPROGRESS
			这个错误发生在对非阻塞的 socket 调用 connect,而连接又没有立即建立时。
			这种情况下,可以用 select、poll 等函数来监听这个连接失败的 socket 上的 可写事件。			
			当select、poll 等函数返回后,再利用 getsockopt 来读取错误码并清除该 socket 上的错误。
			如果错误码是 0,表示连接成功建立,否则 连接失败。

3.6 关闭连接(close、shutdown)

#include <unistd.h>
int close(int fd);

fd:
	待关闭的socket。

扩展:
	close 关闭只能同时关闭 socket 读写。
	close调用并不是立即关闭一个连接,而是将fd引用计数减 1。
	只有当 fd 引用计数为 0 时,才真正关闭连接。 
	例:
		对进程程序中,一次 fork 调用默认将使父进程中打开的 socket 引用计数加 1.
		故,必须在父进程和子进程中都对该 socket执行 close 才能将连接关闭。
立即终止连接,而不是和close一样将 socket 引用计数减 1.

#include <sys/socket.h>
int shutdown(int sockfd,int howto);

sockfd:
	待关闭的 socket。
howto:
	此取值决定 shutdown 行为。
	可取值如下图 5-3。
成功:
	0
失败:
	-1,并设置errno.

扩展:
	可指定关闭读或写。
	

在这里插入图片描述

3.7 TCP 数据读写

3.7.1 TCP 读

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd,void *buf,size_t len,int flags);

	sockfd:读该socket连接上的数据
	buf:指定读缓存区的位置
	len:指定读缓存区的长度,它可能小于我们期望的长度。因此可能需要多次执行 recv()才能读到完整数据。
	flags:详见后文
	返回:	0,意味对方已关闭连接。
			-1, 意味出错并设置errno。

详解常用 errno:

EAGAIN
		它的另一个名字叫做EWOULDBLOCK ,这两个宏定义在GNU的c库中永远是同一个值。
		大多数系统下是EWOULDBLOCK 和 EAGAIN 是同一个东西。
		对非阻塞socket而言,EAGAIN不是一种错误。
		下次循环接着recv即可。
		在VxWorks和Windows上,EAGAIN的名字叫做EWOULDBLOCK。
		例:
			如果使用fork创建进程,如果资源不足,也会返回EAGAIN。
			重新读即可,资源可用就会正常返回。

EINTR
		如果在读的过程中遇到了中断则read()应该返回-1,同时置errno为EINTR。
		

3.7.2 TCP 写

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd,const void *buf,size_t len,int flags);

	sockfd:往 该连接上 写数据。
	buf:指定写缓冲区的位置
	len:指定写缓冲区大小
	成功:返回实际写入数据长度
	失败:返回-1,并设置errno。


3.7.3 参数 flags详解

在这里插入图片描述

3.8 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);

	sockfd:recvfrom 读取 sockfd上的数据
	buf:指定读缓冲区的位置
	len:指定读缓冲区大小
	src_addr:因为UDP通信没有连接概念,故每次读取都需要获取发送端 socket 地址,即 src_addr 的内容。
	addrlen:指 src_addr 的长度
	flags:同上面TCP flags详解。

ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t * addrlen)

	sockfd:sendto 向 sockfd上写入数据
	buf:指定写缓冲区的位置
	len:指定写缓冲区大小
	dest_addr:因为UDP通信没有连接概念,故每次写入都需要指定接收端 socket 地址,即 dest_addr 的内容。
	addrlen:指 dest_addr的长度
	flags:同上面TCP flags详解。


扩展:
	recvfrom/sendto 系统调用也可以用于面向连接(STREAM)的socket数据读写,
	只需要把最后两个参数设置为 NULL 即可。

3.9 通用数据读写

socket 编程接口还提供不仅能用于TCP流数据,也可用于UDP数据报的系统调用:

#include <sys/socket.h>
ssize_t recvmsg(int sockfd,struct msghdr* msg,int flags);
ssize_t sendmsg(int sockfd,struct msghdr* msg,int flags);
参数详解:
sockfd:读写的socket连接
flags:同上面TCP send/recv 的flags 参数。

msghdr:
	struct msghdr
	{
		void * msg_name;		//socket地址
		socklen_t msg_namelen;	//socket地址长度
		struct iovec* msg_iov;	//分散内存块,详见下文
		int msg_iovlen;			//分散内存块数量
		void * msg_control;		//指向辅助数据的起始位置
		socklen_t msg_controllen;//辅助数据大小
		int msg_flags;			//复制函数中的 flags 参数,并在调用过程中更新
	}
	参数详解:
		msg_name:
				指向一个 socket 地址结构变量。
				指定通信对方的 socket 地址。对于TCP而言,必须设为NULL。
		msg_namelen:
				指定 msg_name 所指地址长度。
		msg_iov:
				iovec结构体定义如下,iovec结构体封装了一块内存的起始位置和长度:
					struct iovec
					{
						void * iov_base;	//内存起始地址
						size_t iov_len;		//这块内存长度
					}
		msg_iovlen:
				指定	iovec 这样的结构对象有多少个。	
		msg_control、msg_controllen:
				用于辅助数据的传送
		msg_flags:
				无需设定。
				它会复制 recvmsg/sendmsg 的 flags 参数内容。
				recvmsg 还会在调用结束前,将某些更新后的标志设置到 msg_flags 中。

3.10 带外数据(紧急数据)的读取

实际开发中无法预料带外数据何时到来。
Linux内核检测到 TCP紧急标志时,将通知应用程序有带外数据需接收。
内核通知应用程序带外数据到达两种常见方式:
	1.I/O复用产生的异常事件
	2.SIGURG 信号

应用程序得到信号后,还要知道带外数据在数据流中的位置,通过如下系统调用实现:
#include <sys/socket.h>
int sockarmark(int sockfd);

判断sockfd是否处于带外标记,即下一个被读取到的数据是否是带外数据,
若是,返回 1,此时可利用带 MSG_OOB标志的 recv调用来接收带外数据。
若不是,返回 0.

3.10 获取 socket 连接 地址信息

#include <sys/socket.h>
int getsockname(int sockfd,struct sockaddr * address,socklen_t * address_len);
	sockfd:获取sockfd对应的本端 scoket 地址,
	address:将获取到的地址存储于指向的内存中
	address_len:存储address地址长度。若实际socket地址长度大于address_len所指大小,那么 address 将被截断。
	成功:返回 0
	失败:返回 -1并设置 errno。
	
int getpeername(int sockfd,struct sockaddr * address,socklen_t * address_len);

	获取对端地址信息。 参数同上。

3.11 socket 选项详解

下面两个系统调用专门用来读取和设置 socket 文件描述符属性;

#include <sys/socket.h>
int getsockopt(int sockfd,int level,int option_name,void * option_value,socklen_t * restrict option_len);
int setsockopt(int sockfd,int level,int option_name,const void *option_value,socklen_t option_len);

上面两个函数参数和返回值含义基本一致,如下:	
	sockfd:
			指定被操作的目标 socket。
	level:
			指定要操作哪个协议的选项(即属性),如 IPv4、IPv6、TCP等。
	option_name:
			指定选项名。如下图 5-5 列举了常用的选项。
	option_value:
			被操作选项的值。不同的选项有不同的值,参照 图5-5 数据类型列。
	option_len:
			被操作选项的长度。
	成功返回 0
	失败返回-1,并设置errno。

在这里插入图片描述

扩展:
	对于服务器:
		1.部分 socket 选项只能在调用 listen 系统调用前针对监听 socket 设置才有效。
				因为连接 socket 只能由 accept 返回,
				而 accept 从 listen 监听队列中接受的连接至少已经完成了 TCP 三次握手的前两步
				(因为 listen 监听队列的连接至少进入了 SYN_RCVD状态),
				这说明服务器已经往被接受连接上发送了 TCP 同步报文段。
		2.有的 socket 选项却应该在 TCP 报文段中设置。
			如:
				TCP最大报文段。
				对这种情况Linux提供的解决方案:
					对监听 socket 设置这些 socket 选项,那么 accept 返回的连接 socket 自动继承这些选项。
					这种能继承的 socket 选项包括:图 5-5 画红√的选项。
	对于客户端:					
		这些 socket 选项应在调用 connect 之前设置。
				因为 connect 调用成功返回后,TCP三次握手已完成。

下面详解部分重要 socket 选项。

3.11.1 SO_REUSEADDR 选项

通过设置 socket 选项 SO_REUSEADDR 来强制使用处于 TIME_WAIT 状态的连接占用的 socket 地址。实现如下代码:

int sock = socket(PF_INET,SOCK_STREAM,0);
assert(sock >= 0);
int reuse = 1;
setsockopt(sock, SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));	//重点,注意此代码所处位置,创建socket后就绑定。

struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port = htons(port);
int ret = bind(sock,(struct sockaddr*)&address,sizeof(address));
扩展:
	也可以通过修改内核参数 /proc/sys/net/ipv4/tcp_tw_recycle 来快速回收被关闭的 socket,
	从而使TCP连接根本就不进入 TIME_WAIT 状态,进而允许应用程序立即重用本地 socket 地址。

3.11.2 SO_RCVBUF 和 SO_SNDBUF 选项

	SO_RCVBUF 选项:用来设置 TCP 接收缓冲区大小 
	SO_SNDBUF 选项:用来设置 TCP 发送缓冲区大小 
	
	当我们使用 setsockopt 设置如上两个选项时,系统都会将该值加倍,并且不小于某个最小值。
	系统自动设置不小于最小值的目的:
			主要是确保一个 TCP 连接拥有足够空闲缓冲区来处理拥塞(如快速重传算法期望TCP接收缓冲区至少容纳 4 个大小为SSMS的TCP报文段)。
	一般情况下(不同系统会有不同值):
			TCP 接收缓冲区最小值是 256字节,
			TCP 发送缓冲区最小值是 2048字节。

扩展:
	可通过修改内核参数来强制TCP接收缓冲区和发送缓冲区大小没有限制:
		/proc/sys/net/ipv4/tcp_rmem
		/proc/sys/net/ipv4/tcp_wmenm

示例如下图:	

在这里插入图片描述

3.11.3 SO_RCVLOWAT 和 SO_SNDLOWAT 选项

SO_RCVLOWAT  选项:用来表示 TCP 接收缓冲区的低水位标记
SO_SNDLOWAT 选项: 用来表示 TCP 发送缓冲区的低水位标记

一般被 I/O 复用系统调用用来判断 socket 是否可读或可写。
	1.当TCP接收缓冲区可读数据的总数 大于 其低水位标记时,I/O复用系统调用将通知 应用程序可以从对应的 socket 上读取数据。
	2.当TCP接收缓冲区可写入数据的空间 大于 其低水位标记时,I/O复用系统调用将通知 应用程序可以向对应的 socket 上写入数据。

默认情况下 TCP 接收缓冲区低水位标记 和 TCP 发送缓冲区低水位标记 均为 1 字节。

3.11.4 SO_LINGER 选项

此选项 控制 close 系统调用在 关闭 TCP 连接时的行为。
	1.默认情况,
		当利用 close 系统调用关闭一个 socket 时,close 将立即返回,TCP 模块负责将该 socket 对应的 TCP 发送缓冲区中残留的数据发送给对方。
	
	2.设置 SO_LINGER 选项,会产生三种行为
		设置该选项时,需给 setsockopt(getsockopt)系统调用传递一个 linger 类型结构体(详见下面)
		根据 linger 结构体 成员变量不同的值,会产生如下 3 种行为:
		1.	l_onoff 等于 0.
				此时 SO_LINGER选项不起作用,close使用默认行为关闭 socket。
		2.	l_onoff 不为 0 ,同时 l_linger 等于 0.
				此时 close 系统调用立即返回,
				TCP模块将丢弃被关闭的 socket 对应的 TCP 缓冲区的残余数据,同时给对方发送一个 复位报文段。
		3.	l_onoff 不为 0,同时 l_linger 大于 0.
				此时	 close 行为取决于两个条件:
				1.	被关闭的 socket 对应的 TCP 发送缓冲区中是否还有残留的数据
				2.	该 socket 是阻塞的,还是非阻塞的。
						①对于阻塞:
							close 将等待一段长为 l_linger 的时间,直到 TCP 模块发送完所有残余数据并得到对方确认。
							若 l_linger 时间内 TCP模块没有发送完残留数据并得到对方确认,那么 close 将返回 -1 并设置 errno 为 EWOULDBLOCK。
						②对于非阻塞:
							close 将立即返回,此时根据其返回值和 errno 来判断残留数据是否已发送完毕。
#include <sys/socket.h>
struct linger
{
	int l_onoff;	//开启:非0,关闭: 0
	int l_linger;	//滞留时间
}

3.12 socket 网络信息 API

3.12.1 根据主机名 或 IP获取主机信息

gethostbyname	:	
		根据主机名获取主机完整信息 
		查找流程:
			1.通产先在本地 /etc/hosts 配置文件中查找主机,若没有找到再到 DNS 服务器
gethostbyaddr :
		根据 IP 获取主机完整信息
注:这两个函数是不可重入的,即非线程安全的,
	Linux还给出了线程安全版本的相应函数,即 原函数名后 加 _r(re-entrant)
	
#include <netdb.h>
struct hostent * gethostbyname(const char * name);
struct hostent * gethostbyaddr(const void * addr,size_t len,int type);

	name:
		指定主机名
	addr:
		指定目标主机IP
	len:
		指定 addr 所指 IP 地址长度。
	type:
		指定 addr 所指 IP地址类型
		取值示例:
			IPv4:AF_INET
			IPv6:AF_INET6
	
hosent 结构体定义:
#include <netdb.h>
struct hostent
{
	char * h_name;			//主机名
	char ** h_aliases;		//主机别名列表,可能有多个
	int h_addrtype;			//地址类型(地址族)
	int h_length;			//地址长度
	char ** h_addr_list;	//网络字节序(大端)列出的主机 IP 地址列表
}

3.12.2 根据名称 或 端口号获取某个服务完整信息

getservbyname:
		根据名称获取某个服务完整信息
getservbyport:
		根据端口号获取某个服务完整信息

实际上两者都是通过读取 /etc/services 文件来获取服务信息的。
注:这两个函数是不可重入的,即非线程安全的,
	Linux还给出了线程安全版本的相应函数,即 原函数名后 加 _r(re-entrant)
	
#include <netdb.h>
struct servent * getservbyname(const char * name,const char * proto);
struct servent * getservbyport(int port,const char * proto);

	name:
		指定目标服务名。
	port;
		指定目标服务端口号
	proto:
		指定服务类型。
			传递 tcp 表示获取流服务
			传递 udp 表示获取数据服务,
			传递 NULL 表示获取全部服务。


servent结构体定义如下:
#include <netdb.h>
struct servent
{
	char * s_name;		//服务名称
	char ** s_aliases;	//服务别名列表,可能有多个
	int s_port;			//服务端口号
	char * s_proto;		//服务类型,通常为 tcp 或 udp
}

3.12.3 getaddrinfo

getaddrinfo 函数
	即可通过主机名获得 IP(内部使用gethostbyname函数),
	亦可通过服务名获得端口号(内部使用getservbyname函数)。
#include <netdb.h>
int getaddrinfo(const char * hostname,const char * service,const struct addrinfo * hints,struct addrinfo ** result);
	
	hostname:
			1.可以接收主机名
			2.也可以接收 字符串表示的IP地址(点分十进制IPv4,16进制字符串IPv6)
	service:
			1.可以接收服务名
			2.也可接收字符串表示的十进制端口号
	hints:
			是应用程序给 getaddrinfo 的一个提示,以对输出进行更精确控制。
			可以设置为 NULL,表示允许 getaddrinfo 反馈任何可用结果。
	result:
			指向一个链表,该链表存储 getaddrinfo 返回结果。
	成功:
			返回 0
	失败:
			 返回错误码,错误码列表如下表 5-8 所示。	

addrinfo结构体定义如下:
struct addrinfo
{
	int ai_flags;				//可以取 表 5-6 中的标志 按位或
	int ai_family;				//地址族
	int ai_socktype;			//服务类型, SOCK_STREAM 或 SOCK_DGRAM
	int ai_protocol;			//指具体的网络协议,其含义好socket系统调用的第三个参数相同,通常默认设置为 0
	socklen_t ai_addrlen;		//socket 地址 ai_addr 的长度
	char * ai_canonname;		//主机别名
	struct sockaddr * ai_addr;	//指向 socket 地址
	struct addrinfo * ai_next;	//指向下一个 sockinfo 结构对象
}

注意:
	当使用 hits参数时,可以设置 ai_flags、 ai_family、ai_socktype、ai_protocol四个字段,
	其它字段必须设置为 NULL

在这里插入图片描述
例子:
在这里插入图片描述

3.12.4 getnameinfo

getnameinfo 能通过 socket 地址同时获得 以字符串表示的主机名(内部使用 gethostbyaddr 函数)和 服务名(内部使用 getservbyport 函数)。
#include <netdb.h>
int getnameinfo(const struct sockaddr* sockaddr,socklen_t addrlen,char * host,socklen_t hostlen,char * serv,socklen_t servlen,int flags);

	host:
			将返回的主机名存储在 host 指向的内存中。
	hostlen:
			指定 host 指向的内存 长度
	serv:
			将返回的服务名存储在 serv 指向的内存中。
	servlen:
			指定 serv 指向的内存 长度
	flags:
			控制 getnameinfo 的行为,接收下表 5-7 中的选项。
	
	成功:
		返回 0
	失败 返回错误码,错误码列表如下表 5-8 所示。	

在这里插入图片描述
在这里插入图片描述
使用如下函数将数值错误码 errno 转换为可读的字符串形式:

#include <netdb.h>
const char * gai_strerror(int error);

4.高级I/O函数

4.1 创建文件描述符的函数

4.1.1 pipe 函数

用于创建一个管道,以实现进程间通信
#include <unistd.h>
int pipe(int fd[2]);

参数:
	两个 int 型整数指针。

返回值:
	成功:
		 返回 0.并将一对打开的文件描述符填入其参数指向的数组
	失败:
		返回 -1,并设置errno。

详解:
	pipe 函数创建的两个文件描述符 fd[0] 和 fd[1]分别构成管道两端,
	管道内传输的是 字节流,和TCP 字节流有差异:
		大小区别:
			TCP连接:
				往 TCP连接 写入数据多少取决于 对方接收通告窗口大小和本端的拥塞窗口大小。
			管道:
				管道 本身有一个容量限制,它规定如果应用程序不将数据从管道读走的话,
				该管道最多能被写入多少字节数据。
				Linux 2.6.11内核起,管道最大容量默认为 65536 字节,可以使用 fcntl 函数来修改容量。
			 
	假设 fd[0] 为 读端,fd[1] 为 写端:
		读端只能读,写端只能写。故fd[0]只能读, fd[1] 只能写,不能反过来用。
		若要双向传输,应该使用两个管道。
	
	默认情况下,这一对描述符都是阻塞的:
		例:
			若调用 write 往一个满的管道中写入数据,则 write 将被阻塞,直到管道有足够多的空闲空间可用。
	
	若设置 创建的两个文件描述符 fd[0] 和 fd[1] 都为非阻塞的,此时 read 和 write 会有不同行为。
	
	如果管道 写端 文件描述符的引用计数减少至 0,即没有任何数据往管道中写,
	-> 则相应的读端的 read 操作将返回 0,即读到了文件结束标记 (EOF,End Of File).
	如果管道 读端 文件描述符引用计数减少至 0,即没有任何数据需要从管道读取,
	-> 则 相应的 管道 写端的 write 操作将失败,并引发 SIGPIPE 信号。

	

扩展:

创建 双向管道函数:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain,int type,int protocol,int fd[2]);

	参数:
		1.	前三个参数和socket系统调用参数完全相同,
			但 domain 只能使用 UNIX 本地协议族 AF_UNIX.
		2.	fd[2]参数含义和 pipe相同,
			但区别于 pipe 参数的是这对描述符都是 既可读又可写的。
	返回:
		成功 0
		失败 -1 并设置 errno。
	
	注意:
		仅能在本地使用这个 双向管道。
		


4.1.2 dup 和 dup2 函数

在这里插入图片描述

#include <unistd.h>
int dup(int  file_descriptor);
	创建一个新的文件描述符,该文件描述符和原有文件描述符 file_descriptor 指向相同的文件、管道或网络连接,
	返回:
		返回的文件描述符总是取系统当前可用的最小整数值。
		失败 -1 并设置 errno。
	
	
int dup2(int file_descriptor_one , int file_descriptor_two);
	
	返回:
		返回的文件描述符为 第一个不小于 file_descriptor_two 的整数值。
		失败 -1 并设置 errno。

在这里插入图片描述
例:
在这里插入图片描述

4.1.3 readv 和 writev 函数

readv:
	将数据从文件描述符读到分散的内存块中
writev:
	将多块分散内存数据一并写入文件描述符中

相当于简化版的 recvmsg 和 sendmsg函数。
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec* vector,int count);
ssize_t writev(int fd, const struct iovec* vector,int count);

参数:
	fd:
		备操作的文件描述符。
	iovec:
		此结构体描述一块内存。 详见下文。
	count:
		是vector数组长度,即有多少块内存数据从fd读出或写入。
	返回:
		成功 返回读出/写入 fd 的字节数。
		失败 返回 -1,并设置 errno。
	
	


iovec结构体定义如下,iovec结构体封装了一块内存的起始位置和长度:
		struct iovec
		{
			void * iov_base;	//内存起始地址
			size_t iov_len;		//这块内存长度
		}

在这里插入图片描述

4.1.4 sendfile 函数

两个文件描述符间传递数据(完全在内核中操作),从而避免了内核缓冲区和用户缓冲区间的数据拷贝,这被称为零拷贝

sendfile 函数:
	是零拷贝。
#include <sys/sendfile.h>
ssize_t sendfile(int out_fd, int in_fd, off_t * offset,size_t count);
	
	out_fd:
			待写入内容的文件描述符。
			注意,此参数必须是一个 socket。
	in_fd:
			待读出内容的文件描述符。
			注意:此参数必须是指向真实的文件,不能是socket和管道。
	offset:
			指定从读入文件流(in_fd)的哪个位置开始读。
			若为空,使用读入文件默认的起始位置。
	count:
			指定in_fd 和 out_fd 间传输的字节数。
	返回:
			成功: 
				返回传输的字节数。
			失败:
				-1 并设置 errno。
	
由上可知,sendfile几乎是专门为网络上传输文件而设计的。

例:在这里插入图片描述

4.1.5 mmap 函数 和 munmap 函数

mmap 函数用于申请一段内存空间,
munmap 用于释放这段内存空间。

这段内存的作用;
	1.进程间通信的共享内存。
	2.将文件直接映射到其中。
#include <sys/mman.h>
void * mmap(void * start,size_t length,int prot, int flags, int fd, off_t offset);
int mummap(void * start, size_t length);
	
	start:
			允许用户使用某个特定的地址作为这段内存的起始地址。
			若被设为 NULL,则系统自动分配一个地址。
	length:
			指定 start指向内存段的长度。
	prot:
			设置 start指向内存段访问权限。
			可以取以下几个值得按位或:
				PROT_READ:	内存段可读。
				PROT_WRITE:	内存段可写。
				PROT_EXEC:	内存段可执行。
				PROT_NONE:	内存段不能被访问。
	flags:
			控制内存段内容被修改后程序的行为。
			可以被设置为 如下图6-1 (只有常用的,不是全部)中的某些值的 按位或。
			注意(MAP_SHARED 和 MAP_PRIVATE 是互斥的,不能同时指定。)
	fd:
			被映射文件对应的文件描述符。
			一般通过 open 系统调用获得。
	offset:
			设置从文件的何处开始映射(对于不需要读入整个文件的情况)
	
	返回:
			成功:
				返回指向目标内存区域的指针
			失败:
				返回 -1,并设置errno。

在这里插入图片描述

4.1.6 splice 函数

splice 函数 
	用于在两个文件描述符间移动数据。
	是零拷贝操作。
注意:
	使用此函数时,fd_in 或 fd_out 必须有一个是管道文件描述符。

#include <fcntl.h>
ssize_t splice(int fd_in, loff_t * off_in, int fd_out, loff_t * off_out, size_t len, unsigned int flags);

	fd_in:
			待输入数据的文件描述符。
	off_in:
			若 fd_in 是一个管道文件描述符:
				off_in 必须设置为 NULL
			若 fd_in 不是一个管道文件描述符:
				如 是一个 socket,此时 off_in 表示从输入数据流的何处开始读取数据。
				此时:
					若 off_in 设置为 NULL,表示从输入流当前位置读入
					若 off_in 设置不为 NULL,将指出具体偏移位置。
	fd_out:
			输出数据的文件描述符。
	off_out(同 off_in,但表示 输出流):
			若 fd_out 是一个管道文件描述符:
				off_out 必须设置为 NULL
			若 fd_out 不是一个管道文件描述符:
				如 是一个 socket,此时 off_out 表示从输入数据流的何处开始读取数据。
				此时:
					若 off_out 设置为 NULL,表示从输入流当前位置读入
					若 off_out 设置不为 NULL,将指出具体偏移位置。
	len:
			指定移动数据长度。
	flags:
			控制数据如何移动。
			可以被设置为 表 6-2 中某些值的 按位或。
	返回:
			成功:
				返回移动字节数量。
				返回 0,代表没有数据移动。
			失败:
				-1,并设置errno。
				常见 errno 见表 6-3.

在这里插入图片描述
在这里插入图片描述
例:
在这里插入图片描述

4.1.7 tee 函数

在两个管道文件描述符之间复制数据。
是零拷贝。
源文件描述符上的数据仍然可以用于后续 读操作。
#include <fcntl.h>
ssize_t tee(int fd_in ,int fd_out,size_t len, unsigned int flags);

	fd_in :
			必须是管道文件描述符。
	fd_out:
			必须是管道文件描述符。
	len:
			指定移动数据长度。
	flags:
			控制数据如何移动。
			可以被设置为 表 6-2 中某些值的 按位或。
	返回:
			成功:
				返回两个文件描述符间复制的数据数量(字节数)。
				0,表示没有复制任何数据。
			失败:
				-1,并设置 errno。

在这里插入图片描述
例:
在这里插入图片描述

4.1.8 fcntl 函数

fcntl : file control缩写。
提供对文件描述符的各种控制操作。
#include <fcntl.h>
int fcntl(int fd, int cmd,	...);
	
	fd:
			被操作的文件描述符。
	cmd:
			指定执行何种类型的操作。
			根据此类型的不同,可能还需要第三个可选参数表。
			支持的类型,详见 表 6-4.
	...:
			根据 cmd 类型 才回存在此参数。
	返回:
			成功:
				详见 表 6-4 最后一列。
			失败:
				-1,并设置 errno。

在这里插入图片描述
例:
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/614741.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Nat Biotechnol -- 生成式AI进军更高效价抗体

类似于ChatGPT的语言模型已被应用于改进针对COVID-19、埃博拉和其他病毒的抗体疗法。 代码看不懂&#xff1f;ChatGPT 帮你解释&#xff0c;详细到爆&#xff01; 单克隆抗体&#xff08;Y形&#xff09;与SARS-CoV-2病毒纤突蛋白&#xff08;红色&#xff09;上的结合位点&…

长光程气体吸收池的真空压力精密控制解决方案

摘要&#xff1a;目前用于气体吸收池真空压力控制的压力控制器存在有残留气体和无法进行高真空测量的问题&#xff0c;无法进行微量气体的光谱分析。为此&#xff0c;本文提出了动态平衡法的解决方案&#xff0c;即采用两个高速真空低漏率的电子针阀分别调节进气和出气流量&…

02_类加载子系统

目录 1、Jvm内存结构概述二、类加载器与类的加载过程1、类加载器子系统的作用2、类的加载过程 三、类加载器的分类1、启动类加载器2、扩展类加载器3、应用程序类加载器4、用户自定义加载器5、获取ClassLoader的几种方式 五、双亲委派机制1、什么是双亲委派机制2、双亲委派机制的…

Windows上SVN迁移至Linux

1.从windows导出svn文件 bat脚本 echo offsvnadmin dump E:\Repositories\3goodsoft_carbon_admin > D:/test/3goodsoft_carbon_admin.dump svnadmin dump E:\Repositories\3goodsoft_android > D:/test/3goodsoft_android.dump svnadmin dump E:\Repositories\3g…

chatgpt赋能python:Python如何在指定目录下创建文件

Python如何在指定目录下创建文件 Python是一种流行的编程语言&#xff0c;因为它易于学习、易于使用和非常灵活。其中一个常见的任务是在指定目录下创建文件&#xff0c;这在编写应用程序或脚本时经常需要。在本文中&#xff0c;我们将介绍使用Python在指定目录下创建文件的方…

第05章 数组

一 数组的概述 1.1 为什么需要数组 需求分析1&#xff1a; 需要统计某公司50个员工的工资情况&#xff0c;例如计算平均工资、找到最高工资等。用之前知识&#xff0c;首先需要声明50个变量来分别记录每位员工的工资&#xff0c;这样会很麻烦。因此我们可以将所有的数据全部…

chatgpt赋能python:Python如何取消空格

Python如何取消空格 在Python编程中&#xff0c;取消字符串中的空格是一个常见的需求。特别是在进行字符串处理和数据清洗时&#xff0c;取消空格可以方便数据的分析和处理。在本次文章中&#xff0c;我们将介绍使用Python语言如何取消字符串中的空格&#xff0c;以及一些常见…

如何将会议录音转文字?你知道如何将会议录音转文字吗?

会议录音转文字的需求在现代工作和生活中变得越来越重要。随着会议的频繁举行&#xff0c;我们常常需要记录会议内容以便后续查阅和分析。而传统的手动记录方式效率低下且容易出错。幸运的是&#xff0c;现在有许多会议录音转文字的应用程序可供选择&#xff0c;它们可以将会议…

uniPush2.0踩坑实录

首先&#xff0c;按照下面链接&#xff0c;把预备工作做完&#xff0c;基本可以实现dcloud后台网页推送&#xff1a; uniPush2.0 消息推送_没有白天的CXY的博客-CSDN博客 第二步&#xff0c;走完本流程后&#xff0c;会遇到各种坑&#xff0c;一个一个来踩&#xff1a; 第一坑…

龙芯2K1000实战开发-处理器硬件接口测试(下)

文章目录 概要整体架构流程技术名词解释技术细节小结概要 提示:这里可以添加技术概要 龙芯 2K 集成了 USB、SATA、PCIE 等高速串行接口,在板级测试时需要按照相应的 规范发出测试波形。本文档对这些接口分别介绍了发出测试波形的方法 整体架构流程 提示:这里可以添加技术…

电子企业如何快速部署一套MES生产管理系统

电子企业如何快速部署一套MES生产管理系统 引言 在数字化时代&#xff0c;电子企业的发展离不开高效的生产管理。MES生产管理系统是生产管理的核心工具&#xff0c;可以帮助电子企业提升生产效率和产品质量。本文将介绍电子企业快速部署一套MES生产管理系统的步骤和优势&…

C++算法:排序之三(堆排序)

C算法&#xff1a;排序 排序之一&#xff08;插入、冒泡、快速排序&#xff09; 排序之二&#xff08;归并、希尔、选择排序&#xff09; 排序之三&#xff08;堆排序&#xff09; 排序之四&#xff08;计数、基数、桶排序&#xff09; 文章目录 C算法&#xff1a;排序二、比较…

全排列--回溯

1题目 给定一个不含重复数字的数组 nums &#xff0c;返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;nums [1,2,3] 输出&#xff1a;[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]示例 2&#xff1a; 输入&#xff1a…

关于Spring中自带的@Schedule实现自动任务

SpringBoot中自带了一个能够实现定时任务的注解Schedule 如果定时任务比较简单&#xff0c;则可以使用SpringBoot中自带的Schedule&#xff0c;但是如果任务调度很复杂的话&#xff0c;就建议使用Quartz组件了。 说一下这个Schedule注解怎么用&#xff1f; 基本使用 第一步&a…

Centos8之系统升级

一、centos8系统简介 CentOS 8是一个基于Red Hat Enterprise Linux&#xff08;RHEL&#xff09;源代码构建的开源操作系统。它是一款稳定、可靠、安全的服务器操作系统&#xff0c;适合用于企业级应用和服务的部署。CentOS 8采用了最新的Linux内核和软件包管理系统&#xff0c…

chatgpt赋能python:Python如何分行——提高代码可读性和效率的必备技能

Python如何分行——提高代码可读性和效率的必备技能 什么是分行&#xff1f; 分行&#xff0c;即将一行长代码分为多行&#xff0c;使得代码更加易读、易维护、易修改。 Python作为一门高级编程语言&#xff0c;具有简洁、易读、高效的特点。但在实际编程过程中&#xff0c;…

Amazon Device EDI 数据库方案开源介绍

近期为了帮助广大用户更好地使用 EDI 系统&#xff0c;我们根据以往的项目实施经验&#xff0c;将成熟的 EDI 项目进行开源。用户安装好知行之桥EDI系统之后&#xff0c;只需要下载我们整理好的示例代码&#xff0c;并放置在知行之桥指定的工作区中&#xff0c;即可开始使用。 …

C++调python程序示例

背景 平台&#xff1a;Xavier nvidia AGX板子 编写c程序测试单目3D目标检测DEVIANT&#xff08;https://blog.csdn.net/qq_39523365/article/details/130982966?spm1001.2014.3001.5501&#xff09;python算法的过程。代码如下&#xff1a; 文件结构 具体代码&#xff1a; …

基于 prefetch 的 H5 离线包方案 | 京东云技术团队

前言 对于电商APP来讲&#xff0c;使用H5技术开发的页面占比很高。由于H5加载速度非常依赖网络环境&#xff0c;所以为了提高用户体验&#xff0c;针对H5加载速度的优化非常重要。离线包是最常用的优化技术&#xff0c;通过提前下载H5渲染需要的HTML/JS/CSS资源&#xff0c;加…

如何用Arcgis做一个地区的温度或降水量分布图

1.首先需要拥有一张shp格式的研究地区的矢量区域图&#xff08;很多网站都可以下载到全国各地区县域或者省域界线的矢量图&#xff0c;比如小木虫&#xff09;&#xff0c;以山西省为例: 2.导入本研究区域样地或样点的经纬度、温度&#xff08;或降水&#xff09;的csv格式数据…