C/C++教程

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

本文主要是介绍什么是socket?什么是文件描述符?非科班程序员告诉你!,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

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

 

絮叨

随着互联网行业的蓬勃发展,市场对于程序员的需求激增,尤其是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

二、socket建立连接的过程

 

 

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

 

 

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

 

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

三、TCP/IP和Socket的关系

 

 

 

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模型之前,我们还需要知道什么是文件描述符

五、什么是文件描述符

 

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

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

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

这是自己总结的:通过上面可以知道,我们的80端口创建一个socket,socket也是文件,那么这个进程的文件描述符表中又加1,那么一直加到1024,应为我们的系统限制一个进程最多只能打开1024个文件。

IO复用

IO复用的历史和多进程一样长,Linux很早就提供了select系统调用,可以在一个进程内维护1024个连接,后来加入poll系统调用,poll做了一系列改进后解决了1024个连接的限制问题,可以维持任意数量的连接。但是selectpoll存在一个问题是,它们需要循环检测连接是否有事件。这样问题就来了,如果服务器有100w个连接,在某一时间只有一个连接是向服务器发送了数据,select/poll就需要做100w次循环,而其中只会有1次命中,剩下99w9999次都是无效的,白白浪费CPU时间片资源。

直到Linux2.6内核开始提供新的epoll系统调用,可以维持无限数量的连接,而且无需轮询,这才真正解决了C10K问题。现在各种高并发异步IO的服务器程序都是基于epoll实现的,如Nginx、Node.js、Erlang、Golnag。像Node.js这样单进程单线程的程序,都可以维持超过100wTCP连接,这全部都要归功于epoll技术。

应为我们的80端口主进程最多只能生成1024个socket[socket其实就是连接],所以使用select模型,不管你生成多少子进程,再多的连接主进程都不能创建socket了,它只会把这1024 socket连接分配给子进程。

还有就是在select模型下,这些socket是在select调用前就有的,然后调用select监控socket,也就是select(socket),此时我们的进程是处于阻塞的,但是select一直在遍历,当发现某个socket有数据了,此时就通知进程,然后进程就发送系统调用,此时该进程就发送read系统调用来读数据。这个read也要遍历这些所有socket才能取得想要数据。

 

poll本质上和select没有区别,与select的区别的是他没有最大连接数的限制,原因是他是基于链表来存储的。如果它一个进程内维护100w个连接,那poll遍历也需要很多时间。然后它再通知进程有一个socket有消息了,那么这个进程也遍历100w个socket,此时也需要不少时间。

epoll和poll类似,不限制连接数,但是epoll模型不需要遍历,当某个socket有数据的时候,直接通知进程,然后进程直接去那个socket中取。这时进程也不用遍历了。

如果把上面的都理解为单进程单线程,那么其实就是这个线程处理上面的socket内容,每个socket其实就是一个请求,所以io复用能处理多请求

 

1Blocking IO

在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:

第一步通常涉及等待数据从网络中到达。当所有等待数据到达时,它被复制到内核中的某个缓冲区。

第二步就是把数据从内核缓冲区复制到应用程序缓冲区。

 

 

当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整 个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除 block的状态,重新运行起来。

所以,blocking IO的特点就是在IO执行的两个阶段都被block了。

 

2、非阻塞式I/O

linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:

 

 

从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。 从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次 发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。

所以,用户进程第一个阶段不是阻塞的,需要不断的主动询问kernel数据好了没有;第二个阶段依然总是阻塞的。

 

IO多路复用

 

IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。

IO复用同非阻塞IO本质一样,不过利用了新的select系统调用,由内核来负责本来是请求进程该做的轮询操作。看似比非阻塞IO还多了一个系统调用开销,不过因为可以支持多路IO,才算提高了效率。

它的基本原理就是select /epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:

 

 

当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个 socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。

这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用 select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。

select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。比如epoll可以处理无限个连接

在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被 block的。只不过process是被select这个函数block,而不是被socket IO给block。

讲下我的理解。
多路复用这个词多出现在网络编程,首先理解多路.
多路:有多个客户端连接,一路就是一个连接
复用:一个进程或线程处理上面所有的连接。如果不复用又需要同时服务多个客户端,需要多线程或多进程,这个就不是复用了。

这篇关于什么是socket?什么是文件描述符?非科班程序员告诉你!的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!