如何使用Socket在客户端实现长连接

bianmaren 发布于 2018-09-20 14:38:27    访问

标签 : JAVA

image.png

长连接貌似是一个很高深莫测的知识,但是只要你做直播、IM、游戏、弹幕里面的任何一种,或者是你的app想要实时的接收某些消息,你就会要接触到长连接技术。本文主要教你如何在客户端如何使用Socket实现长连接。

Socket背景知识

要做长连接的话,是不能用http协议来做的,因为http协议已经是应用层协议了,并且http协议是无状态的,而我们要做长连接,肯定是需要在应用层封装自己的业务,所以就需要基于TCP协议来做,而基于TCP协议的话,就要用到Socket了。

  • Socket是java针对tcp层通信封装的一套网络方案

  • TCP协议我们知道,是基于ip(或者域名)和端口对指定机器进行的点对点访问,他的连接成功有两个条件,就是对方ip可以到达和端口是开放的

  • Socket能帮完成TCP三次握手,而应用层的头部信息需要自己去解析,也就是说,自己要制定好协议,并且要去解析byte

http也有长连接。在http1.0的时候,使用的是短连接,也就是说,每次请求一次数据,都要重新建立连接。但是从http1.1之后,我们看到头部会有一个

Connection:keep-alive

这个表示tcp连接建立之后不会马上销毁,而是保存一段时间,在这段时间内如果需要请求改网站的其他数据,都是使用这个连接来完成传输的。

Socket使用方式

Socket看上去不是很好用,因为他是基于java.io来实现的,你要直接跟InputStream和OutputStream打交道,也就是直接跟byte[]打交道,所以用起来并不是这么友好。
下面通过一个简单的例子,往一台服务器发\01 \00 \00 \00 \00这一串字节,服务器也返回相同的字节流,上代码:

@Test
    public void testSocket() throws Exception {
        logger.debug("start");
        Socket socket = new Socket();
        socket.connect(address);        byte[] output = new byte[]{(byte) 1, (byte) 0, (byte) 0, (byte) 0, (byte) 0};
        socket.getOutputStream().write(output);        byte[] input = new byte[64];        int readByte = socket.getInputStream().read(input);
        logger.debug("readByte " + readByte);        for (int i = 0; i < readByte; i++) {
            logger.debug("read [" + i + "]:" + input[i]);
        }
        socket.close();
    }

输出:

11:40:40.326 [main] DEBUG com.roy.test.SocketTest - start
11:40:40.345 [main] DEBUG com.roy.test.SocketTest - readByte 5
11:40:40.345 [main] DEBUG com.roy.test.SocketTest - read 1
11:40:40.345 [main] DEBUG com.roy.test.SocketTest - read 0
11:40:40.345 [main] DEBUG com.roy.test.SocketTest - read 0
11:40:40.345 [main] DEBUG com.roy.test.SocketTest - read 0
11:40:40.345 [main] DEBUG com.roy.test.SocketTest - read 0

看出来写起来还是比较麻烦的,主要就是InputStream, OutputStream 和byte[]使用起来太不方便了。

SocketChannel blocking

Socket为了优化自己的封装和并发性能,推出了nio包下面的SocketChannel,这个相比于Socket的好处就是并发性能的提高和封装的优化了。

SocketChannel有两种方式——阻塞和非阻塞的,阻塞的用法和Socket差不多,都是在read和write的时候会阻塞线程,下面用一段代码来实现相同的功能。

@Test
    public void testSocketChannelBlock() throws Exception {        final SocketChannel channel = SocketChannel.open(address);

        ByteBuffer output = ByteBuffer.allocate(5);
        output.put((byte) 1);
        output.putInt(0);
        output.flip();
        channel.write(output);
        logger.debug("write complete, start read");
        ByteBuffer input = ByteBuffer.allocate(5);        int readByte = channel.read(input);
        logger.debug("readByte " + readByte);
        input.flip();        if (readByte == -1) {
            logger.debug("readByte == -1, return!");            return;
        }        for (int i = 0; i < readByte; i++) {
            logger.debug("read [" + i + "]:" + input.get());
        }
    }

本文转载至:https://www.jianshu.com/p/b36632165de5