进程间通信(7)——套接字

news/2024/11/17 23:51:13/

0. 前言

进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进

程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如:

UNIX BSD:管道(pipe)、命名管道(fifo)、信号(sinal)

UNIX system:消息队列(message)、共享内存(shm)、信号量(semaphore)

它们都仅限于本地进程间通信。而网络间通信要解决的是不同主机进程间的通信问题(可把同机进程间通信看成一个特例)。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。 其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。 

TCP/IP协议族已经帮我们解决了这个问题,网络层的 “ip地址” 可以唯一标识网络中的主机,而传输层的 “协议+端口” 可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。

使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX  BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。就目前而言,几乎所有的应用程序都是采用 socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。

1. socket 套接字

socket 套接字是通信断点的抽象。与应用程序要使用文件描述符访问文件一样,访问套接字也需要套接字描述符。套接字描述符在 UNIX 系统是用文件描述符实现的,因此很多处理文件描述符的函数(如 read 和write)也可以调用socket 描述符。

2. socket()

#include <sys/socket.h>int socket(int domain, int type, int protocol);

该函数用以创建一个套接字描述符,该描述符唯一标识一个 socket。

返回值:

  • 若成功,返回socket描述符;
  • 若出错,放回-1;

参数:

  • domain:域,确定通信的特性,详细看第 2.1 节;
  • type:确定socket 的类型,进一步确定通信特征,详细看第 2.2 节;
  • protocol:通常为0,表示按照给定的域和套接字type 选择默认协议。当然,对同一域和socket type支持多个协议时,可以使用 protocol 参数选择一个特定的协议。
    •  在AF_INET 通信域、socket type为 SOCK_STREAM 默认协议是TCP;
    • 在AF_INET 通信域、socket type为 SOCK_DGRAM 默认协议是UDP; 

2.1 参数domain

域的类型大致分:

  • AF_INET:   IPv4 因特网域;
  • AF_INET6: IPv6 因特网域;
  • AF_UNIX:   UNIX 域,多数系统还会定义 AF_LOCAL,这个是AF_UNIX 别名;
  • AF_UNSPEC:未指定,可以代表任何域;

UNIX 域套接字用于在同一台机器上运行的进程之间的通信。虽然因特网域套接字可以用于同一目的,但UNIX 域套接字的效率更高。UNIX 域仅仅复制数据,它们并不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不要产生顺序号,无需发送确认报文。

UNIX 域套接字提供流和数据报两种接口。UNIX域数据报服务是可靠的,既不会丢失消息也不会传递出错。UNIX 域套接字是 套接字管道 之间的混合物。

为了创建一对非命名的、相互连接的 UNIX 域套接字,用户可以使用它们面向网络的域套接字接口,也可以使用 socketpair() 函数。

#include <sys/socket.h>int socketpair(int domain, int type, int protocol, int sockfd[2]);返回值:若成功返回0,若出错返回-1

2.2 参数type

type 用于确定套接字的类型:

  • SOCK_STREAM:又称字节流,有序、可靠、双向的面向连接字节流
  • SOCK_DGRAM:又称数据报,长度固定、无连接的不可靠的报文传递;
  • SOCK_RAW:IP 协议的数据报接口;
  • SOCK_SEQPACKET:长度固定、有序、可靠的面向连接报文传递;
     

数据报(SOCK_DGRAM)是一种自包含报文。发送数据报近似于给某人邮寄信件。可以邮寄很多信,但不能保证投递的次序,并且可能有些信件丢失在路上。每一封信件包含接收者的地址,使这封信件独立于所有其他信件。每一封信件可能送达不同的接收者。

对于字节流(SOCK_STREAM),应用程序意识不到报文界限,因为套接字提供的是字节流服务。这意味着当从套接字读出数据时,它也许不会返回所有由发送进程所写的字节数。最终可以获取发送过来的所有数据,但也许要通过若干次函数调用得到。

SOCK_SEQPACKET 与 SOCK_STREAM 套接字类似,但从该套接字的大的是基于报文的服务而不是字节流服务。这意味着从 SOCK_SEQPACKET 套接字接收的数据量与对方所发送的一致。流控制传输协议(Stream Control Transmission Protocol,SCTP)提供了因特网域上的顺序数据包服务。

SOCK_RAW 套接字提供一个数据报接口用于直接访问下面的网络层(在因特网域中为 IP)。使用这个套接字,应用程序负责构造自己的协议首部,用以防止恶意程序绕过内建安全机制来创建报文。

3. bind()

#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t len);返回值:若成功返回0,若出错返回-1

对于服务器来说,需要给一个接收客户端请求的套接字绑定一个众所周知的地址。使用 bind() 函数将地址绑定到套接字。

而客户端不用指定,由系统自动分配一个端口号和自身的IP 地址组合。

参数:

  • sockfd:通过 socket() 创建的套接字描述符,将会是addr 绑定到该套接字上;
  • addr:指向要绑定给 sockfd 的协议地址,这个地址结构根据创建 socket 时的地址协议族不同而不同;
  • len:对应地址的长度;

重点来关注下参数 addr,这个地址结构根据创建 socket 时的地址协议族不同而不同。

地址的格式与特定的通信域是有关联的,为了让不同格式的地址能够传入到套接字函数,地址被强制转换成通用的 sockaddr 表示:

struct sockaddr {sa_family_t	sa_family;	/* address family, AF_xxx	*/char		sa_data[];...
};

例如在linux 中,该结构定义如下:

struct sockaddr {sa_family_t	sa_family;	/* address family, AF_xxx	*/char		sa_data[14];	/* 14 bytes of protocol address	*/
};

在freeBSD 中,该结构定义如下:

struct sockaddr {unsigned char sa_len;     /*total length*/sa_family_t	sa_family;    /*address family*/char		sa_data[14];  /*variable-length address*/
};

在IPv4 因特网域中,该结构定义如下:

struct sockaddr_in {sa_family_t	    sin_family;	/* Address family		*/in_port_t		    sin_port;	/* Port number			*/struct in_addr	sin_addr;	/* Internet address		*/
}struct in_addr {uint32_t    s_addr;        /* address in network byte order */
};

对于所能使用的地址有一些限制:

  • 在进程所运行的机器上,指定的地址必须有效,不能指定一个其他机器的地址;
  • 地址必须和创建套接字时的地址族所支持的格式相匹配;
  • 端口号必须不小于1024,除非该进程具有相应的特权(即为超级用户);
  • 一般只有套接字断点能够与地址绑定,尽管有些协议允许多重绑定;

3.1 网络字节序和主机字节序

主机字节序就是我们平常说的大端和小端模式:不同的CPU有不同的字节序类型,这些字节序是指整数在内存中保存的顺序,这个叫做主机序。引用标准的 Big-Endian和 Little-Endian的定义如下:

  • Little-Endian 就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端。
  • Big-Endian   就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端。

网络字节序,4个字节的32 bit值以下面的次序传输:首先是 0~7bit,其次 8~15bit,然后16~23bit,最后是 24~31bit。这种传输次序称作大端字节序由于 TCP/IP 首部中所有的二进制整数在网络中传输时都要求以这种次序,因此它又称作网络字节序。字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序,一个字节的数据没有顺序的问题了。

所以,在将一个地址绑定到socket的时候,请先将主机字节序转换成为网络字节序,而不要假定主机字节序跟网络字节序一样使用的是Big-Endian。由于这个问题曾引发过血案!公司项目代码中由于存在这个问题,导致了很多莫名其妙的问题,所以请谨记对主机字节序不要做任何假定,务必将其转化为网络字节序再赋给socket。

3.2 getsockname()

#include <sys/socket.h>int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp);返回值:若成功返回0,若出错返回-1

可以通过调用函数 getsockname() 来发现绑定到一个套接字的地址。

在调用 getsockname() 之前,设置 alenp 为一个指向整数的指针,该整数指定缓冲区 sockaddr 的大小。返回时,该整数会被设置成返回地址的大小。如果该地址和提供的缓冲区长度不匹配,则将其截断而不报错。如果当前没有绑定到套接字的地址,其结果没有定义。

4. connect()

如果处理的是面向连接的网络服务(SOCK_STREAM 或 SOCK_SEQPACKET),在开始交换数据之前,需要在请求服务的进程套接字 (客户端) 和提供服务的进程套接字(服务端)之间建立一个连接。

#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t len);返回值:若成功返回0,若出错返回-1

在connect() 中所指定的地址是想与之通信的服务器地址。如果 sockfd 没有绑定到一个地址,connect() 会给调用者绑定一个默认的地址。

当连接一个服务器时,出于一些原因,连接可能会失败。要连接的机器必须开启并且正在运行,服务器必须绑定到一个想与之连接的地址,并且在服务器的等待连接队列中应有足够的空间。因此,应用程序必须能够处理 connect() 返回的错误,这些错误可能由一些瞬时变化条件引起。

5. listen()

#include <sys/socket.h>int listen(int sockfd, int backlog);返回值:若成功返回0,若出错返回-1

参数:

  • sockfd:需要监听的套接字描述符;
  • backlog:用以限定连接请求的数量,一旦队列满,系统会拒绝多余连接请求;

socket() 函数创建的socket 默认是一个主动类型的,listen() 将socket 变成被动类型的,等待客户的连接请求。

6. accept()

#include <sys/socket.h>int accept(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict len);返回值:若成功返回socket描述符,若出错返回-1

当服务器调用 listen() 监听函数之后,socket 就能接受连接请求,使用 accept() 获取连接请求并建立连接。

当accept() 成功返回,这个返回值为一个 socket 描述符,用以连接到调用 connect() 的客户端。这个新的套接字描述符 (又称连接socket)与 原始的sockfd (又称监听socket) 具有相同的socket type和地址族。

如果不关心客户端标识,可以将参数 addr 和 len 设为NULL,否则在调用 accept() 之前,应将参数 addr 设为足够大的缓冲区来存放地址,并且将 len 设为指向代表缓冲区大小的整数的指针。返回时,accept() 会在缓冲区填充客户端的地址并且更新指针 len 所指向的整数为该地址的大小。

如果没有连接到来,accept() 会阻塞直到连接请求的到来。

7. 数据传输

  • read()  /  write()
  • sned() /  recv()
  • sendto()  /  recvfrom()
  • sendmsg()  / recvmsg()

socket 数据传输大致分为上面 4 组,除了 read() 、write() 系统调用,socket 还提供了三组数据传输接口。

#include <sys/socket.h>ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);返回值:若成功返回发送字节数,若出错返回-1

类似于 write(),使用send() 时套接字必须已经连接。

如果 send() 成功返回,并不必然表示连接另一端的进程接收数据。所保证的仅是当send() 成功返回,数据已经无错误地发送到网络上。

相关博文:

进程间通信(0)——序 

进程间通信(1)——信号(Signal)

进程间通信(2)——管道(PIPE)

进程间通信(3)——命名管道(FIFO)

进程间通信(4)——消息队列

进程间通信(5)——共享内存

进程间通信(6)——信号量(semaphore​​​​​​)

进程间通信(7)——套接字(socket)


http://www.ppmy.cn/news/774580.html

相关文章

LLaMA: Open and Efficient Foundation Language Models

背景 用最少的计算资源&#xff0c;解决了LLM大模型预测问题&#xff0c;训练了一些列的LLaMa模型&#xff0c;在参数量比较少的情况下&#xff0c;达到业界大模型效果。 主要贡献就是提升了LLM模型的训练速度和效率&#xff0c;在小容量的基础上&#xff0c;大大提升了模型的…

##ID悬浮窗消失解决方法

##ID悬浮窗消失解决方法 1.取消idm选项—常规—使用高级浏览器集成 的那一项&#xff0c;保存彻底退出idm; 2.关闭浏览器中的IDM扩展程序&#xff0c;以谷歌浏览器为例 3.删除C:\Users\jyp\AppData\Roaming\IDM\ 目录下的全部文件&#xff0c;重启IDM&#xff0c;打开浏览器中…

苹果手机悬浮窗怎么打开_悬浮窗搜题神器_悬浮窗搜题神器app源码苹果软件预约 v1.0...

悬浮窗搜题神器&#xff0c;是一个非常不错的学习软件&#xff0c;它主要为中小学生打造&#xff0c;覆盖语数英理化地史政治生课程&#xff0c;学生可以随时在线学习&#xff0c;需要什么课程&#xff0c;直接搜索就可以快速找到&#xff0c;很方便&#xff0c;并且提供了大量…

苹果手机悬浮窗怎么打开_怎么通过悬浮窗录音?再也不用担心文本被遮挡

怎么通过悬浮窗录音&#xff1f;在录音的时候经常遇到这种情况&#xff0c;打开了录音软件&#xff0c;录音软件就遮挡了要录制的文本&#xff0c;将文本缩小看起来又很不方便。在生活中&#xff0c;这种情况其实很普遍&#xff0c;主要是因为录音软件没有选择好。 在浏览器搜索…

iapp调用java顶部显示界面_iapp 悬浮窗权限

判断有没有悬浮窗权限&#xff0c;没有就跳转到权限设置界面 可以放到myu模块里面调用 对于一些必须要有悬浮窗权限才能实现的功能 //判断悬浮窗权限 java(uxfqx,null,"android.provider.Settings.canDrawOverlays","android.content.Context",activity) /…

iapp悬浮窗权限代码

iapp判断有没有悬浮窗权限&#xff0c;没有就跳转到权限设置界面。 当然也可以放到myu模块里面调用。 对于一些必须要有悬浮窗权限才能实现的功能 iapp代码如下 //判断悬浮窗权限 java(uxfqx,null,"android.provider.Settings.canDrawOverlays","android.cont…

【IOS】震惊!ios居然自带悬浮窗调试工具

我们经常使用各种调试工具&#xff0c;或者开源库来支持悬浮窗调试信息&#xff0c;但苹果的私有方法就提供了UIDebuggingInformationOverlay。 系统要求: iOS10 经测试 iOS11不可用 使用方法: 在 AppDelegate 的 didFinishLaunchingWithOptions 方法中加入两行代码即可。 #if …

iOS中全局悬浮按钮,类似IPhone中的AssistiveTouch (可以替换为视频悬浮窗口)

前提&#xff1a;当时看到别人写过这个类似AssistiveTouch的demo&#xff0c;但是有问题&#xff0c;第一改变不了位置、第二切换页面后无法使用、第三运行时偶尔会崩溃。然后自己就去度娘、论坛中都查了一些资料&#xff0c;然后结合起来写了这么一个demo。 思路&#xff1a;…