UNIX再学习 -- 网络IPC:套接字

前端之家收集整理的这篇文章主要介绍了UNIX再学习 -- 网络IPC:套接字前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

一、基本概念

1、编程接口

什么是伯克利套接字(Berkeley Socket)?

美国加利福尼亚大学比克利分校于 1983年发布 4.2 BSD Unix 系统。其中包含一套用 C 语言编写的应用程序开发库。该库既可用于在同一台计算机上实现进程间通信,也可用于在不同计算机上实现网络通信。当时的 Unix 还受 AT&T 的专利保护,因此直到 1989 年,伯克利大学才能自由发布他们的操作系统和网络库,而后者即被称为伯克利套接字应用编程接口(Berkeley Socker APIs)。
伯克利套接字接口的实现完全基于 TCP/IP 协议,因此它是构建一切互联网应用的基石。几乎所有现代操作系统都或多或少有一些源自伯克利套接字接口的实现。它已成为应用程序连接互联网的标准接口。

什么是套接字?

套接字(socket)的本意是指电源插座,这里将其引申为一个基于 TPC/IP 协议可实现基本网络通信功能的逻辑对象。
机器与机器的通信,或者进程与进程的通信,在这里都可以被抽象地看作是套接字与套接字的通信。
应用程序编写者无需了解网络协议的任何细节,更无需知晓系统内核和网络设备的运作机制,只要把想发送的数据写入套接字,或从套接字中读取想接收的数据即可。
从这个意义上讲,套接字就相当于一个文件描述符,而网络就是一种特殊的文件,面向网络的编程与面向文件的编程已没有分别,而这恰恰是 unix 系统一切皆文件思想的又一例证。

什么是套接字的异构性?

如前所述,套接字是对 ISO/OSI 网络协议模型中传输及其以下诸层的逻辑抽象,是对 TCP/IP 网络通信协议的高级封装,因此无论所依赖的是什么硬件,所运行的什么操作系统所使用的是什么编程语言,只要是基于套接字构建的应用程序,只要是在互联网环境中通信,就不会存在任何障碍。

2、通信模式

单播模式

每个数据包发送单个目的主机,目的地址指明单个接收者。
服务器可以及时响应客户机的请求。
服务器可以针对不同客户的不同请求提供个性化的服务。
网络中传输的信息量与请求该信息的用户量成正比,当请求该信息的用户量较大时,网络中将出现多份内容相同的信息流,此时带宽就成了限制传输质量的瓶颈。

广播模式

一台主机向网上的所有其它主机发送数据。
无需路径选择,设备简单,维护方便,成本低廉。
服务器不用向每个客户机单独发送数据,流量负载极低。
无法针对具体客户的具体要求,及时提供个性化的服务。
网络无条件地复制和转发每一台主机产生的信息,所有的主机可以收到所有的信息,而不管是否需要,网络资源利用率低,带宽浪费严重。
禁止广播包穿越路由器,防止在更大范围内泛滥。

多播模式

网络中的主机可以向路由器请求加入或退出某个组,路由器和交换机有选择地复制和转发数据,只将组内数据转发给那些加入组的主机。
需要相同信息的客户机只要加入同一个组即可共享同一份数据,降低了服务器和网络的流量负载
既能一次将数据传输给多个有需要的主机,又能保证不影响其他不需要的主机。
多播包可以穿越路由器,并在穿越中逐渐衰减。
缺乏纠错机制,丢包错包在所难免。

3、绑定与连接

如前所述,套接字是一个提供给程序员使用的逻辑对象,它表示对 ISO/OSI 网络协议模型中传输层及其以下诸层的的抽象。但真正发送和接收数据的毕竟是那些实实在在的物理设备。这就需要在物理设备和逻辑对象之间建立一种关联,使后续所有针对这个逻辑对象的操作,最终都能反映到实际的物理设备上。建立这种关联关系的过程就叫做绑定

绑定只是把套接字对象和一个代表自己的物理设备关联起来。但为了实现通信还需要把自己的物理设备与对方的物理设备关联起来。只有这样才能建立一种以物理设备为媒介的,跨越不同进程甚至机器的,多个套接字对象之间的联系。建立这种联系的过程就叫做连接

二、常用函数

1、函数 socket:创建套接

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain,int type,int protocol);
返回值:成功返回套接字描述符,失败返回 -1

(1)参数解析

domain:通信域,即地址族,可取以下值:
AF_LOCAL/AF_UNIX 本地通信,即进程间通信
AF_INET 基于 IPv4 的网络通信
AF_INET6 基于 IPv6 的网络通信
AF_PACKET 基于底层包接口的网络通信
tyoe:套接字类型,可取以下值:
SOCK_STREAM 流式套接字,即使用 TCP 协议的套接字。有序的、可靠的、双向的、面向连接的字节流。
SOCK_DGRAM 数据报套接字,即使用 UDP 协议的套接字。固定长度的、无连接的、不可靠的报文传递。
SOCK_RAW 原始套接字,即使用 IP 协议的套接字。
SOCK_SEQPACKET 固定长度的、有序的、可靠的、面向连接的报文传递。
protocol:特殊协议,通常不用,取 0 即可。表示为给定的域和套接字类型选择默认协议。当对同一域和套接字类型支持多个协议时,可以使用 protocol 选择一个特定协议。

(2)函数解析

socket 函数所返回的套接字描述符类似于文件描述符,Unix 系统把网络也看成是文件,发送数据即写文件,接收数据即读文件,一切皆文件
虽然套接字描述符本质上是一个文件描述符,但不是所有参数为文件描述符的函数都可以接受套接字描述符。
下图总结了到目前为止所讨论的大多数以文件描述符为参数的函数使用套接字描述符的行为。未指定和由实现定义的行为通常意味着该函数套接字描述符无效。例如,lseek 不能以套接字描述符为参数,因为套接不支持文件偏移量的概念。

2、函数 shutdown:禁止套接

套接字通信是双向的。可以采用 shutdown 函数禁止一个套接字的 I/O。
#include<sys/socket.h>  
int shutdown(int sockfd,int how); 
返回值:若成功则返回0,出错则返回-1.  
如果 how 是 SHUT_RD (关闭读端),那么无法从套接字读取数据;如果 how 是 SHUT_WR (关闭写端),那么无法使用套接字发送数据。如果 how 是 SHUT_RDWR ,则既无法读取数据,又无法发送数据。
能够关闭(close)套接字,为什么还要使用 shutdown 呢?这里有若干个理由。
首先,只有最后一个活动引用关闭时,close 才释放网络端点。这意味着,如果复制一个套接字(如采用 dup),套接字直到关闭了最后一个引用它的文件描述符之后才会被释放。而 shutdown 允许使一个套接字处于不活动状态,和引用它的文件描述符数目无关。
其次,有时可以很方便地关闭套接字双向传输中的一个方向。

3、地址结构

套接字接口库通过地址结构定位一个通信主体,可以是一个文件,可以是一台远程主机,也可以是执行自己。 基本地址结构,本身没有实际意义,仅用于泛型化参数。
为使不同格式地址能够传入到套接函数,地址会被强制转换成一个通用的地址结构 sockaddr
struct sockaddr{
sa_family_t sa_family; //地址族
char sa_data[]; //地址值
...
};

本地地址结构,用于 AF_LOCAL/AF_UNIX 域的本地通信

#include <sys/un.h>
struct sockaddr_un
{
	sa_family_t sun_family; //地址族(AF)LOCAL)
	char sun_path[];  //踏破戒指文件路径
};

网络地址结构,用于 AF_INET 域的 IPv4 网络通信

#include <netinet/in.h>
struct sockaddr_in
{
	sa_family_t sin_family;  //地址族(AF_INET)
	in_port_t sin_port;  //端口号
	struct in_addr sin_addr;  //IP地址
};

struct in_addr
{
	in_addr_t s_addr;
};

typedef unit16_t in_port_t;  //无符号短整型
typedef unit32_t in_addr_t;  //无符号长整型
如前所述,通过 IP 地址可以定位网络上的一台主机,但一台主机上可能同时又多个网络应用在运行,究竟想跟哪个网络应用通信呢?这就需要靠所谓的端口号来区分,因为不同的网络应用会使用不同的端口号。用 IP 地址定位主机,再用端口号定位运行子啊这台主机上的一个具体的网络应用,这样一种对通信主体的描述才是唯一确定的。
套接字接口库中的端口号被定义为一个 16 位的无符号整数,其值介于 0 到 65535,其中 0 到 1024 已被系统和一些网络服务占据,比如 21 端口用于 ftp 服务、23端口用于 telnet服务。80端口用于 www 服务等,因此一般应用程序最好选择 1024 以上的端口号,以避免和这些服务冲突
网络应用与单机应用不同,经常需要在具有不同硬件架构和操作系统的计算机之间交换数据,因此编程语言里一些多字节数据类型的字节序问题就是需要特别予以关注。
这就涉及到大小端问题,参看:C语言再学习-- 大端小端详解(转)
假设一台小端机器里有一个 32 位整数:0x1234 5678,它在内存中按照小端字节序低位低地址的规则存放:

现在,这个整数通过网络被传送到一台大端机器里,内存中的形态不会有丝毫差别,但在大端机器看来地址越低的字节数位应该越高,因此它会把这 4 个字节解读为:0x7856 3412,而这显然有悖于发送端的初衷。
为了避免字节序带来的麻烦,套接字接口库规定凡是在网络中交换的多字节整数(short、int、long、long long 和它们的 unsigned 版本)一律采用网络字节序传输。
而所谓网络字节序,其实就是大端字节序。也就是说,发数据时,先从主机字节序转成网络字节序,然后发送;收数据时,先从网络字节序转成主机字节序,然后使用。

网络地址结构 sockaddr_in 中表示端口号的 sin_port 成员和表示 IP 地址的 sin_addr.s_addr 成员,分别为 2 字节和 4 字节的无符号整数,同样需要用网络字节序来表示。

猜你在找的Bash相关文章