Linux 长连接:稳定高效的网络通信方式 (linux 长连接)

随着互联网技术的不断发展,网络通信在人们的日常生活中扮演着至关重要的角色。在现代化的网络通信中,长连接一直是一种非常常见的通信方式。Linux作为一个开源的操作系统,其提供的长连接也一直备受人们青睐。本文将详细介绍Linux长连接的概念、原理、优点和应用,并对几个常见的长连接实现进行了详细分析。

一、Linux长连接的概念

Linux长连接是指在一个TCP连接上保持连续不断的信息传输,直到通信结束或有特别的指示。相对于短连接,长连接在进行网络通信时,不需要频繁地建立和关闭连接,从而节省了资源和时间。在Linux中,长连接通常通过socket实现,可以实现可靠的数据传输。

二、Linux长连接的原理

长连接的实现原理是在TCP连接应用层与传输层之间增加一个心跳机制。当有数据需要发送时,先发送心跳包,告诉对方连接还在,然后再发送数据。如果在一段时间内没有数据发送,就会发送一个更长时间的心跳包,保持连接的有效性。这样就可以避免服务端在一些可能很短暂的空闲时间内关闭连接,以避免资源浪费。

三、Linux长连接的优点

1.稳定性

长连接提供了更稳定的通信方式,不需要频繁地建立和关闭连接。相对于短连接,长连接更不容易出现网络中断和断线等问题。

2.效率

长连接可以实现数据在TCP连接上连续不断地传输,减少了因建立和关闭连接引起的成本,同时也减少了网络带宽的消耗。在高并发和高负载的情况下,使用长连接可以提高通信效率。

3.灵活性

长连接还可以通过控制心跳包的发送时间调整连接的灵活性。根据具体应用场景,可以灵活地调整心跳包的发送间隔,以更好地适应网络环境。

四、Linux长连接的应用

1.聊天应用

在聊天应用中,长连接可以实现即时通信的功能。当用户发送一条消息时,可以通过长连接让服务器实时地将消息推送给对方。使用长连接,可以避免用户频繁请求服务器,实现更优秀的体验。

2.直播应用

在直播应用中,长连接可以实现实时的直播弹幕和在线人数等功能。当用户观看一场直播时,可以通过长连接实时地推送弹幕和当前在线人数等信息。

3.物联网应用

在物联网应用中,长连接可以实现设备之间的实时通信。当一个设备的状态发生变化时,可以通过长连接实时地将数据推送到监控设备或其他相关设备。

五、常见的Linux长连接实现

1. Nginx

Nginx是一个高性能Web服务器,其也支持长连接技术。通过Nginx提供的keepalive模块,可以实现高效的长连接应用。

2. WebSocket

WebSocket是一种基于HTTP的协议,可以实现浏览器和服务器之间的双向通信。通过WebSocket,可以轻松实现长连接技术,其使用长连接进行通信。

3. Redis

Redis是一个支持缓存、消息队列、发布订阅等功能的存储系统。在Redis中,也可以实现长连接技术,并通过发布订阅机制实现实时通信。

Linux长连接是一种稳定高效的网络通信方式,其在现代网络通信中扮演着非常重要的角色。通过充分理解Linux长连接的概念、原理、优点和应用,可以实现更高效、灵活、稳定的网络通信。

相关问题拓展阅读:

  • 基于事件驱动的高性能开源网络库libevent介绍及安装
  • 畅谈linux下TCP(上)

基于事件驱动的高性能开源网络库libevent介绍及安装

libevent是一个轻量级的基于事件驱动的高性能的开源网络库,并且支持多个平台,对多个平台的I/O复用技术进行了封装,当我们编译库的代码时,编译的脚本将会根据OS支持的处理事件机制,来编译相应的代码,从而在libevent接口上保持一致。

在当前的服务器上,面对的主要问题就是要能处理大量的连接。而通过libevent这个网络库,我们就可以调用它的API来很好的解决上面的问题。首先,可以来回顾一下,对这个问题的传统解决方法。

问题:

如何处理多个客户端连接

解决方案1:

I/O复用技术

这几种方式都是同步I/O,即当读写事件就绪,他们自己需要负责进行读写,这个读写过程是阻塞的,而异步I/O则不需要自己负责读写,只需要通知负责读写的程序就可以了。

解决方案2:

多线程技术或多进程技术

多线程技术和多进程技术也可以处理高并发的数据连接,因为在服务器中可以产生大量的进程和线程和处理我们需要监视的连接。但是,这两种方式也是有很大的局限性的,比如多进程模型就不适合大量的短连接,因为进程的产生和关闭需要消耗较大的系统性能,同样,还要进程进程间的通信,在CPU性能不足的情况下不太适合。而多线程技术则不太适合处理长连接,因为当我们建立一个进程时,linux中会消耗8G的栈空间,如果我们的每个连接都杵着不断开,那么大量连接长连接后,导致的结果就是内存的大量消耗。

解决方案3:

常用的上述二者复合使用

上述的两种方法各具有优缺点,因此,我们可以将上述的方法结合起来,这也是目前使用较多的处理高并发的方法。多进程+I/O复用或者多线程+I/O复用。而在具体的实现上,又可以分为很多的方式。比如多线程+I/O复用技术,我们使春旁轿用使用一个主线程负责监听一个端口和接受的描述符是否有读写事件产生,如果有,则将事件分发给其他的工作进程去完启吵成,这也是进程池的理念。

在说完上述的高并发的处理方法之后,我们可以来介绍一个libevent的主要特色了。

同样,lievent也是采用的上述系统提供的select,poll和epoll方法来进行I/O复用,但是针对于多个系统平台上的不同的I/O复用实现方式,libevent进行了重新的封装,并提供了统一的API接口。libevent在实现上使用了事件驱动这种机制,其本质上是一种Reactor模式。

在Libevent中也是一样,向Libevent框架注册相应的事件和回调函数;当这些事件发生时,Libevent会调用这些回调函数处理相应的事件。

lbevent的事件支持三种,分别是网络IO、定时器和信号。定时器的数据结构使用最小堆(Min Heap),以提高效率。网络IO和信号的数据结构采用了双向链表(TAILQ)。

更多linux内核视频教程文本资料免费获取后台私信【内核】。

libevent的安装很简单,我是直接从github上clone下一个源码,然后进行编译安装的。

具体的命令是(假设扒肆你已经安装了git):

现在的libevent版本已经到达libevent2了,其增加了多线程的支持,API函数也发生了一些微小的变化。

如果你想知道更多的API使用情况,请点击这里。

下面,就基于libevent2编写一个聊天室服务器。

设计思想:

首先创建一个套接字,进而创建一个事件对此端口进行监听,将所请求的用户组成一个队列,并监听所有的用户事件,当某个用户说话了,产生了读事件,就将该用户的发言发送给队列中的其他用户。

程序分析

需要包含的libevent函数头:

创建一个client结构体,接受连接后存放数据:

先来看下mian函数的处理:

首先,函数初始化了一个用户队列tailq,接着创建了一个socket套接字,并将套接字设定为非阻塞模式,接着对一个全局的evbase事件,注册了事件,事件源是listen_fd,回调函数是on_accept,事件发生的情况是EV_READ,而且标志EV_PESIST表明该事件一直存在,而后开启事件扫描循环event_base_dispatch(evbase)。

再看一下回调函数on_accpet实现:

这个回调函数的作用很显然,就是接受了一个客户端的请求,并申请好了一个client信息,将需要的内容填写好,在填写中需要注意的是,又向上述的事件集evbase中注册了一个bufferevent事件client->buf_ev,并注册了回调函数buffered_on_read,buffered_on_error,这三个函数分别是当接受后的连接发生了读或者错误事件后的执行函数。接着,将用户的client结构放入了用户的队列tailq中去。

用户的buffer可读后的执行函数:

执行函数的作用很明显,将libevent管理中的buffer数据读取出,存入本地的data数组内,然后对队列中的client进行检索,如果不是发数据的client,则将数据写入该client的buffer中,发送给该用户。这里注意的是需要反复读取buffer中的数据,防止一个读取并没有读取干净,直到读取不到数据为止。

buffer出错处理函数和上述函数差不多,功能就是出错后,结束掉保存的client结构,详细就不说了。

编译的时候记得修改Makefile中Libevent文件夹的位置

设计思想:

所谓回显服务器就是将客户端发过来的数据再发回去,这里主要也就是说明libevent的纯IO复用实现。实现方法和上面的差不多,甚至可以说更加简单。

程序和上面的聊天服务器差不多,只是在buffer可读的事件函数中,不是将用户的数据发送给其他用户,而是直接发送给用户本身。

设计思想:

上面的方法单纯使用libevent的简单函数来实现服务,但是这里,我们假设我们需要处理的客户端很少,于是我们可以使用对于每个连接我们分配一个线程这样的方式来实现对用户的服务。这种方式简单有效,一对一服务,就算业务逻辑出现阻塞也不怕。

程序分析

首先定义了一些数据结构,worker数据结构定义的是一个工作者,它包含有一个工作线程,和结束标志,需要获取的工作队列,和建立链表需要的指针。job数据结构定义的是操作一个job的方法和对象,这回到程序中,实际上就是指的是事件发生后,封装好的client结构体和处理这个结构体的方法。workqueue数据结构指的是当前的工作队列中的工作者,以及工作队列中的待完成的工作,以及互斥锁和条件变量(因为多个工作进程需要访问这些资源)。

具体的流程就是,用一个主线程监听一个套接字,并将套接字接受到的连接accept,并创建一个client数据结构保存该连接的信息,在这个client结构中注册一个bufferevent事件,注册到client->evbase上(这时候这是向client中的evbase注册了一个事件还没有进行循环这个事件集)。

接着,当监听到某个client有bufferevent事件发生,主线程就把该client结构体和需要进行的工作方法包装成一个job结构,然后把这个job扔到workqueue上去,并通知各个工作者。而后,各个工作者开着的线程就被激活了,疯狂地去workqueue上去抢工作做,某个worker拿到工作后,就可以解包job,根据job的工作说明书(job_function)操作工作对象(client)了。这里,job的工作说明有是循环client中的client->evbase,于是这样线程就会一直去监视这个连接的状态,如果有数据就这会调用回调函数进行处理。同时,这个线程也就是阻塞在这里,这对这一个连接负责。

建立workqueue需要的结构体和函数有:

主线程的on_accept函数为:

job中的工作指南为:

设计思想:

假设我们的用户很多,高并发,长连接,那么我们还是来用I/O复用和线程池实现吧,用一个控制线程通过I/O复用负责监听和分发事件,用一组线程池来进行处理事件,这样就可以灵活地将控制逻辑和业务逻辑分开了,见下述讲解。

程序分析

具体的流程和上面的差不多,用一个主线程监听一个套接字,并将套接字接受到的连接accept,并创建一个client数据结构保存该连接的信息,在这个client结构中注册一个bufferevent事件,但是这里,将事件注册到accept_evbase中,仍然用主线程进行监听。

而面对监听后出现的事件,将client和操作client的方法打包成一个job,放到上述的workqueue中去,让工作进程来完成。这样的操作和上述的差别在于上述方法将bufferevent注册到client中的evbase中,用工作线程监听,而本方法用主线程监听,工作线程负责处理监听产生的事件。

这要的差别在于两个函数 on_accept函数:

在buffered_on_read中,提交job。

在job工作指南server_job_function中就可以做你工作该做的事儿了,根据发来的信息进行数据库处理,http返回等等。

畅谈linux下TCP(上)

tcp 协议 是互联网中最常用的协议 , 开发人员基本上天天和它打交道,对它进行深入了解。 可以帮助我们排查定位bug和进行程序优化。下面我将就TCP几个点做深入的探讨

客户端:收到 ack 后 分配连接资源。 发送数据

服务器 : 收到 syn 后立即 分配连接资源

客户端:收到ACK, 立即分配资源

服务器:收到ACK, 立即分配资源

既然三次握手也液银颤不是100%可靠, 那四次,五次,六次。。。呢? 其实都一样,不管多少次都有丢包问题。

client 只发送一个 SYN, server 分配一个tcb, 放入syn队列中。 这时候连接叫

半连接

状态;如果server 收不到 client 的ACK, 会不停重试 发送 ACK-SYN 给client 。重试间隔 为 2 的 N 次方 叠加(2^0 , 2^1, 2^2 ….);直至超时才释放syn队列中的这个 TCB;

在半连接状态下, 一方面会占用队列配额资源,另一方面占用内存资源。我们应该让半连接状态存在时间尽可能的小

当client 向一个未打开的端口发起连接请求时,会收到一个RST回复包

当listen 的 backlog 和 somaxconn 都设置了得时候, 取两者min值

Recv-Q 是accept 队列当前个数, Send-Q 设置更大值

这种SYN洪水攻击是一种常见攻击方式,就是利用半连接队列特性,占满syn 队列的 资源,导致 client无法连接上。

解决方案:

为什么不像握手那样合并成三次挥手? 因为和刚开始连接情况,连接是大家都从0开始, 关闭时有历史包袱的。server(被动关闭方) 收到 client(主动关闭方) 的关闭请求FIN包。 这时候可能还有未发送完的数据,不能丢弃。 所以需要分开。事实可能是这样

当然,在没有待发数据,并且允许 Delay ACK 情况下, FIN-ACK合并还是非常常见的事情,这是三次挥手是可以的。

同上

CLOSE_WAIT 是被动关闭方才有的状态

被动关闭方 到 期间的状态为 CLOSE_WAIT, 这个状态仍然能发闹败送数据。 我们叫做

半关闭

, 下面用个例子来分析:

这个是我实际生产环境碰到的一个问题,长连接会话场景,server端收到client的rpc call 请求1,处理发现请求包有问题,就强制关闭结束这次会话, 但是 因为client 发送 第二次请求之前,并没有去调用recv,所以并不知道 这个连接被server关闭, 继续发送 请求2 , 此时是半连接,能够成功发送到对端机器,但是recv结果后,搏兆遇到连接已经关闭错误。

如果 client 和 server 恰好同时发起关闭连接。这种情况下,两边都是主动连接,都会进入 TIME_WAIT状态

1、

被动关闭方在LAST_ACK状态(已经发送FIN),等待主动关闭方的ACK应答,但是 ACK丢掉, 主动方并不知道,以为成功关闭。因为没有TIME_WAIT等待时间,可以立即创建新的连接, 新的连接发送SYN到前面那个未关闭的被动方,被动方认为是收到错误指令,会发送RST。导致创建连接失败。

2、

主动关闭方断开连接,如果没有TIME_WAIT等待时间,可以马上建立一个新的连接,但是前一个已经断开连接的,延迟到达的数据包。 被新建的连接接收,如果刚好seq 和 ack字段 都正确, seq在滑动窗口范围内(只能说机率非常小,但是还是有可能会发生),会被当成正确数据包接收,导致数据串包。 如果不在window范围内,则没有影响( 发送一个确认报文(ack 字段为期望ack的序列号,seq为当前发送序列号),状态变保持原样)

TIME_WAIT 问题比较比较常见,特别是CGI机器,并发量高,大量连接后段服务的tcp短连接。因此也衍生出了多种手段解决。虽然每种方法解决不是那么完美,但是带来的好处一般多于坏处。还是在日常工作中会使用。

1、改短TIME_WAIT 等待时间

这个是之一个想到的解决办法,既然等待时间太长,就改成时间短,快速回收端口。但是实际情况往往不乐观,对于并发的机器,你改多短才能保证回收速度呢,有时候几秒钟就几万个连接。太短的话,就会有前面两种问题小概率发生。

2、禁止Socket lingering

这种情况下关闭连接,会直接抛弃缓冲区中待发送的数据,会发送一个RST给对端,相当于直接抛弃TIME_WAIT, 进入CLOSE状态。同样因为取消了 TIME_WAIT 状态,会有前面两种问题小概率发生。

3、tcp_tw_reuse

net.ipv4.tcp_tw_reuse选项是 从 TIME_WAIT 状态的队列中,选取条件:1、remote 的 ip 和端口相同, 2、选取一个时间戳小于当前时间戳; 用来解决端口不足的尴尬。

现在端口可以复用了,看看如何面对前面TIME_WAIT 那两种问题。 我们仔细回顾用一下前面两种问题。

都是在新建连接中收到老连接的包导致的问题

, 那么如果我能在新连接中识别出此包为非法包,是不是就可以丢掉这些无用包,解决问题呢。

需要实现这些功能,需要扩展一下tcp 包头。 增加 时间戳字段。 发送者 在每次发送的时候。 在tcp包头里面带上发送时候的时间戳。 当接收者接收的时候,在ACK应答中除了TCP包头中带自己此时发送的时间戳,并且把收到的时间戳附加在后面。也就是说ACK包中有两个时间戳字段。结构如下:

那我们接下来一个个分析tcp_tw_reuse是如何解决TIME_WAIT的两个问题的

4、tcp_tw_recycle

tcp_tw_recycle 也是借助 timestamp机制。顾名思义, tcp_tw_reuse 是复用 端口,并不会减少 TIME-WAIT 数量。你去查询机器上TIME-WAIT 数量,还是 几千几万个,这点对有强迫症的同学感觉很不舒服。tcp_tw_recycle 是 提前 回收 TIME-WAIT资源。会减少 机器上 TIME-WAIT 数量。

关于linux 长连接的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。

版权声明:本文采用知识共享 署名4.0国际许可协议 [BY-NC-SA] 进行授权
文章名称:《Linux 长连接:稳定高效的网络通信方式 (linux 长连接)》
文章链接:https://zhuji.vsping.com/30779.html
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。