Linux 项目实战记录

Posted 行码阁119

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux 项目实战记录相关的知识,希望对你有一定的参考价值。

1.阻塞/非阻塞、同步/异步(网络IO)

典型的一次IO的两个阶段是什么?数据就绪和数据读写

数据就绪:根据IO操作的就绪状态

        阻塞 :1、调用IO方法的线程进入阻塞状态,(函数进入waiting函数等待);

        非阻塞:不会改变线程的状态,通过返回值判断(会立即返回);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

1、size == -1

EINTR:不是一种错误 要处理,信号产生,进入中断(中断当前程序),中断回来会返回-1,产生一个EINTR错误号。recv没有数据 也会返回-1(非阻塞法人时候),返回EAGAIN/ EWOULDBLOCK错误号。

2、size == 0

读取到数据末尾了,对方连接关闭了/3、size > 0 读取到了多少的数据

同步:会一直等待结果返回(我觉得这里同步和阻塞是一样的)。

异步:函数调用IO操作后,不管有没有返回值,就去干自己的事了(编程复杂)

怎样理解阻塞非阻塞与同步异步的区别? - 知乎 (zhihu.com) 讲的挺不错的(通知,信号,回调函数)。

char buf[1024];

int len = ssize_t recv(int sockfd, void *buf, size_t len, int flags);

当用的是同步的调用方式时候,会将TCP读缓冲区间的数据加载到buf数组,加载完成后,返回读取成功字节数,此时才会继续执行下一个函数。反之异步,在调用recv函数时,会继续执行下去,只是你需要将socket关键字,以及buf数组传到系统函数里面,数据的拷贝是通过操作系统来完成的,当将读缓冲区的数据全部都复制到buf数组的时候,操作系统会通过通知,信号,回调函数等方式,来告诉主函数。而非阻塞指的是马上调用马上返回一个数值,这个就是和异步的区别。

linux 中异步的io是aio_read(),

aio_write(); 

视屏原话:

一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪” 和 “数据读写”,据就绪阶段分为 阻塞和非阻塞,表现得结果就是,阻塞当前线程或是直接返回。

同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是 由请求方A自己来完成的(不管是阻塞还是非阻塞);异步表示A向B请求调用一个网络IO接口时 (或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以 处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结果。

2、Unix/Linux上的五种IO模型

IO多路复用是同步的还是异步的?epoll(检测数据到达,数据的存取都是在原先进程操作), poll, select。

1、a.阻塞 blocking

        调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必 须等这个函数返回才能进行下一步动作。

2、b.非阻塞 non-blocking(NIO)(Java)

        非阻塞 non-blocking(NIO) 非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调 用总是立即返回,不管事件是否已经发生,若事件没有发生(数据没有到达),则返回-1,此时可以根据 errno 区分这两 种情况,对于accept,recv 和 send,事件未发生时,errno 通常被设置成 EAGAIN

3、c.IO复用(IO multiplexing)

Linux 用 select/poll/epoll 函数实现 IO 复用模型,这些函数也会使进程阻塞,但是和阻塞IO所不同的是 这些函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。直到有数 据可读或可写时,才真正调用IO操作函数。

对于a,b两种IO模型,a模型阻塞,自然不讲。b模型不阻塞,读数据的时候,也会阻塞在哪里。就算读完了,也会有其他情况,阻塞在哪里,其他线程或者进程到达了,也读取不了,单线程和单进程每次就只能检测一个。

4、d.信号驱动(signal-driven)

Linux 用套接口进行信号驱动 IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO事件就绪,进 程收到SIGIO 信号,然后处理 IO 事件。

信号驱动与io复用的区别:

        多路复用是内核们监听多个文件描述符,阻塞在监听的函数比如select, 拷贝数据也是阻塞的,多路复用只是防止进程在某个io阻塞后,不能及时处理其他io的事件。信号驱动则是先登记信号处理函数,当数据准备完毕后由内核发送信号给进程,让进程处理。信号驱动不阻塞在数据准备过程,但阻塞在数据拷贝,所以两者都是同步IO,

5、e.异步(asynchronous)

        Linux中,可以调用 aio_read 函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方 式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。

/* Asynchronous I/O control block. */
struct aiocb

int aio_fildes; /* File desriptor. */
int aio_lio_opcode; /* Operation to be performed. */
int aio_reqprio; /* Request priority offset. */
volatile void *aio_buf; /* Location of buffer. */
size_t aio_nbytes; /* Length of transfer. */
struct sigevent aio_sigevent; /* Signal number and value. */
/* Internal members. */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value;
#ifndef __USE_FILE_OFFSET64
__off_t aio_offset; /* File offset. */
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
__off64_t aio_offset; /* File offset. */
#endif
char __glibc_reserved[32];
;
通常用户使用 Web 浏览器与相应服务器进行通信。在浏览器中键入“域名”或“IP地址:端口号”,浏览器则
先将你的域名解析成相应的 IP 地址或者直接根据你的IP地址向对应的 Web 服务器发送一个 HTTP 请
求。这一过程首先要通过 TCP 协议的三次握手建立与目标 Web 服务器的连接,然后 HTTP 协议生成针
对目标 Web 服务器的 HTTP 请求报文,通过 TCP、IP 等协议发送到目标 Web 服务器上。
4.

3、Web Server(网页服务器)

一个 Web Server 就是一个服务器软件(程序),或者是运行这个服务器软件的硬件(计算机)。其主 要功能是通过 HTTP 协议与客户端(通常是浏览器(Browser))进行通信,来接收,存储,处理来自 客户端的 HTTP 请求,并对其请求做出 HTTP 响应,返回给客户端其请求的内容(文件、网页等)或返 回一个 Error 信息。

 通常用户使用 Web 浏览器与相应服务器进行通信。在浏览器中键入“域名”或“IP地址:端口号”,浏览器则 先将你的域名解析成相应的 IP 地址或者直接根据你的IP地址向对应的 Web 服务器发送一个 HTTP 请 求。这一过程首先要通过 TCP 协议的三次握手建立与目标 Web 服务器的连接,然后 HTTP 协议生成针 对目标 Web 服务器的 HTTP 请求报文,通过 TCP、IP 等协议发送到目标 Web 服务器上。

4.HTTP协议(应用层的协议)

超文本传输协议(Hypertext Transfer Protocol,HTTP)是一个简单的请求 - 响应协议,它通常运行在 TCP 之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的 头以 ASCII 形式给出;而消息内容则具有一个类似 MIME 的格式。

HTTP是万维网的数据通信的基础。

HTTP的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万 维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC,其中最著名的是1999年6月公布的 RFC 2616,定 义了HTTP协议中现今广泛使用的一个版本——HTTP 1.1。

4.1 概述

HTTP 是一个客户端终端(用户)和服务器端(网站)请求和应答的标准(TCP)。通过使用网页浏览 器、网络爬虫或者其它的工具,客户端发起一个HTTP请求到服务器上指定端口(默认端口为80)。我们 称这个客户端为用户代理程序(user agent)。应答的服务器上存储着一些资源,比如 html 文件和图 像。我们称这个应答服务器为源服务器(origin server)。在用户代理和源服务器中间可能存在多个“中 间层”,比如代理服务器、网关或者隧道(tunnel)。

尽管 TCP/IP 协议是互联网上最流行的应用,HTTP 协议中,并没有规定必须使用它或它支持的层。事实 上,HTTP可以在任何互联网协议上,或其他网络上实现。HTTP 假定其下层协议提供可靠的传输。因 此,任何能够提供这种保证的协议都可以被其使用。因此也就是其在 TCP/IP 协议族使用 TCP 作为其传 输层。

通常,由HTTP客户端发起一个请求,创建一个到服务器指定端口(默认是80端口)的 TCP 连接。HTTP 服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比 如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息。

4.2 工作原理

HTTP 协议定义 Web 客户端如何从 Web 服务器请求 Web 页面,以及服务器如何把 Web 页面传送给客 户端。HTTP 协议采用了请求/响应模型客户端向服务器发送一个请求报文,请求报文包含请求的方 法、URL、协议版本、请求头部和请求数据。服务器以一个状态行作为响应,响应的内容包括协议的版 本、成功或者错误代码、服务器信息、响应头部和响应数据。

研究http就是研究http报文的格式

以下是 HTTP 请求/响应的步骤:

        1. 客户端连接到 Web 服务器

        一个HTTP客户端,通常是浏览器,与 Web 服务器的 HTTP 端口(默认为 80 )建立一个 TCP 套接 字连接。例如,http://www.baidu.com。(URL)

        2. 发送 HTTP 请求

        通过 TCP 套接字,客户端向 Web 服务器发送一个文本的请求报文,一个请求报文由请求行、请求 头部、空行和请求数据 4 部分组成

        3. 服务器接受请求并返回 HTTP 响应

        Web 服务器解析请求,定位请求资源。服务器将资源复本写到 TCP 套接字,由客户端读取。一个 响应由状态行、响应头部、空行和响应数据 4 部分组成。

        4. 释放连接 TCP 连接

        若 connection 模式为 close,则服务器主动关闭 TCP连接,客户端被动关闭连接,释放 TCP 连 接;若connection 模式为 keepalive,则该连接会保持一段时间,在该时间内可以继续接收请求;

        5. 客户端浏览器解析 HTML 内容

        客户端浏览器首先解析状态行,查看表明请求是否成功的状态代码。然后解析每一个响应头,响应 头告知以下为若干字节的 HTML 文档和文档的字符集。客户端浏览器读取响应数据 HTML,根据 HTML 的语法对其进行格式化,并在浏览器窗口中显示。

例如:在浏览器地址栏键入URL,按下回车之后会经历以下流程:

1. 浏览器向 DNS 服务器请求解析该 URL 中的域名所对应的 IP 地址;
2. 解析出 IP 地址后,根据该 IP 地址和默认端口 80,和服务器建立 TCP 连接;
3. 浏览器发出读取文件( URL 中域名后面部分对应的文件)的 HTTP 请求,该请求报文作为 TCP 三
次握手的第三个报文的数据发送给服务器;
4. 服务器对浏览器请求作出响应,并把对应的 HTML 文本发送给浏览器;
5. 释放 TCP 连接;
6. 浏览器将该 HTML 文本并显示内容。

 HTTP 协议是基于 TCP/IP 协议之上的应用层协议,基于 请求-响应 的模式。HTTP 协议规定,请求从客 户端发出,最后服务器端响应该请求并返回。换句话说,肯定是先从客户端开始建立通信的,服务器端 在没有接收到请求之前不会发送响应。

4.3 HTTP 请求报文格式

5、服务器的基本框架

虽然服务器程序种类繁多,但其基本框架都一样,不同之处在于逻辑处理。

 

         I/O 处理单元是服务器管理客户连接的模块。它通常要完成以下工作:等待并接受新的客户连接,接收客户数据,将服务器响应数据返回给客户端。但是数据的收发不一定在 I/O 处理单元中执行,也可能在逻辑单元中执行,具体在何处执行取决于事件处理模式。
        一个逻辑单元通常是一个进程或线程。它分析并处理客户数据,然后将结果传递给 I/O 处理单元或者直接发送给客户端(具体使用哪种方式取决于事件处理模式)。服务器通常拥有多个逻辑单元,以实现对多个客户任务的并发处理。
        网络存储单元可以是数据库、缓存和文件,但不是必须的。请求队列是各单元之间的通信方式的抽象。I/O 处理单元接收到客户请求时,需要以某种方式通知一个逻辑单元来处理该请求。同样,多个逻辑单元同时访问一个存储单元时,也需要采用某种机制来协调处理竞态条件。请求队列通常被实现为池的一部分。

6.两种高效的事件处理模式

服务器程序通常需要处理三类事件:I/O 事件、信号及定时事件。有两种高效的事件处理模式:Reactor 和 Proactor,同步 I/O 模型通常用于实现 Reactor 模式异步 I/O 模型通常用于实现 Proactor 模式

6.1 Reactor模型

要求主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话就立即将该事件通知工作 线程(逻辑单元),将 socket 可读可写事件放入请求队列,交给工作线程(一般是子线程)处理。除此之外,主线程不做 任何其他实质性的工作。读写数据,接受新的连接,以及处理客户请求均在工作线程中完成。

使用同步 I/O(以 epoll_wait 为例)实现的 Reactor 模式的工作流程是:

        1. 主线程往 epoll 内核事件表中注册 socket 上的读就绪事件。
        2. 主线程调用 epoll_wait 等待 socket 上有数据可读。
        3. 当 socket 上有数据可读时, epoll_wait 通知主线程。主线程则将 socket 可读事件放入请求队列。
        4. 睡眠在请求队列上的某个工作线程被唤醒,它从 socket 读取数据,并处理客户请求,然后往 epoll内核事件表中注册该 socket 上的写就绪事件。
        5. 当主线程调用 epoll_wait 等待 socket 可写。
        6. 当 socket 可写时,epoll_wait 通知主线程。主线程将 socket 可写事件放入请求队列。
        7. 睡眠在请求队列上的某个工作线程被唤醒,它往 socket 上写入服务器处理客户请求的结果。

 6.2 Proactor模式

Proactor 模式将所有 I/O 操作都交给主线程和内核来处理(进行读、写),工作线程仅仅负责业务逻 辑。使用异步 I/O 模型(以 aio_read 和 aio_write 为例)实现的 Proactor 模式的工作流程是:

1. 主线程调用 aio_read 函数向内核注册 socket 上的读完成事件,并告诉内核用户读缓冲区的位置, 以及读操作完成时如何通知应用程序(这里以信号为例)。

2. 主线程继续处理其他逻辑。

3. 当 socket 上的数据被读入用户缓冲区后,内核将向应用程序发送一个信号,以通知应用程序数据 已经可用。

4. 应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求 后,调用 aio_write 函数向内核注册 socket 上的写完成事件,并告诉内核用户写缓冲区的位置,以 及写操作完成时如何通知应用程序。

5. 主线程继续处理其他逻辑。

6. 当用户缓冲区的数据被写入 socket 之后,内核将向应用程序发送一个信号,以通知应用程序数据已经发送完毕。

7. 应用程序预先定义好的信号处理函数选择一个工作线程来做善后处理,比如决定是否关闭 socket。

 

7 线程池

线程池是由服务器预先创建的一组子线程,线程池中的线程数量应该和 CPU 数量差不多。线程池中的所 有子线程都运行着相同的代码。当有新的任务到来时,主线程将通过某种方式选择线程池中的某一个子 线程来为之服务。相比与动态的创建子线程,选择一个已经存在的子线程的代价显然要小得多。至于主 线程选择哪个子线程来为新任务服务,则有多种方式:

        1、主线程使用某种算法来主动选择子线程。最简单、最常用的算法是随机算法和 Round Robin(轮流选取)算法,但更优秀、更智能的算法将使任务在各个工作线程中更均匀地分配,从而减轻服务器的整体压力。
       2、主线程和所有子线程通过一个共享的工作队列来同步,子线程都睡眠在该工作队列上。当有新的任务到来时,主线程将任务添加到工作队列中。这将唤醒正在等待任务的子线程,不过只有一个子线程将获得新任务的”接管权“,它可以从工作队列中取出任务并执行之,而其他子线程将继续睡眠在工作队列上。

同一时刻,有一万个客服端来了,就需要申请一万个线程池,线程数量太多,消耗的资源,来一个客户端,就需要创建一个线程,线程创建时间和线程消耗时间。解决这个问题,是通过线程池, 容器里面的线程是固定数量的,来一个客户端,就去池子里面去找一个空闲的线程去服务。

 

线程池中的线程数量最直接的限制因素是中央处理器(CPU)的处理器(processors/cores)的数量
N :如果你的CPU是4-cores的,对于CPU密集型的任务(如视频剪辑等消耗CPU计算资源的任务)来
说,那线程池中的线程数量最好也设置为4(或者+1防止其他因素造成的线程阻塞);对于IO密集
型的任务,一般要多于CPU的核数,因为线程间竞争的不是CPU的计算资源而是IO,IO的处理一
般较慢,多于cores数的线程将为CPU争取更多的任务,不至在线程处理IO的过程造成CPU空闲导
致资源浪费。

        1、空间换时间,浪费服务器的硬件资源,换取运行效率。

        2、池是一组资源的集合,这组资源在服务器启动之初就被完全创建好并初始化,这称为静态资源。

         3、当服务器进入正式运行阶段,开始处理客户请求的时候,如果它需要相关的资源,可以直接从池中 获取,无需动态分配。

        4、当服务器处理完一个客户连接后,可以把相关的资源放回池中,无需执行系统调用释放资源。

以上是关于Linux 项目实战记录的主要内容,如果未能解决你的问题,请参考以下文章

Go短网址项目实战---下

Elasticsearches 7.8.0 唐诗三百首写入 Elasticsearch 会发生什么

IdentityServer4实战 - 必须使用HTTPS问题解析

Linux脚本实战之检测网卡流量

java web 前端实战项目记录

SpringBoot搭建聚合项目-实战记录01