博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
菜鸟学网络之 —— 长连接和短连接
阅读量:6291 次
发布时间:2019-06-22

本文共 9621 字,大约阅读时间需要 32 分钟。

本文参考文章:

  • (推荐阅读)

目录

1. Http协议与TCP/IP 协议的关系

HTTP的长连接和短连接本质上是TCP长连接和短连接。HTTP属于应用层协议,在传输层使用TCP协议,在网络层使用IP协议。IP协议主要解决网络路由和寻址问题,TCP协议主要解决如何在IP层之上可靠的传递数据包,使在网络上的另一端收到发端发出的所有包,并且顺序与发出顺序一致。TCP有可靠,面向连接的特点。

2. Http/TCP/Socket连接

2.1 Http连接

Http协议,即超文本传输协议,是Web联网的基础。Http协议是建立在TCP协议之上的一种应用。Http协议负责如何包装数据,而TCP协议负责如何传输数据。因此,如果只有TCP协议,那么将无法解析传输过来的数据。

HTTP连接最显著的特点是客户端发送的每次请求都需要服务器回送响应,在请求结束后,会主动释放连接。从建立连接到关闭连接的过程称为“一次连接”。

1)在HTTP 1.0中,客户端的每次请求都要求建立一次单独的连接,在处理完本次请求后,就自动释放连接,这是一种“短连接”。

2)在HTTP 1.1中则可以在一次连接中处理多个请求,并且多个请求可以重叠进行,不需要等待一个请求结束后再发送下一个请求,这是一种“长连接”。在Http 1.1 中只需要在请求头配置keep-alive : true即可实现长连接。此时,服务端返回的请求头中会有 connection : keep-alive 表明这是一个长连接。

2.2 TCP连接

手机能够使用联网功能是因为手机底层实现了TCP/IP协议,可以使手机终端通过无线网络建立TCP连接。TCP协议可以对上层网络提供接口,使上层网络数据的传输建立在“无差别”的网络之上。

TCP连接需要经过“三次握手”,断开连接需要经过“四次挥手”。

2.3 Socket连接

2.3.1 Socket的定义

Socket,即套接字,是支持TCP/IP协议的网络通信的基本操作单元。它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

2.3.2 Socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

  • 服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。

  • 客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

  • 连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

2.4 Socket连接和TCP连接的关系

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接

总结:socket是对TCP/IP协议的封装和应用(程序员层面上),它提供了一组基本的函数接口(比如:create、listen、accept等),使得程序员更方便地使用TCP/IP协议栈。

TCP/IP只是一个协议栈,就像操作系统的运行机制一样,必须要具体实现,同时还要提供对外的操作接口。

2.5 Socket连接和Http连接的关系

Socket连接一般情况下都是TCP连接,因此Socket连接一旦建立,通信双方就可以进行互相发送内容。但在实际网络应用中,客户端到服务器之间的通信往往需要穿越多个中间节点,例如路由器、网关、防火墙等,大部分防火墙默认会关闭长时间处于非活跃状态的连接而导致 Socket 连接断连,因此需要通过轮询告诉网络,该连接处于活跃状态。(这也就是常说的“心跳策略”

Http连接是**“请求-响应”**的方式,不仅在请求时需要先建立连接,而且需要客户端向服务器发出请求后,服务器端才能回复数据。

总结:如果建立的是Socket连接,服务器可以直接将数据传送给客户端;如果方建立的是HTTP连接,则服务器需要等到客户端发送一次请求后才能将数据传回给客户端。

3. Http长连接和短连接

长连接: 客户端和服务端建立连接后不进行断开,之后客户端再次访问这个服务器上的内容时,继续使用这一条连接通道。

短连接: 客户端和服务端建立连接,发送完数据后立马断开连接。下次要取数据,需要再次建立连接。

在HTTP/1.0中,默认使用的是短连接。但从 HTTP/1.1起,默认使用长连接。

4 Http长连接和TCP长连接的区别

Http长连接 和 TCP长连接的区别在于: TCP 的长连接需要自己去维护一套心跳策略。,而Http只需要在请求头加入keep-alive:true即可实现长连接。

5 手写一次TCP长连接

思路: (1) 服务端就只需要编写一个读线程,不断读取来自客户端的消息,并打印出来即可 (2) 客户端需要开启两个定时器,一个是用来模拟发送普通消息,一个用来模拟发送心跳包 (3) 服务端和客户端之间有协议,用来标识什么情况下,这个数据表示的是普通消息,什么情况下,这个数据表示的是心跳消息。

步骤一:定义协议,表明什么情况下表示普通消息,什么情况下表示心跳消息,在这里,我们用前四位用来区分普通消息和心跳消息

步骤二:定义一个方法,按照协议内容包装内容

现在给出完整的协议类的代码: BasicProtocol:

public abstract class BasicProtocol {    static final int TYPE_LEN = 4;//表示业务类型;1111 -> 心跳包   1234 -> 发送普通文字消息    static final int CONTEXT_LEN = 4;    /**     * 获取正文文本     * @return     */    public abstract String getContext();    /**     * 获取包装好的byte[]     * @return     */    public byte[] getData() {        try {            ByteArrayOutputStream baos = new ByteArrayOutputStream();            baos.write(getType().getBytes(),0,TYPE_LEN);            byte[] bytes = getContext().getBytes();            baos.write(ProtocolUtil.int2ByteArrays(bytes.length),0,CONTEXT_LEN);            baos.write(bytes,0,bytes.length);            return baos.toByteArray();        }catch (Exception e){            return null;        }    }    /**     * 获取业务类型     * @return     */    public abstract  String getType();    /**     * 解析数据     * @param bytes     */    public abstract void parseBinary(byte[] bytes);}复制代码

HeartBeatProtocol:

public class HeartBeatProtocol extends BasicProtocol {    static final String TYPE = "1111";    @Override    public String getContext() {        return "兄弟,我还在,你不要担心";    }    @Override    public String getType() {        return TYPE;    }    @Override    public void parseBinary(byte[] bytes) {    }}复制代码

MessageProtocol:

public class MessageProtocol extends BasicProtocol {    private String context;    static final String TYPE = "1234";    public void setContext(String context){        this.context = context;    }    @Override    public String getContext() {        return context;    }    @Override    public String getType() {        return TYPE;    }    @Override    public void parseBinary(byte[] bytes) {        setContext(new String(bytes));    }}复制代码

步骤三:编写服务端代码:启动一个读线程,读取客户端数据

public class LongServer implements Runnable {    private ReadTask readTask;//读数据的线程    private Socket socket;    public LongServer(Socket socket){        this.socket = socket;    }    @Override    public void run() {        try {            readTask = new ReadTask();            readTask.inputStream = new DataInputStream(socket.getInputStream());            readTask.start();        } catch (Exception e) {            e.printStackTrace();        }    }    /**     * 负责读取数据     */    public class ReadTask extends Thread{        private DataInputStream inputStream;        private boolean isCancle = false;//是否取消循环        @Override        public void run() {         //   try {                while (!isCancle){                    try {                       // inputStream = new DataInputStream (socket.getInputStream());                        BasicProtocol protocol = ProtocolUtil.readInputStream(inputStream);                        if(protocol != null){                            System.out.println("================:"+protocol.getContext());                        }                    } catch (Exception e) {                        e.printStackTrace();                    }                }//            } catch (IOException e) {//                e.printStackTrace();//                stops();//捕获到io异常,可能原因是连接断开了,所以我们停掉所有操作//            }        }    }    /**     * 停止掉所有活动     */    public void stops(){        if (readTask!=null){            readTask.isCancle=true;            readTask.interrupt();            readTask=null;        }}复制代码

步骤四:客户端代码

public class Client {    private Socket socket;    private WriteTask writeTask;    public static void main(String[] args) throws IOException{         Client client = new Client();         client.start();    }    String[] string = {
"用户名:admin;密码:admin", "身无彩凤双飞翼,心有灵犀一点通。", "两情若是久长时,又岂在朝朝暮暮。" , "沾衣欲湿杏花雨,吹面不寒杨柳风。", "何须浅碧轻红色,自是花中第一流。", "更无柳絮因风起,唯有葵花向日倾。" , "海上生明月,天涯共此时。", "一寸丹心图报国,两行清泪为思亲。", "清香传得天心在,未话寻常草木知。", "和风和雨点苔纹,漠漠残香静里闻。"}; public Client() throws IOException { //1、创建客户端Socket,指定服务器地址和端口 socket = new Socket("127.0.0.1", 9013); } public void start(){ try { writeTask = new WriteTask(); writeTask.outputStream = new DataOutputStream(socket.getOutputStream());//默认初始化发给自己 writeTask.start(); } catch (Exception e) { e.printStackTrace(); } } public static byte[] int2ByteArrays(int i) { byte[] result = new byte[4]; result[0] = (byte) ((i >> 24) & 0xFF); result[1] = (byte) ((i >> 16) & 0xFF); result[2] = (byte) ((i >> 8) & 0xFF); result[3] = (byte) (i & 0xFF); return result; } //消息队列 private volatile ConcurrentLinkedQueue
reciverData= new ConcurrentLinkedQueue
(); /** * 负责写入数据 */ public class WriteTask extends Thread{ private DataOutputStream outputStream; private boolean isCancle = false; private Timer heart = new Timer();//发送心跳包的定时任务 private Timer message = new Timer();//模拟发送普通数据 @Override public void run() { //每隔20s发送一次心跳包 heart.schedule(new TimerTask() { @Override public void run() { reciverData.add(new HeartBeatProtocol()); } },0,1000*20); //先延时2s,然后每隔6s发送一次普通数据 Random random = new Random(); message.schedule(new TimerTask() { @Override public void run() { MessageProtocol bp = new MessageProtocol(); bp.setContext(string[random.nextInt(string.length)]); reciverData.add(bp); } },1000*2,1000*6); while (!isCancle){ BasicProtocol bp = reciverData.poll(); if(bp!=null){ System.out.println("------:"+bp.getContext()); ProtocolUtil.writeOutputStream(bp,outputStream); } } } } /** * 停止掉所有活动 */ public void stops(){// if (readTask!=null){// readTask.isCancle=true;// readTask.interrupt();// readTask=null;// } if (writeTask!=null) { writeTask.isCancle = true; //取消发送心跳包的定时任务 writeTask.heart.cancel(); //取消发送普通消息的定时任务 writeTask.message.cancel(); writeTask.interrupt(); writeTask=null; } }}复制代码

以上代码参考:

6 影响TCP连接寿命的因素

1、NAT超时

大部分移动无线网络运营商都在链路一段时间没有数据通讯时,会淘汰 NAT 表中的对应项,造成链路中断(NAT超时的更多描述见附录6.1)。NAT超时是影响TCP连接寿命的一个重要因素(尤其是国内),所以客户端自动测算NAT超时时间,来动态调整心跳间隔,是一个重要的优化点。

2、DHCP的租期(lease time)

目前测试发现安卓系统对DHCP的处理有Bug,DHCP租期到了不会主动续约并且会继续使用过期IP,这个问题会造成TCP长连接偶然的断连。(租期问题的具体描述见附录6.2)。

3、网络状态变化

手机网络和WIFI网络切换、网络断开和连上等情况有网络状态的变化,也会使长连接变为无效连接,需要监听响应的网络状态变化事件,重新建立Push长连接。

7. 不同网络状态下的心跳策略

稳定的网络状态下:

其中:

  • [MinHeart,MaxHeart]——心跳可选区间。

  • successHeart——当前成功心跳,初始为MinHeart

  • curHeart——当前心跳初始值为successHeart

  • heartStep——心跳增加步长

  • successStep——稳定期后的探测步长

如何判断网络状态稳定?

答:使用 短心跳连续成功三次,此时认为网络相对稳定。

8. 各平台Push策略研究

以下内容来自于微信分享的关于心跳策略的。

8.1 WhatsApp的Push策略

8.2 Line的Push策略

8.3 微信的Push策略

微信没有使用GCM,自己维护TCP长连接,使用固定心跳。

心跳典型值为:

转载地址:http://alkta.baihongyu.com/

你可能感兴趣的文章
弗洛伊德算法
查看>>
【算法之美】求解两个有序数组的中位数 — leetcode 4. Median of Two Sorted Arrays
查看>>
精度 Precision
查看>>
Android——4.2 - 3G移植之路之 APN (五)
查看>>
Linux_DHCP服务搭建
查看>>
[SilverLight]DataGrid实现批量输入(like Excel)(补充)
查看>>
秋式广告杀手:广告拦截原理与杀手组织
查看>>
翻译 | 摆脱浏览器限制的JavaScript
查看>>
闲扯下午引爆乌云社区“盗窃”乌云币事件
查看>>
02@在类的头文件中尽量少引入其他头文件
查看>>
JAVA IO BIO NIO AIO
查看>>
input checkbox 复选框大小修改
查看>>
BOOT.INI文件参数
查看>>
vmstat详解
查看>>
新年第一镖
查看>>
unbtu使用笔记
查看>>
OEA 中 WPF 树型表格虚拟化设计方案
查看>>
Android程序开发初级教程(一) 开始 Hello Android
查看>>
使用Gradle打RPM包
查看>>
“我意识到”的意义
查看>>