目录
首先通过对比法来了解两者不同的特性:
下面列出在生产项目中应用 Socket 所需要注意的几点:
的确,Socket 连接在理想的网络环境下是持久的长连接,但实际网络环境是复杂的,网络抖动、路由宕机等各种网络问题都会导致 Socket 连接被动断开。而且 Socket 没有提供「自动重连」的机制,所以解决网络断开重连问题,是 Socket 程序稳定性的重要保证。
思路:在 发送 和 接收 前检查 Socket 连接是否依然生效,若不生效,则重新建立 Socket 连接。
那么首先需要解决的是:如何判断 Socket 连接状态是否 ACTIVE?
NOTE:一般的来说,「判断 Socket 连接状态是否 ACTIVE」都是服务端的功能需求,因为服务端需要以此来作为是否回收连接资源的依据。而客户端则无需特别在意,因为即便断开了连接也只需捕获异常、重新连接、重新发送即可。
别名定义:
一般的 Socket 应用程序逻辑中,ser-socket 应该能够感知到 cli-socket 的断开,并且执行相应的断开逻辑处理,释放相应的 Socket 连接资源。但实际是,ser-socket 无法有效的区分 cli-socket 是处于长时间空闲还是处于 offline 的状态,所以也无法确定 cli-socket 的连接是否已经断开。为解决这个问题,程序员所提出的思路就是,屏蔽「长时间空闲」的场景,让 cli-socket 看起来始终是忙碌的(不断发送「无用包」),直到其静默即表示连接断开。
这就是较为通用的用于保证连接质量的心跳机制,而 cli-socket 发送的无用包也称之为心跳包。所谓心跳包就是 cli-socket 定时发送简单的协议信息给 ser-socket,以此让 ser-socket 知道 cli-socket 依旧 online。相对的, ser-socket 就会认为 cli-socket 已经断开。注意,发包方可以是 cli-socket 也可以是 ser-socket,但出于效率的考虑(减轻服务器压力),一般由 cli-socket 承担。如果是流式 Socket(for TCP),则使用 send 发出;如果是数据报式 Socket(for UDP),则使用 sendto 发出。还有一点需要说明,心跳包实际是一个自定义协议包,由开发者制定,并在 cli-socket 和 ser-socket 中遵守。
NOTE:如果仅为了确定 ser-socket 是否 online,可以用 TCP 协议自带的心跳包,应用 socket.socket.setsockopt 的 SO_KEEPALIVE 属性,来设置发包时间间隔。SO_KEEPALIVE 是操作系统的底层机制,用于维护每一个 TCP 连接。但SO_KEEPALIVE 并不能用于替代心跳机制,因为其仅能确保 ser-socket 一方的连接状态。
以异步(非阻塞)模式建立连接 s.setblocking(0),如果 select 函数返回的值为 1 (表示 Socket 可读),但使用 recv 函数读取到的数据长度为 0,并且 errno != EINTR and errno != EAGAIN,则说明该 Socket 已经断开。
NOTE:需要注意的是,在非阻塞模式下,即便 recv 函数的返回值小于等于 0,依旧不足以证明问题。此时还需要继续判断 errno ?= EINTR,如果 Yes,则说明此次 recv 是由于程序接收到 EINTR 中断信号后返回的,Socket 连接仍然正常。除此之外,如果 write 写的太快,很有可能会把 Buffer 写满,这时的 errno == EAGAIN。根据实际需要,如果 errno == EAGAIN 的话,建议重试几次。当然,Read 也有类似的情况。
如果服务端程序应用了 Session 机制,那么在实现客户端程序时除了需要考虑 Socket 连接的问题之外,还需要考虑 Session 是否过期的问题。
在 发送 和 接收 前首先检查连接是否有效,然后检查会话是否过期:
连接失效:
则重新建立连接,并且重新创建 session
连接有效,但会话过期:
则重新创建 session
连接有效,会话有效:
PASS
选择同步还是异步模式是非常重要的,使用了错误的连接模式将无法达到预期效果。例如高并发需求没能达到,例如程序的稳定性没能提高等等,如何进行选择需要结合实际的应用场景:
NOTE:对于后者,应该尽可以能仅保证 send 和 recv 原子操作是同步的,以此来优化效率。
Socket 的 recv 具有缓存功能,如果其中一方发送的数据量超过了另一方 recv 所允许一次接受的最大数据量,数据会被截短,并将剩余的数据缓冲在接收端。再次调用 recv 时,剩余的数据会从缓冲区取出并删除。这一特性与 HTTP 连接方式有很大区别,表示 Socket 连接无法像 HTTP 连接那般,一次 Request 对应的一次 Response 就能完成一次操作单元,而是很可能需要任意次的 send 以及任意次的 recv 才能完成。
换句话说就是,需要开发者来保证 send 和 recv 的完整性,你需要手动的整理、组合出完整的发送和响应结果数据。经常的,为了得到一个完整的响应结果可能需要进行多次 recv。
调用 Socket 的 close 函数并不会马上断开 Socket 连接,一般的我们会在 close 之前调用 shutdown 函数来确保连接会被正常关闭。而且 shutdown 提供了多种不同的关闭方式:
SHUT_RD:
关闭读,不能使用 read/recv
SHUT_WR:
关闭写,不能使用 send/write
SHUT_RDWR:
关闭读写,不能使用 send/write/recv/read
NOTE:在客户端程序中,一般我们会选择使用 SHUT_WR 方式,立即停止写操作,但可以继续将响应数据读完。而在服务端程序中一般会选择 SHUT_RD 模式,立即停止对客户端请求的读取,但会继续完成响应。当然了,在某些对精度要求不要的场景中,SHUT_RDWR 是不错的选择。
https://blog.51cto.com/u_15301988/5133991
欢迎加入极客星球,分享多年工作经验和技术理解,扩展视野(很多你不知道你不知道的东西),直播分享,一起做项目,经典面试题,帮助有想进大厂(在校大学生校招和社招镀金的同学找到最佳学习路线,针对性突破); 帮助想提高技术实力的,制定技术成长路线,分享各种宝贵的职场经验和人生经验,希望你们站在前辈们的肩膀上。
详细点击查看-> [极客星球] 。
[ IT工程师的成长路线] 这里我正在准备搞一个后端集训营,当前招聘要求越来越高,要想获得高新offer,必须拿出自己的实力,尤其是背景不怎么好的同学,技术实力就是最好的竞争力,但很多知识需要历练才能理解深刻,在项目实战中才能加深理解,所以需要有人指导才行(直播分享技术理解和项目实战),这样才能快速崛起,从上到下打通整个技术链条(从编程语言,算法,应用框架,中间件,到底层内核(Linux内核),甚至到底层硬件等),加强内功修炼(硬件+软件),加强基本功, 让自己上升几个level,这个事本身是很费力的,但我希望尽最大努力帮助大家。
- END -
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8