websocket初探

过去都是使用浏览器端插件(如flash和java applet)与服务器建立套接字通信,这些插件技术曾经广泛应用在网页聊天和网页游戏,html5提供的websocket有取代这些插件的趋势。

在websocket之前,实现服务器向浏览器推送消息有两种方式:ajax轮询和comet长轮询。

  • ajax轮询(polling):浏览器端不停地向服务器发起请求,问:“有新数据没有?”,不管有没有数据它都会返回来。这种方式不管对客户端还是对服务器都是巨大的压力。
  • comet技术(long-polling):浏览器发起一次请求,与服务器建立一个长连接,直到数据来临时才回复浏览器。一个请求结束之后,浏览器会马上发起另一个请求。这种技术的缺点是需要服务器维护的请求数很多。相当于有很多请求在同时发生。这种技术依旧流行。

一、Java中的websocket库

websocket是java标准库的一部分,位于javax包下,但它只是定义一些接口。
websocket有不同的实现,如Tomcat的,jetty的,Spring的,还有一个名叫TooTallNate组织发布的java-websocket库,atmosphere库,socket.io的java版本等。

二、Python中的websocket库

websocket协议本身并不复杂,100行Python代码可以实现简单的websocket协议。
Python中最基础的websocket库是gevent.websocket。使用Flask的人们对这个库进行了一下封装,让它变得更好用,这个库名叫flask-sockets。
还有一个封装得比较重的库flask-socketio。
但是我运行flask-socketio和flask-sockets这两个库都失败了,gevent-websocket运行成功了。

在JS中有一个库socketio,这个库能够兼容各个版本的浏览器,是对websocket的封装,在网页端用这个库应该是最佳选择。

三、旧版的tomcat的websocket

在javax.websocket接口出来之前,tomcat7就已经对websocket提供支持了。于是在javax.websocket出来之后,tomcat8就开始废弃tomcat7中定义的websocket,tomcat7关于websocket的包位于org.apache.catalina.websocket中。

包org.apache.catalina.websocket中的这些类为WebSocket开发服务端提供了支持,这些类的主要功能简述如下:

1、Constants:包org.apache.catalina.websocket中用到的常数定义在这个类中,它只包含静态常数定义,无任何逻辑实现。
2、MessageInbound:基于消息的WebSocket实现类(带内消息),应用程序应当扩展这个类并实现其抽象方法onBinaryMessage和onTextMessage。
3、StreamInbound:基于流的WebSocket实现类(带内流),应用程序应当扩展这个类并实现其抽象方法onBinaryData和onTextData。
4、WebSocketServlet:提供遵循RFC6455的WebSocket连接的Servlet基本实现。客户端使用WebSocket连接服务端时,需要将WebSocketServlet的子类作为连接入口。同时,该子类应当实现WebSocketServlet的抽象方法createWebSocketInbound,以便创建一个inbound实例(MessageInbound或StreamInbound)。
5、WsFrame:代表完整的WebSocket框架。
6、WsHttpServletRequestWrapper:包装过的HttpServletRequest对象。
7、WsInputStream:基于WebSocket框架底层的socket的输入流。
8、 WsOutbound:提供发送消息到客户端的功能。它提供的所有向客户端的写方法都是同步的,可以防止多线程同时向客户端写入数据。

它的典型写法如下:

public class ChatWebSocketServlet extends WebSocketServlet {  
  
    private static final long serialVersionUID = 1L;  
  
    private static final String GUEST_PREFIX = "Guest";  
  
    private final AtomicInteger connectionIds = new AtomicInteger(0);  
    private final Set<ChatMessageInbound> connections =  
            new CopyOnWriteArraySet<ChatMessageInbound>();  
    // 创建Inbound实例,WebSocketServlet子类必须实现的方法  
    @Override  
    protected StreamInbound createWebSocketInbound(String subProtocol,  
            HttpServletRequest request) {  
        return new ChatMessageInbound(connectionIds.incrementAndGet());  
    }  
    // MessageInbound子类,完成收到WebSocket消息后的逻辑处理  
    private final class ChatMessageInbound extends MessageInbound {  
  
        private final String nickname;  
  
        private ChatMessageInbound(int id) {  
            this.nickname = GUEST_PREFIX + id;  
        }  
        // Open事件  
        @Override  
        protected void onOpen(WsOutbound outbound) {  
            connections.add(this);  
            String message = String.format("* %s %s",  
                    nickname, "has joined.");  
            broadcast(message);  
        }  
        // Close事件  
        @Override  
        protected void onClose(int status) {  
            connections.remove(this);  
            String message = String.format("* %s %s",  
                    nickname, "has disconnected.");  
            broadcast(message);  
        }  
        // 二进制消息事件  
        @Override  
        protected void onBinaryMessage(ByteBuffer message) throws IOException {  
            throw new UnsupportedOperationException(  
                    "Binary message not supported.");  
        }  
        // 文本消息事件  
        @Override  
        protected void onTextMessage(CharBuffer message) throws IOException {  
            // Never trust the client  
            String filteredMessage = String.format("%s: %s",  
                    nickname, HTMLFilter.filter(message.toString()));  
            broadcast(filteredMessage);  
        }  
        // 向所有已连接的客户端发送文本消息(广播)  
        private void broadcast(String message) {  
            for (ChatMessageInbound connection : connections) {  
                try {  
                    CharBuffer buffer = CharBuffer.wrap(message);  
                    connection.getWsOutbound().writeTextMessage(buffer);  
                } catch (IOException ignore) {  
                    // Ignore  
                }  
            }  
        }  
    }  

四、javax.websocket定义的函数参数

IllegalArgumentException
No payload parameter present on the method[message]

意思是该有的参数没有,比如

  • onError()必须有Throwable参数
  • onMessage()必须有String message参数或者ByteBuffer类型的参数来接受消息

沿着抛出这个异常的异常栈逐个打开源代码,会看见容器初始化ServerEndPoint的每个细节,以及对其函数的解析.
onOpen(EndpointConfig)
onClose(CloseReason)
onError(Throwable)
onMessage(PhongMessage | InputStream | byte[] | ByteBuffer | Reader | String,boolean isLastMessage)

上面这些是有且仅能包含的参数,其中onMessage必须接受一种数据类型的数据,可以是Reader(接受文本),也可以是InputStream(二进制).PhongMessage是处理ping信息的.byte[]和ByteBuffer都是对InputStream进行了一下读取,String是对Reader进行了一下读取.
OnOpen和OnError函数不能有String类型的参数,因为它们只能包含以上类型的参数,如果OnOpen和OnError有String类型的参数,则只能是@PathParam注解的String类型的参数,否则报错A parameter of type [class java.lang.String] was found on method[error] of class [java.lang.reflect.Method] that did not have a @PathParam annotation

五、TooTallNate-java-websocket

这个库100%用Java实现,基于nio。它包含了一个websocket服务器和一个websocket客户端。官方仓库包含丰富的代码示例。这个库并不符合javax.websocket接口,它纯粹是民间的websocket实现。
服务器端需要实例化WebSocketServer这个类,可以覆盖它的onOpen,onMessage等函数。
客户端除了可以是网页,也可以是Java。用Java实现websocket客户端需要实例化WebSocketClient这个类。

Tomcat的websocket实现能够很好地跟整个web应用融为一体,比如websocket和web应用可以共用8080端口。如果用TooTallNate-java-websocket,那就必须用两个端口了。

原文地址:https://www.cnblogs.com/weiyinfu/p/6287117.html