Socket简析

用过了WebSocket,来看看Socket是咋回事

一直用着HTTP,然后因为面试,所以接触了TCP/IP,甚至UDP等,后续又听说微服务的RPC和进程间的IPC,然而这些东西都有一些共同点就是都属于进程间通信,只是说进程可能位于同一台主机或者不同主机,究其底层原理,看到了socket,本文主要是深入了解下socket的内容。

一、什么是 socket?

“什么”这种东西能在百度百科得到的,就不再赘述,不过看到一个比较有意思的比喻:

我们把插头插到插座上就能从电网获得电力供应,同样,为了与远程计算机进行数据传输,需要连接到因特网,而 socket 就是用来连接到因特网的工具。

然后我能给出的理解是:我们以前会采用书信来传输信息,现在用网络,那么需要一个承载物,我们的信息能够转化为电信号在线路上传输,所以我们只需要在两端接收即可,这个用于接收的东西,便是socket,在概念上是一个抽象的东西,但是在本质上,它是一个文件。

1.1 UNIX/Linux中的 socket 是什么?

你也许听很多高手说过,UNIX/Linux 中的一切都是文件!那个家伙说的没错。

在 UNIX/Linux 系统中,为了统一对各种硬件的操作,简化接口,不同的硬件设备也都被看成一个文件。对这些文件的操作,等同于对磁盘上普通文件的操作。

请注意,网络连接也是一个文件,它也有文件描述符。

我们可以通过 socket() 函数来创建一个网络连接,或者说打开一个网络文件,socket() 的返回值就是文件描述符。有了文件描述符,我们就可以使用普通的文件操作函数来传输数据了,例如:

  • 用 read() 读取从远程计算机传来的数据;
  • 用 write() 向远程计算机写入数据。

1.2 Window 系统中的 socket 是什么?

Windows 也有类似“文件描述符”的概念,但通常被称为“文件句柄”。

与 UNIX/Linux 不同的是,Windows 会区分 socket 和文件,Windows 就把 socket 当做一个网络连接来对待,因此需要调用专门针对 socket 而设计的数据传输函数,针对普通文件的输入输出函数就无效了。

以下摘自百度百科:

二、socket做了哪些事情

熟悉操作系统和计算机组成原理的同学会知道我们所有的数据实际上都是在存储器中进行操作,只是通过不同的存储器分类,实现了缓存,硬盘,内存等一系列存储器,假设现在你要编程网络程序,进行服务器端和客户端的通信(数据交换),不使用 socket 的话,你会做下面的一堆事情:

  • 管理缓存区来接收和发送数据
  • 告诉操作系统自己要监听某个端口的数据,还有自己处理这些系统调用来读取数据
  • 当没有连接的时候或者另外一端要还没有发送数据时候,要处理 IO 阻塞,把自己挂起
  • 封装和解析 tcp/ip 协议的数据,维护数据发送的顺序
  • 等等

做了一大堆东西,发现最重要的还没有做:发送/接收数据。如果有一个程序能够自动帮我们把上面的东西都做掉,这样我们就可以只关心数据的读写,编程就简单的多了。那么这样一个程序就是 socket,它现在已经是操作系统的一部分,在 linux 中是标准的系统调用(当然Windows也提供了,都是基于posix标准),通过这些调用,我们就能轻松地建立连接,读写数据,关闭连接,让网络操作就像文件操作一样简单。

事实上socket就是做了一系列基础性的底层数据操作的事情,让我们能够简单的编程。

三、Socket分类

在了解了Socket的本质,便开始考虑它的实际用途,其实不同人有不同的理解,分类只是为了方便本文叙述,至于为什么,后续会讲到,首先给出一个知识点方便理解为什么有这些分类:

内存的拷贝问题(网上都说零拷贝,但是也有反对的声音,我这里不做认可和反对):

主要过程就是我们从磁盘中读取文件数据然后通过socket发送网络的另一方,其中又因为内核态和用户态的问题涉及到了三个缓冲区需要进行数据传输,其中socket就占用其中一个,所以可以得知socket本身是在传输层之上的,它需要其他的数据传输机制来提供支持,但是正因为底层的数据传输层机制而出现了因应用场景不同而出现的分类。

网上的分类很多,这里我归纳了一下:

  • 对socket进行分类(底层数据传输机制不同)
    • Internet domain socket(Internet 套接字)
    • Unix domain socket(Unix套接字)
  • socket有哪些类型(底层数据传输机制相同)
    • SOCK_STREAM(流格式套接字)
    • SOCK_DGRAM(数据报格式套接字)
    • SOCK_RAW(原始套接字)

3.1 底层数据传输机制不同

Internet domain socket(Internet 套接字)可以用于不同主机间进程的通信,只要知道了对方的ip地址和端口就可以通信了所以这种socket通信是基于网络协议栈的。

Unix domain socket(Unix套接字)用于同主机间进程通信,不需要基于网络协议,主要是基于文件系统的。与Internet domain socket类似,需要知道是基于哪一个文件(相同的文件路径)来通信的。

此外,在应用方面的区别有unix domain socket 适合IPC ,Internet domain socket 适合RPC,其实很简单的道理,根据使用方式就能知道两者的适用场景不通,但是我们需要考虑的底层实现,两者应该在socket的处理上是一样的,只是在数据传输上的封装不一样。

简单说:Internet套接字主要是用于TCP等网络协议之上,而Unix套接字主要是可以直接获取到socket文件地址,通过unix:文件绝对路径来获取socket端从而实现通信效果,最基本的例子就是nginx中反向代理php-fpm的两种方式:

1
2
3
fastcgi_pass 127.0.0.1:9000
#or
fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock

3.2 底层数据传输机制相同

其实这个分类主要是当我们从socket中读取数据时,需要通过何种方式来获取数据而出现的分类。

通过大家喜闻乐见的C语言举例(因为各个语言的实现不同,使用方式也不同,c语言能够将各个细节的参数给暴露出来):

1
2
3
4
//这是创建socket文件描述符
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);

其中各个参数的意义如下(摘抄自https://cnodejs.org/topic/5ae1f5da39a81e4548f45741,改掉了其中的错误):

  • domain代表通讯所在的域,表示这个通讯是在哪个范围内进行,不同的域的通讯协议是不同的。 Unix中支持的domain的值和含义为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Name                Purpose                          Man page
    AF_UNIX, AF_LOCAL Local communication unix(7)
    AF_INET IPv4 Internet protocols ip(7)
    AF_INET6 IPv6 Internet protocols ipv6(7)
    AF_IPX IPX - Novell protocols
    AF_NETLINK Kernel user interface device netlink(7)
    AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
    AF_AX25 Amateur radio AX.25 protocol
    AF_ATMPVC Access to raw ATM PVCs
    AF_APPLETALK AppleTalk ddp(7)
    AF_PACKET Low level packet interface packet(7)
    AF_ALG Interface to kernel crypto API

    Unix支持多种类型的通讯域,其中AF_UNIX(本地通讯)、AF_INET(IP4通讯)、AF_INET6(IP6通讯)最为常见的,AF是address family的缩写,中文一般翻译为地址族,是指某种类型的地址的集合。例如AF_INET就是所有IPv4地址的集合,AF_LOCAL就是Unix Socket的集合(大部分为一个本地文件的路径)。我个人是这样理解的,domain指定的是一个通讯的范围或者空间,可以通过地址族来描述这个空间的范围(因为地址族就是所有通讯地址的集合),因此通过地址族来指定domain是很合理的

  • type代表通讯数据的语意,所谓的“语意”我个人的理解是指:通讯前是否需要建立连接,通道是否双工,数据是否有序,通讯是否可靠。一开始我觉得这些不是协议的部分内容么,为何要在这里指定?后来再仔细想想这些其实和具体的协议是不相关的,协议的具体内容是构建于这些“语意”之上,“语意”比协议更加低层,它是socket进行数据交换的方式,而如何解读这些数据才是协议的事情。 Unix中支持的type的值和含义为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.

    SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).

    SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.

    SOCK_RAW Provides raw network protocol access.

    SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.

    SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).

    SOCK_STREAM提供面向连接的、可靠的、顺序的字节流,因为流式数据没有提供定界功能,因此需要自己去处理数据重组,即所谓的“粘包”。SOCK_DGRAM提供无连接的、不可靠的、最大长度是确定的数据报。一种类型的domain可以选择不同的type,例如我们选择AF_INET类型的socket进行通讯,可以选择流式数据还是数据报数据。而网络编程中,TCP和UDP分别使用SOCK_STREAMSOCK_DGRAM的方式进行通信。

  • protocol代表通讯所用的协议类型
    一般来说对于某个给定的协议族,只有一个protocaldomain对应,在这种情况下你只需将protocol设为0系统就会自动为你选择适当的协议。但是理论上在一个协议族中,是有可能有多个protocal适用于同一个domain,此时需要显示指定protocol的值。

其实到这里,应该有人发现我的文章已经前后矛盾了,因为在我的例子中,我并没有给出我这样分类的依据,但是如果认真看过的人就会发现,在例子中提到了语义,在我看来,这就是一种分类,但是我最开始也是有不理解的地方,看到这个大佬的解释,其实这和一千个人眼里有一千个哈姆雷特是同一个道理,标准已经定好,怎么理解在于个人,这也是一个人自我思考能力的表现。(别说什么就是没有思考才来看文章,没思考你也看不懂)

3.3 总结

在本文的两种分类归纳,其实就对应了socket()函数的两个参数domaintype,在实际的使用过程中,两者应该是交叉使用的,所以在开头我就有提到,事实上分类这个说法就不够准确。

四、使用

使用方面其实没啥好讲的,本篇文章是我参考了十几个网页加上自己的理解才归纳出来,这是我认为能够讲清楚socket知识点的文章,所以并不准备放上使用细节,因为使用方面,也有标准,但是实际上在语言不同的时候细节也不一样,网上的文章也都是大同小异,这里就给个c语言的参考和Go的参考吧:

https://cnodejs.org/topic/5ae1f5da39a81e4548f45741

https://blog.csdn.net/liumiaocn/article/details/54849558

五、总结

本文就是希望能够讲清楚socket背后的故事,以及如何讲其与所学过的知识点联系起来,我写这篇文章的起因就是我因为某次面试被问及nginx的epoll网络模型,所以我准备看看多路复用的知识,但是在学习过程我无法理解为什么要用多路复用,直接的对比我能知道,但是在整体上有什么区别,然后我看到了一个说法是一个进程只能有一个socket,多路复用就是可以有多个socket,所以我想了解下socket的背后原理。

其实本篇文章也没有能讲清楚socket的实现原理,只是在概念上梳理了一下,实现原理我认为其实涉及更多的是底层与操作系统打交道了,这就涉及更多的知识点,这里肯定是讲不完的,这篇文章主要希望能给和我有相同疑问的人们帮助,顺带我录了写这篇文章的视频,准备到b站上试试,自己本身是感兴趣的,给大家参考下写博客以及自己学习的方式。

接下来我准备看看多路复用和进程间通信的一些知识,看看到底写这篇文章对我理解有没有帮助,刚好我在写完本篇文章时找到了一个完整联系的文章:https://www.zmonster.me/notes/tcp-ip-socket-in-c.html

顺带一提,还好我录了视频,不然这篇文章我都不想发了,因为我本地写markdown喜欢用typora,就在写完这篇文章的时候,我关掉了他准备用hexo上传到我的博客上,但是当我p好我的首页图时,发现文章内容少了一半,已经是晚上11点半了,我简直崩溃哦,原因就是typora不提供自动保存功能,反正我没找到,然后我对着视频又补充了一些东西,也算是再检查一遍吧。