什么是socket?什么是文件描述符?非科班程序员告诉你!

3,557 阅读9分钟

絮叨

随着互联网行业的蓬勃发展,市场对于程序员的需求激增,尤其是java程序员,而非科班出身的java程序员也不占少数,我本人也是其中之一。由于对计算机底层了解不深,导致有很多框架底层相关的实现不理解。但是作为一个优秀的java程序员,怎么能容忍这样的事情发生,有不理解的就要千方百计的搞懂。

这两天我刚开始学习redis,正当我在了解redis为什么如此快的时候,一个陌生的名词出现在我眼前,“io多路复用”,于是乎我和大部分人一样,有问题上就百度一下,可是我发现,要理解io多路复用,就必须理解linux的io模型,理解socket的连接,理解文件描述符等等我从来没接触过的知识。到了这一步,有些人可能会想,我只要会用就行了,这么复杂的东西工作也用不到。可是我想说,只是会用代表你只是一个合格的程序员,不代表你是个优秀的程序员。因此我翻遍了各大博客论坛,终于对这些陌生的概念有了一些理解,下面我会从一个非科班程序员的角度(大神请自觉绕道)带你们了解socket和文件描述符,同时学习一下linux的其他io模型




一、什么是socket

在理解什么是socket之前,我们举一个生活中常见的例子,打电话。我想要给女朋友打电话,首先我要拨通女朋友的电话号码,经过无线电传输,对方手机收到到我打的电话信号并发出电话铃声,最后女朋友点击接听键,一次电话连线就完成了。连线成功后,你可以对女朋友说话,女朋友也可以对你说话。这个过程就和socket连接非常像

如果有计算机A想要和计算机B通过网络进行通信,那么计算机A中必须要有一个socket,计算机B也要有一个socket,这两个socket一旦进行连接,计算机A就能向计算机B发送接收数据,计算机B也能向计算机A发送接收数据了。计算机A向计算机B发送数据,就会用到SocketA的OutputStream,计算机A接收计算机B的数据,就会用到Socket的InputStream



官方解释

oracle官方文档中有对socket的详细解释,相信看了我上面的解说,就能更好的理解了

A socket is one endpoint of a two-way communication link between two programs running on the network. A socket is bound to a port number so that the TCP layer can identify the application that data is destined to be sent to.

中文翻译是,网络中运行的两个程序,他们建立起了一个能够使双方互相通信的链接,一个socket就是这个链接的一个末端。一个socket绑定一个端口,这样使得TCP传输层能够知道数据传送的目的地




二、socket建立连接的过程

首先服务器(server)上有一个socket绑定了80端口(80端口是为HTTP超文本传输协议开放的端口),服务器会一直等待,直到有客户端(client)向服务端发送了连接请求

client会把自己的ip和端口信息告诉server,这样server就会在本地开启一个与client同端口号的端口,并创建一个新的socket,保证80端口的socket能够继续监听其他的连接

这样一对socket就建立完成了,客户端与服务端就能通过socket进行数据的发送和读取了





三、TCP/IP和Socket的关系

TCP/IP, or the Transmission Control Protocol/Internet Protocol, is a suite of communication protocols used to interconnect network devices on the internet.

TCP/IP也叫做传输控制协议/网络协议,它是一套用来连接互联网上网络设备的协议。那么什么是协议呢?通俗的来说,协议就好比是交通规则,它规划公路上的汽车司机怎么走,它是一套规范。TCP/IP协议就是一套互联网间数据传输的规范。

说了这么多,那socket在哪里呢? 从图中可以看出,Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口(API)。我们所说的TCP/IP网站栈是在操作系统内核实现的,而Socket就是操作系统内核提供给应用层的一系列接口,Socket封装了TCP/IP,要使用TCP/IP来发送数据,就调用Socket的OutputStream,要使用TCP/IP接收数据,就调用Socket的InputStream,现在大家应该对TCP/IP与Socket的关系有所了解了吧





四、Socket的读写缓存区

现在计算机A与计算机B建立了Socket连接,这时候计算机A要发送数据给计算机B,是不是直接就发送过去了呢,答案是NO,Socket发送数据首先需要经过Socket的读写缓冲区,我们现在来了解一下Socket的读写缓冲区

首先我们必须要搞清楚数据发送的流程,用户态的数据,要想发送到互联网上,必须先把数据拷贝到内核态,由内核态帮我们把数据发送出去。 因此,计算机每创建一个socket,cpu就会在内存中为它分配一对读写缓冲区,读写缓冲区在内核态,它的大小不随数据大小而改变。

计算机A想发数据到计算机B,首先计算机A把用户态的数据拷贝到内核态的输出缓冲区,再由把输出缓冲区的数据通过互联网发送到计算机B的输入缓冲区,计算机B把输入缓冲区的数据拷贝到用户态,就完成了一次数据的发送和接收。

由于数据缓冲区的大小有限,如果数据缓冲区里有数据没有发送出去,用户态这时候又有其他数据要发送,数据缓冲区的空间就不够用了,就会造成一系列问题。如果计算机B要接收数据,而一直没有收到计算机A发送过来的数据,导致输入缓冲区一直为空,也会造成问题。

对于上面这些存在的问题,linux有5中解决方案,这就是linux的5大IO模型。

在了解IO模型之前,我们还需要知道什么是文件描述符






五、什么是文件描述符

参考知乎:zhuanlan.zhihu.com/p/105086274

文件描述符(file descriptor)是操作系统内核为了高效管理已被打开的文件所创建的索引,用于指代被打开的文件

在linux操作系统中,每一个进程中都有一个文件描述符表,它是一个指针数组,系统默认初始化了数组的前3位。第0位指向标准的输入流(一般是键盘),第1位指向标准的输出流(一般是显示器),第2位指向标准的错误流(一般是也显示器)。

现在如果有一个进程中只打开了一个 hello.txt 文件,那么这个进程的文件描述符表的第3位就是指向这个 hello.txt 的指针。之后如果该进程创建了一个socket,那么这个文件描述符表的第4位就是指向这个socket的指针,因为在linux中一切皆文件,socket也是一个文件。我们所说的文件描述符就是进程中这个数组的下标,因此他也可以说是一个索引



六、linux实操

前面所讲的socket和文件描述符都是概念,我们可以在linux中通过一些命令来看到这些东西(其实就是这些文件,linux中一切皆文件)

1、查看当前bash的文件描述符

ll /proc/$$/fd

image.png 命令中的 $$ 表示当前bash的进程id号 可以看到当前进程中有0,1,2三个文件描述符,代表标准输入、标准输出、错误输出的入口,这里的255是bash独有的,可以不用管



2、在linux中与www.baidu.com建立socket连接

exec 6<> /dev/tcp/www.baidu.com/80
  • 6是文件描述符,数字任意,不能与存在的数字重复(0,1,2,255)
  • <> 是重定向,< 表示是输入流,> 表示输出流,意思是/dev/tcp/www.baidu.com/80这个文件的文件描述符重定向到6(或者是这个文件的输入输出流执行文件描述符6),根据文件描述符6可以得到这个文件的输入输出流



3、查看socket连接

ll /proc/$$/fd   

这里还是查看进程中的文件描述符,因为socket也是一个文件 image.png 可以看到文件描述符6指向了一个socket,说明与百度的socket建立成功



4、往socket中发请求头,获取百度首页html

echo -e 'GET / HTTP/1.0\n'  1>&  6
  • echo 是在控制台输出的命令,-e 表示字符串中的 \n 自动转为换行
  • 1> 6 是控制台的输出重定向到文件描述符6,就是往百度发数据
  • 1>& 6因为 > 的后面跟的是文件描述符,所以 > 后要加 &



5、查看返回的数据

cat 0<& 6
  • cat是查看文件内容的命令
  • cat 0<& 6 表示文件描述符 6 的内容(socket 读缓冲区中的内容)输入到 cat 的标准输入中,这样就能查看到百度发过来的内容了

image.png 但是并没有返回数据,因为我们操作的时间太长了,socket连接上却并没有发送任何数据,服务器那么超时断开了连接,所以我们只要把上面的步骤操作快点,就可以获取内容了 image.png 这样就得到百度的数据了






对读者说的话

相信大家读了这篇文件对socket和文件描述符有了一定的了解。在这文章开头我说过,因为我是个非科班出身的程序员,没有系统性的学习这些操作系统底层的知识,所以我写了这篇文章来记录我学习到的内容。我认为如果系统的学习这些底层的知识会非常枯燥,没有目的性。我的学习方式就是在学习过程中有遇到底层的知识就进行深入的了解,比方说我学习redis时,需要理解什么是IO多路复用,理解IO多路复用要理解Linux的5大IO模型,理解这些模型又要知道什么是socket和文件描述符等等,然后我对这些进行深入的学习,这样学习更加有目的性,不会出现学了就忘的问题。

下一篇文章,我会基于这些知识点来讲解Linux的五大内存模型,这其中就包括了IO多路复用,大家敬请期待。

文章中如果哪里有问题,欢迎大家纠正,在评论区告诉我,如果觉得文章写得不错,各位赏个赞吧