UNIX环境高级编程——网络IPC:套接字

news2024/9/25 17:15:14

16.1 引言

本章将考察不同计算机(通过网络相连)上的进程相互通信的机制:网络进程间通信(network IPC)。

16.2 套接字描述符

为创建一个套接字,调用socket函数:

#include <sys/socket.h>

int socket(int domain, int type, int protocol);
										// 返回值:若成功,返回文件(套接字描述符);若出错,返回-1
  • 参数domain(域)确定通信的特征,包括地址格式,POSIX.1指定的各个域如下:
    在这里插入图片描述
  • 参数type确定套接字的类型,进一步确认通信特征,POSIX.1定义的套接字类型如下:
    在这里插入图片描述
  • 参数protocol通常为0,表示为给定的域和套接字类型选择默认协议,因特网域套接字定义的协议如下:
    在这里插入图片描述
    套接字通信是双向的,可以采用shutdown来禁止一个套接字的I/O:
#include <sys/socket.h>

int shutdown(int sockfd, int how);
										// 返回值:若成功,返回0;若出错,返回-1
  • 参数how的可能值有3种:
    • SHUT_RD(关闭读端),那么无法从套接字读取数据;
    • SHUT_WR(关闭写端),那么无法使用套接字发送数据;
    • SHUT_RDWR(关闭读写端),既无法读取数据,又无法发送数据。

16.3 寻址

进程标识由两部分组成:

  • 网络地址:标识网络上想与之通信的计算机;
  • 端口号:计算机上用端口号表示服务,用于标识特定的进程。

16.3.1 字节序

  • 大端字节序:最大字节地址出现在最低有效字节(Least Significant Byte,LSB)上;
  • 小端字节序:最大字节地址出现在最高有效字节(Most Significant Byte,MSB)上。

对于TCP/IP应用程序,有4个用来在处理器字节序网络字节序之间实施转换的函数:

#include <arpa/inet.h>

uint32_t htonl(uint32_t hostint32);
										// 返回值:以网络字节序表示的32位整数
uint16_t htons(uint16_t hostint16);
										// 返回值:以网络字节序表示的16位整数
uint32_t ntohl(uint32_t netint32);
										// 返回值:以主机字节序表示的32位整数
uint16_t ntohs(uint16_t netint16);
										// 返回值:以主机字节序表示的16位整数
  • h表示“主机”字节序,n表示“网络”字节序;
  • l表示“长”(即4字节)整数,s表示“短”(即2字节)整数。

16.3.2 地址格式

为使不同格式地址能够传入到套接字函数,地址会被强制转换成一个通用的地址结构 sockaddr

struct sockaddr {
	sa_family_t		sa_family;	/* address family */
	char			sa_data[];	/* variable-length address */
	...
};

在IPv4因特网域(AF_INET)中,套接字地址用结构 sockaddr_in 表示:

struct in_addr {
	in_addr_t		s_addr;		/* IPv4 address */
};

struct sockaddr_in {
	sa_family_t		sin_family;	/* address family */
	in_port_t		sin_port;	/* port number */
	struct in_addr	sin_addr;	/* IPv4 address */
};

IPv6因特网域(AF_INET6)套接字地址用结构 sockaddr_in6 表示:

struct in6_addr {
	uint8_t			s6_addr[16];	/* IPv6 address */
};

struct sockaddr_in6 {
	sa_family_t		sin6_family;	/* address family */
	in_port_t		sin6_port;		/* port number */
	uint32_t		sin6_flowinfo;	/* traffic class and flow info */
	struct in6_addr sin6_addr;		/* IPv6 address */
	uint32_t		sin6_scope_id;	/* set of interfaces for scope */
};

inet_ntopinet_pton用于二进制地址格式与点分十进制字符(a.b.c.d)之间的相互转换:

#include <arpa/inet.h>

const char *inet_ntop(int domain, const void *restrict addr,
					  char *restrict str, socklen_t size);
										// 返回值:若成功,返回地址字符串指针;若出错,返回NULL
int inet_pton(int domain, const char *restrict str,
			  void *restrict addr);
										// 返回值:若成功,返回1;若格式无效,返回0;若出错,返回-1
  • 参数 domain 仅支持两个值:AF_INETAF_INET6
  • 对于inet_ntop,参数size指定了保存文本字符串的缓冲区(str)的大小,INET_ADDRSTRLENINET6_ADDRSTRLEN分别定义了足够大的空间来存放一个表示IPv4和IPv6地址的文本字符串;

16.3.3 地址查询

通过调用gethostent,可以找到给定计算机系统的主机信息:

#include <netdb.h>

struct hostent *gethostent(void);
										// 返回值:若成功,返回指针;若出错,返回NULL
void sethostent(int stayopen);
void endhostent(void);
  • 如果主机数据库文件没有打开,gethostent会打开它,函数gethostent返回文件中的下一个条目,得到一个指向 hostent 结构的指针,该结构至少包含以下成员:
struct hostent {
	char	*h_name;		/* name of host */
	char	**h_aliases;	/* pointer to alternate host name array */
	int		h_addrtype;		/* address type */
	int		h_length;		/* length in bytes of address */
	char	**h_addr_list;	/* pointer to array of network addresses */
};
  • 函数sethostent会打开文件,如果文件已经被打开,那么将其回绕;当stayopen参数设置成非0值时,调用gethostent之后,文件将依然是打开的;
  • 函数endhostent可以关闭文件。
#include <netdb.h>

struct netent *getnetbyaddr(uint32_t net, int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
										// 3个函数的返回值:若成功,返回指针;若出错,返回NULL
void setnetent(int stayopen);
void endnetent(void);

netent 结构至少包含以下字段:

struct netent {
	char		*n_name;		/* network name */
	char		**n_aliases;	/* alternate network name array pointer */
	int			n_addrtype;		/* address type */
	uint32_t	n_net;			/* network number */
	...
};

下列函数在协议名字协议编号之间进行映射:

#include <netdb.h>

struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
										// 3个函数的返回值:若成功,返回指针;若出错,返回NULL
void setprotoent(int stayopen);
void endprotoent(void);

POSIX.1定义的 protoent 结构至少包含以下成员:

struct protoent {
	char	*p_name;		/* protocol name */
	char	**p_aliases;	/* pointer to altername protocol name array */
	int		p_proto;		/* protocol number */
	...
};

服务是由地址的端口号部分表示的,每个服务由一个唯一的众所周知的端口号来支持。函数getservbyname将一个服务名映射到一个端口号,函数getservbyport将一个端口号映射到一个服务名,函数getservent顺序扫描服务数据库:

#include <netdb.h>

struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent *getservent(void);
										// 3个函数的返回值:若成功,返回指针;若出错,返回NULL
void setservent(int stayopen);
void endservent(void);

servent 结构至少包含以下成员:

struct servent {
	char	*s_name;		/* service name */
	char	**s_aliases;	/* pointer to alternate service name array */
	int		s_port;			/* port number */
	char	*s_proto;		/* name of protocol */
	...
};

getaddrinfo函数允许将一个主机名和一个服务名映射到一个地址:

#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *restrict host,
				const char *restrict service,
				const struct addrinfo *restrict hint,
				struct addrinfo **restrict res);
										// 返回值:若成功,返回0;若出错,返回非0错误码
void freeaddrinfo(struct addrinfo *ai);
  • 需要提供主机名、服务名,或者两者都提供;如果仅提供一个名字,另一个必须是一个空指针;主机名可以是一个节点名或点分格式的主机地址;
  • getaddrinfo函数返回一个链表结构 addrinfo,可以用freeaddrinfo来释放一个或多个这种结构,这取决于用 ai_next 字段链接起来的机构由多少;addrinfo 结构的定义至少包含以下成员:
struct addrinfo {
	int				ai_flags;		/* customize behavior */
	int				ai_family;		/* adddress family */
	int 			ai_sockettype;	/* socket type */
	int				ai_protocol;	/* protocol */
	socklen_t		ai_addrlen;		/* length in bytes of address */
	struct sockaddr	*ai_addr;		/* address */
	char			*ai_canonname;	/* canonical name of host */
	struct addrinfo	*ai_next;		/* next in list */
	...
};
  • 可以提供一个可选的hint来选择符合特定条件的地址,hint是一个用于过滤地址的模板,包括ai_familyai_flagsai_protocolai_socktype字段;剩余的整数字段必须设置为0,指针字段必须为空;下图总结了ai_flags字段中的标志:
    在这里插入图片描述

如果getaddrinfo失败,不能使用perrorstrerror来生成错误消息,而是要调用gai_strerror将返回的错误码转换成错误消息:

#include <netdb.h>

const char *gai_strerror(int error);
										// 返回值:指向描述错误的字符串的指针

getnameinfo函数将一个地址转换成一个主机名和一个服务名:

#include <sys/socket.h>
#include <netdb.h>

int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen,
				char *restrict host, socklen_t hostlen,
				char *restrict service, socklen_t servlen, int flags);
										// 返回值:若成功,返回0;若出错,返回非0值
  • flags参数提供了一些控制翻译的方式,支持的标志如下:
    在这里插入图片描述

16.3.4 将套接字与地址关联

对于服务器,需要给一个接收客户端请求的服务器套接字关联上一个众所周知的地址;使用bind函数来关联地址和套接字:

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
										// 返回值:若成功,返回0;若出错,返回-1

可以调用getsockname函数来发现绑定到套接字上的地址:

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr *restrict addr,
				socklen_t *restrict alenp);
										// 返回值:若成功,返回0;若出错,返回-1

如果套接字已经和对等方连接,可以调用getpeername函数来找到对方的地址:

#include <sys/socket.h>

int getpeername(int sockfd, struct sockaddr *restrict addr
				socklen_t *restrict alenp);
										// 返回值:若成功,返回0;若出错,返回-1

16.4 建立连接

如果要处理一个面向连接的网络服务(SOCK_STREAMSOCK_SEQPACKET),那么在开始交换数据以前,需要在请求服务的进程套接字(客户端)和提供服务的进程套接字(服务器)之前建立一个连接;使用connect函数来建立连接:

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
										// 返回值:若成功,返回0;若出错,返回-1
  • connect中指定的地址是我们想与之通信的服务器地址;
  • 如果sockfd没有绑定到一个地址,connect会给调用者绑定一个默认地址。

服务器调用listen函数来宣告它愿意接受连接请求:

#include <sys/socket.h>

int listen(int sockfd, int backlog);
										// 返回值:若成功,返回0;若出错,返回-1
  • 参数backlog提示系统该进程所要入队的未完成连接请求数量。

服务器使用accept函数获得连接请求并建立连接:

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *restrict addr,
		   socklen_t *restrict len);
										// 返回值;若成功,返回文件(套接字)描述符;若出错,返回-1
  • 如果不关心客户端标识,可以将参数 addrlen 设置为NULL

16.5 数据传输

send函数用于发送数据,它可以指定标志来改变处理传输数据的方式:

#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
										// 返回值:若成功,返回发送的字节数;若出错,返回-1
  • flags参数指定了套接字调用标志,如下:
    在这里插入图片描述
    sendto函数在无连接的套接字上指定一个目标地址:
#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t nbytes, int flags,
			   const struct sockaddr *destaddr, socklen_t destlen);
										// 返回值:若成功,返回发送的字节数;若出错,返回-1

可以调用带有msghdr结构的sendmsg来指定多重缓冲区传输数据:

#include <sys/socket.h>

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
										// 返回值:若成功,返回发送的字节数;若出错,返回-1

POSIX.1定义了msghdr结构,它至少有以下成员:

struct msghdr {
	void			*msg_name;		/* optional address */
	socklen_t		msg_namelen;	/* address size in bytes */
	struct iovec	*msg_iov;		/* array of I/O buffers */
	int				msg_iovlen;		/* number of elements in array */
	void			*msg_control;	/* ancillary data */
	socklen_t		msg_controllen;	/* number of ancillary bytes */
	int				msg_flags;		/* flags for received message */
	...
};

函数recv可以指定标志来控制如何接收数据:

#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
										// 返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1
  • 下图总结了这些标志:
    在这里插入图片描述
    可以使用recvfrom来得到数据发送者的源地址:
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *restrict buf, size_t len, int flags,
				 struct sockaddr *restrict addr,
				 socklen_t *restrict addrlen);
										// 返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1
  • 如果addr非空,它将包含数据发送者的套接字端点地址。

为了将接收到的数据送入多个缓冲区,类似于readv,或者想接收辅助数据,可以使用recvmsg

#include <sys/socket.h>

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
										// 返回值:返回数据的字节长度;若无可用数据或对等方已经按序结束,返回0;若出错,返回-1
  • msghdr结构指定接收数据的输入缓冲区;
  • 可以设置flags来改变recvmsg的默认行为;返回时,msghdr结构中的msg_flags字段被设为所接收数据的各种特征(进入recvmsgmsg_flags被忽略);recvmsg中返回的各种可能值如下:
    在这里插入图片描述

16.6 套接字选项

可以使用setsockopt函数来设置套接字选项:

#include <sys/socket.h>

int setsockopt(int sockfd, int level, int option, const void *val, socklen_t len);
										// 返回值:若成功,返回0;若出错,返回-1
  • 参数level标识了选项应用的协议;如果选项是通用的套接字层次选项,则level设置成SOL_SOCKET;否则,设置成控制这选项的协议编号,对于TCP选项,levelIPPROTO_TCP,对于IP,levelIPPROTO_IP;下图总结了Single UNIX Specification中定义的通用套接字选项:
    在这里插入图片描述
  • 参数val根据选项的不同指向一个数据结构或者一个整数;一些选项是on/off开关,如果整数非0,则启用选项,如果整数为0,则禁止选项;
  • 参数len指定了val指向对象的大小。

可以使用getsockopt函数来查看选项的当前值:

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int option, void *restrict val, socklen_t *restrict len);

16.7 带外数据

16.8 非阻塞和异步I/O

实例代码

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

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

相关文章

图像增强之图像锐化(边缘增强)之robot算子

目录 note code test note matx (-1,0;1,0) maty (0,-1;1,0) code // 图像增强之图像锐化(边缘增强)之robot算子 void GetRobot(Mat& robotX, Mat& robotY) {robotX (Mat_<int>(2,2) << -1,0,1,0);robotY (Mat_<int>(2,2) << 0,-1,1…

bug汇集-二

1、多个表格 设置 只让当前选中行对应的表格行--高亮 问题&#xff1a;只能设置一个表格高亮&#xff0c;选中一个表格某行高亮&#xff0c;另一行就不高亮 解决&#xff1a; 1、在 表格属性配置里&#xff0c; 把 current-changecurrentChange 写成 current-change"…

多账号矩阵管理系统技术嫁接开发源代码

多账号矩阵管理系统技术嫁接开发源代码 文章目录 一、剪辑部分源代码开发示例二、发布投放部分源代码示例 1.账号绑定一码多扫技术应用开发代码示例2.定时挂载投放源代码示例 一、剪辑部分源代码开发示例 创建工程项目 */ public function createProjectAction() { …

windows电脑如何设置通电自启

原来电脑是需要摁启动摁扭才能开机&#xff0c;现在需要给服务器设置成通电自启 开机后摁del或者F2键进入bois设置&#xff0c;再摁F7进入高级设置&#xff0c;进入Advanced---APM Configuration 进入后 Restore AC Power Loss选择开启。

Selenium教程__获取元素文本、属性值、尺寸(8)

本文将介绍如何使用Selenium来获取元素的文本内容、属性值和尺寸&#xff0c;并展示一些实际应用场景。 学习本文内容将能够轻松地获取并利用元素的相关信息&#xff0c;从而更好地进行元素的验证、操作和断言。 from selenium import webdriverdriver webdriver.Chrome() …

【软考网络管理员】2023年软考网管初级常见知识考点(16)-网络安全与病毒

涉及知识点 网络安全要素&#xff0c;DDOS攻击&#xff0c;网络安全威胁&#xff0c;常见的病毒类型&#xff0c;软考网络管理员常考知识点&#xff0c;软考网络管理员网络安全&#xff0c;网络管理员考点汇总。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;获…

RabbitMQ学习笔记3(小滴课堂)RabbitMQ的安装和web 管控台的讲解

在安装之前我们先要启动docker: docker pull rabbitmq:management 我们现在去运行rabbitmq&#xff1a; 我们可以看到rabbitmq的日志&#xff1a; 可以用之前学过的命令去查看已经运行的容器。 账号admin 密码password 就能登录进去了&#xff1a; 我们可以加一个虚拟主机。

matlab绘制指定球心坐标及半径球面

[x,y,z]sphere(40);%先绘制单位球面&#xff0c;后面都是对此基础上进行更改完成预期的球面&#xff0c;40为分割球面的块数 r2;%球面半径 xx*r;yy*r;zz*r; xx1;yy1;zz1;%球心坐标 axis equal; surf(x,y,z);%绘图axis tight;% 坐标轴样式 square shading flat;% 着色方式light(…

分布式锁工具 Redisson,贼香!!

一、Redisson概述 二、分布式锁 三、Redisson分布式锁 四、RLock 五、公平锁 六、总结 一、Redisson概述 什么是Redisson&#xff1f; Redisson是一个在Redis的基础上实现的Java驻内存数据网格&#xff08;In-Memory Data Grid&#xff09;。它不仅提供了一系列的分布式…

docker搭建redis

一、Docker搜索redis镜像 命令&#xff1a;docker search <镜像名称> docker search redis docker pull redis#若不加版本号默认下载最新版 三、Docker挂载配置文件 接下来就是要将redis 的配置文件进行挂载&#xff0c;以配置文件方式启动redis 容器。&#xff08;挂载&…

js通过原型链的形式封装方法为组件

首先我们可以看看下面一张图 控制区有两个功能&#xff0c;一方面是和前端做交互渲染&#xff0c;另一方面是调用缓存区中所返回回来的封装方法所产生的值 此处我们采用的是angular.js的形式 html文件为 <!DOCTYPE html> <html lang"en"><head>…

区块链运行原理

文章目录 前言区块链的结构区块链的交易过程区块链的共识机制区块链交易存在的问题特性总结 前言 上文《认识区块链》中可以知道区块链是一个通过各种加密算法、共识机制以及其他技术可以实现一个点对点的电子现金系统&#xff0c;从而达到去第三方的效果&#xff08;通常称之…

SNMP 计算机网络管理 实验3(三) SNMP协议工作原理验证与分析

&#x1f680;write in front&#x1f680; 一、实验目的 学习捕获SNMP报文&#xff0c;通过报文分析理解SNMP协议的工作过程。二、实验内容与设计思想 实验内容&#xff1a;编写代码&#xff0c;测试 1&#xff09;使用snmputilg发送SNMP数据包; 使用wireshark抓包&…

【css】实现单独一区域滚动,不影响整体滚动

来源&#xff1a;css实现单独一区域滚动&#xff0c;不影响整体滚动&#xff0c;两种方法 方法 第一种&#xff1a;就该区域高度固定&#xff0c;然后overflow-auto 第二种&#xff1a;使用属性overscroll-behavior: contain; .overfolw{position: absolute;width: 100%;ov…

mysql看视频---01

计算机上的易失和非易失存储器 什么是IO,IO消耗的时间,举例.内存条存什么. IO是指对数据流的输入和输出&#xff0c;就是读写数据,也称为IO流。Java所有的I/O机制都是基于数据流进行输入输出&#xff0c;这些数据流表示了字符或者字节数据的流动序列。 io消耗的时间(如下图)…

【Guava】Guava: Google Core Libraries for Java 好用工具类

文章目录 Guava前言Guava的优势官方网址guava类库使用基本工具类&#xff1a;让使用Java语言更令人愉悦。集合类&#xff1a;集合类库是 Guava 对 JDK 集合类的扩展。Guava 缓存: 本地缓存&#xff0c;可以很方便的操作缓存对象&#xff0c;并且支持各种缓存失效行为模式。Func…

python爬虫-逆向实例小记-1

注意&#xff01;&#xff01;&#xff01;&#xff01;某政府网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01;&#xff01; 案例分析 第一步&#xff1a;下图标注出来的是获取请求断点&#xff0c;断点出自 请求…

Android实现拨打电话功能

Android实现拨打电话功能 先上UI效果图点击联系博主拒绝权限同意权限直接拨号 跳转到拨号界面跳转到拨号界面 同时附带号码 代码实现AndroidManifest.xml中添加权限点击联系博主判断是否有拨号权限申请权限回调直接拨号跳转到拨号界面跳转到拨号界面 同时附带号码 完整代码Main…

chatgpt赋能python:Python色板介绍:让数据可视化更加迷人

Python色板介绍&#xff1a;让数据可视化更加迷人 在数据可视化中&#xff0c;合适的配色方案是非常重要的。Python色板是一种常用的工具&#xff0c;可以帮助数据分析师和科学家创建漂亮、易于阅读的图表。本文将为大家介绍Python色板&#xff0c;包括一些主流的Python色板、…

百度排名代发|百度排名蜘蛛池代发收录排名接单!

百度排名代发|百度排名蜘蛛池代发收录排名接单! 推广告忽然没有展现了&#xff0c;咋回事&#xff1f; 这是今天一个粉丝问我的问题&#xff0c;我帮他看了一下&#xff0c;是因为他推广了一个打不开的链接&#xff0c;所以说百度没有给他展现&#xff0c;可以看一下这个还好…