SocketIO:服务端推送

版权声明:本文为博主原创文章,未经博主允许不得转载。联系QQ:916907826 https://blog.csdn.net/Dome_/article/details/88582850

前言

在介绍SocketIO之前,先说下服务端推送是怎么一回事。所谓服务端推送,就是服务端将数据或者消息实时地推送到客户端上。最常见的场景就是即时通讯,除此之外,视频弹幕、图文直播等功能也用到了服务端推送这项技术。

之所以使用服务端推送,是为了把服务端的数据及时、迅速地推送到客户端,当然,如果能够尽可能地降低服务端的性能损耗,那就再好不过了。

要想及时地获取服务端数据,最简单的实现方案莫过于客户端长轮询了。简而言之,就是客户端起一个定时器,定时向服务端发送HTTP请求查询最新数据,以此实现客户端与服务端的数据保持一致。这种方案的好处是实现简单,坏处就是性能损耗过高,而且数据更新也不及时。由于需要定时向服务器发送HTTP请求,如果客户端数量过多,则服务器需要承受很大的并发压力。再者,服务端的数据更新可能并不频繁,但是客户端也要定期过来询问,这会让服务端和客户端都做很多无用功,效率自然不高。还有一点就是,由于客户端没法做到每时每刻都向服务端请求数据,只能定时请求,假如说相邻2次请求的间隔时间过长,则服务端的数据就没法及时地“推送”到客户端,这会造成数据推送延迟。

服务端推送的其他方案

客户端长轮询方案是最简单的方案,但是缺点诸多,业界应该没几家公司会采用。我们都知道,TCP协议是支持双向通信的,也就是说服务端可以向客户端发送数据包,客户端也可以向服务端发送数据包。但由于HTTP协议的限制,基于HTTP协议通信的网络应用只能由客户端主动发起,服务端被动响应,故无法做到服务端向客户端推送数据。虽然HTTP 2.0支持服务端推送,但是HTTP 2.0的服务端推送跟我们这里说的服务端推送还不是一回事,有兴趣的朋友可以去深入了解下。

既然TCP协议支持服务端推送,而HTTP协议不支持,那么事情就好办了。我们传输层依旧使用TCP协议,应用层改用其他协议不就得了。事实上,业界也确实是这么做的。最常见的方案就是使用现成的协议,如WebSocket。当然,也可以使用自定义协议来实现服务端推送。关于WebSocket协议这里不会展开细讲,感兴趣的可以看旧文:一文读懂WebSocket

如果直接使用WebSocket协议作为服务端与客户端通信协议的话,则意味着服务端与客户端还要关注心跳检测、自动重连等细节,如果使用自定义协议,则还要进行协议的解析,以及拆包粘包等工作,未免过于繁杂。因此,SocketIO应运而生。

SocketIO是什么

SocketIO是一套支持实时双向事件驱动的服务端与客户端通信的解决方案,或者说规范。其基于WebSocket协议,天生支持服务端推送。此外,其还封装了连接检测、自动重连等细节,并且支持命名空间与群聊。不仅如此,它还支持基于反向代理来搭建服务器集群。

因此,使用SocketIO可以很方便地搭建支持服务端推送的集群,而且还无需关注底层连接建立、连接检测等细节。

不过需要指出的是,SocketIO不仅仅支持WebSocket协议,也支持HTTP长轮询的方式来实现服务端推送。这取决于客户端支不支持WebSocket协议,如果客户端支持WebSocket协议,则服务端会优先采用WebSocket协议与客户端通信,假如说客户端不支持WebSocket,则服务端会降级成使用HTTP长轮询的方式来通信。不过,这一切对于使用者来说都是透明的。而且,服务端也可以强制指定使用WebSocket协议来通信。

还有一点,SocketIO使用的不是标准的WebSocket协议,其在每个数据包里增加了一些元数据,如:命名空间、ack id等。因此如果服务端是一个SocketIO服务器,而客户端使用标准的WebSocket协议与服务端通信的话,是没法建立连接的。幸好,SocketIO服务端跟客户端都有相应的开源库,社区也很活跃,所以使用起来非常方便。

SocketIO使用

接下来来看下SocketIO如何使用。

本文使用的服务端是Java语言的实现netty-socketio,项目地址:https://github.com/mrniko/netty-socketio。客户端也是Java语言的开源库socket.io-client-java,项目地址:https://github.com/socketio/socket.io-client-java。

先来看下服务端demo。

首先新建maven工程,引入netty-socketio依赖:

<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.17</version>
</dependency>

然后,开始编写SocketIO服务端代码:

import com.corundumstudio.socketio.Configuration;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.listener.ConnectListener;

public class Server {

public static void main(String[] args){
    Configuration config = new Configuration();
    config.setPort(1337);
final SocketConfig socketConfig = new SocketConfig();
    socketConfig.setReuseAddress(true);
    config.setSocketConfig(socketConfig);
    SocketIOServer server = new SocketIOServer(config);
    server.addConnectListener(new ConnectListener() {
@Override
public void onConnect(SocketIOClient socketIOClient) {
        System.out.println(socketIOClient.getSessionId()+" has connected.");
      }
    });
    server.start();
  }
}

代码很简单,就是设置监听端口,以及设置一些tcp参数,然后添加连接事件监听器,这里的连接监听器逻辑比较简单,就是把客户端的sessionId打印出来而已。最后再启动SocketIO服务器。

SocketIO除了能监听连接事件外,还能监听连接断开事件,以及自定义事件。这里为了方便,只使用了连接监听器。

接下来再编写SocketIO客户端demo。

首先,引入maven依赖socket.io-client-java。

<dependency>
<groupId>io.socket</groupId>
<artifactId>socket.io-client</artifactId>
<version>1.0.0</version>
</dependency>

接着编写客户端代码。

import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;

public class Client {

public static void main(String[] args) throws Exception{
final Socket socket = IO.socket("http://localhost:1337");
    socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {

@Override
public void call(Object... args) {
        System.out.println("I have connected to server.");
      }

    });
    socket.connect();
  }
}

客户端的代码也很简单。首先设置服务端的ip地址和端口,然后设置连接监听器,这里也只是简单地打印一句话而已。最后开启连接。同样地,客户端除了可以监听连接事件,也可以监听断连事件,以及自定义事件。

最后,先后启动服务端与客户端,可以看到服务端的控制台输出如下:


afbecfc5-032d-4e04-a937-faa521b0c7a2 has connected.

客户端的控制台输出如下:


I have connected to server.

至此,一个简易的SocketIO通信demo业已完成。

连接一旦建立,服务端与客户端就可以进行双向通信了。

扩展

不知到了这里,大家是否有这样的疑问:前面不是说SocketIO使用的是WebSocket协议通信吗?那么客户端建立连接的时候,设置的连接串为什么使用的是HTTP协议呢?其实大家可以试着把客户端代码的HTTP协议改为WebSocket协议试试:ws://localhost:1337。然后再先后运行服务端与客户端,会发现客户端报如下错误:

Exception in thread "main" java.lang.RuntimeException: java.net.MalformedURLException: unknown protocol: ws
at io.socket.client.Url.parse(Url.java:52)
at io.socket.client.IO.socket(IO.java:61)
at io.socket.client.IO.socket(IO.java:42)
at io.socket.client.IO.socket(IO.java:38)
at socketio.Client.main(Client.java:10)
Caused by: java.net.MalformedURLException: unknown protocol: ws
at java.net.URL.<init>(URL.java:600)
at java.net.URL.<init>(URL.java:490)
at java.net.URL.<init>(URL.java:439)
at io.socket.client.Url.parse(Url.java:46)
  ... 4 more

报的是未知协议异常。

所以客户端建立连接的时候只能先使用HTTP协议,再升级到WebSocket协议进行通信。至于WebSocket协议的升级细节,可以看旧文:一文读懂WebSocket。这里不再赘述。

以后有时间再讲讲SocketIO的集群方案,以及踩过的坑。

如果觉得有收获,可以点个好看,我会写得更带劲。

猜你喜欢

转载自blog.csdn.net/Dome_/article/details/88582850