Java NIO实现WebSocket服务器

简介

在HTTP请求中,服务器往往处于被动的一方,通常都是客户端向服务器发送请求时,服务器才会做出响应,服务器并不会主动向客户端推送消息。因此WebSocket API就为此诞生。WebSocket API是HTML5中的一大特色,能够使得建立连接的双方在任意时刻相互推送消息,这意味着不同于HTTP,服务器服务器也可以主动向客户端推送消息了。

关于WebSocket的介绍,可以参考下一篇博文http://blog.csdn.net/zwto1/article/details/52493119#websocket%E5%8E%9F%E7%90%86

WebSocket协议的格式

为了实现一个能与H5的WebSocket API通信的服务器,我们需要先熟悉WebSocket数据包的格式。定的格式。

握手数据包

在一个连接建立以后,建立连接的双方才可以互相推送消息。双方通过握手即可建立一个连接。握手数据包的格式如下:

客户端向服务器发起请求

这里写图片描述

可以见到,客户端请求连接建立的数据包是一个字符串,而且第一行表明这实际上是一个HTTP报文。其中Connection: Upgrade以及Upgrade: websocket两字段就是用来告知服务器这是一个WebSocket握手请求。

服务器还要关心的一个字段是Sec-WebSocket-Key(倒数第二行),其值是一个随机base64字符串,服务器怎么处理该字符串请往下看。

服务器回应请求

这里写图片描述

可以看到HTTP状态码为101,同样,服务端也带有Connection和Upgrade字段来表明这是一个WebSocket数据包。

Sec-WebSocket-Accept字段是对请求报文中Sec-WebSocket-Key字段进行摘要运算的结果。其运算过程如下
1、将Sec-WebSocket-Key字段的值与字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接。
2、对拼接后的字符串进行sha1运算,得到160位摘要(二进制)。
3、以base64的形式表示得到的摘要。
客户端会进行同样的运算,并且与服务器返回来的字段作对比,如果发现二者不相同,连接就无法建立了。

通信数据帧

通信数据帧的格式如下(参考官方文档https://www.rfc-editor.org/rfc/rfc6455.txt
这里写图片描述
其中各个字段的含义如下
FIN: 1bit,表示这是否为分片的最后一个数据帧。这是考虑到发送的数据有可能被分片的情况,如果存在分片,将此字段置1就表明这是最后一个分片。如果不存在分片,此字段恒为1。因为只有一个分片就一定是最后一个分片。

RSV1, RSV2, RSV3: 各1bit,全0。现在暂时用不上,为了将来可能用于功能拓展保留的字段。

Opcode: 4bits
指出数据的类型,值的解释如下

含义
0x0 附加数据帧
0x1 文本数据帧
0x2 二进制数据帧
0x3-0x7 暂无定义
0x8 关闭连接
0x9 表示ping
0xA 表示pong
0aB-0xF 暂无定义

MASK: 1bit
表明是否对数据进行掩码运算,置1表示使用掩码。从客户端向服务器发送的数据必须使用掩码。

Payload length: 7 bits, 7+16 bits, or 7+64 bits
表明数据的长度。
如果长度在0-125内,这7bits就表示数据的长度;
如果值为126,紧接着后面2字节(16bits)才表示数据的长度;
如果值为127,后面8字节(64bits)表示数据的长度。

Masking-key: 无 或 4 字节
如果掩码字段(MASK)置0,就不需要Masking-key。如果掩码字段为1,这4字节就是Masking-key,用它与数据部分进行异或运算。

Payload Data: 数据部分,长度可变。

关于其他详细说明可以参考官方文档,例如消息分片规则等。

实现一个WebSocket服务器(群聊天室例子)

为了更加深刻的理解这样一个协议,这里没有使用Java已经封装好操作的类库。

基于NIO监听端口

基于NIO中的ServerSocketChannel,实现一个接收并读取Socket内容的服务端套路如下。


public class WebSocketServer {
   

    private Selector serverSelector;
    private WebSocketListener socketListener;
    private boolean isRunning = true;

    public WebSocketServer(int serverPort, WebSocketListener socketListener) throws IOException {
        //初始化ServerSocketChannel
        ServerSocketChannel serverSocketChannel =
                ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(serverPort));
        serverSocketChannel.configureBlocking(false);

        //创建选择器
        serverSelector = Selector.open();

        //注册ServerSocketChannel的ACCEPT事件至选择器
        serverSocketChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
        this.socketListener = socketListener;
    }

    public void run() throws IOException {
        while (isRunning) {
            int selectCount = serverSelector.select();
            if (selectCount == 0)
                continue;

            Iterator<SelectionKey> iterator = serverSelector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectKey = iterator.next();

                if (selectKey.isAcceptable()) {

                    //ACCEPT就绪,此时调用ServerSocketChannel的accept()方法可获得连接的SocketChannel对象,将其READ事件注册到选择器,就可以读取内容了。
                   
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
Java实现WebSocket多线程可以通过使用Java WebSocket API来实现。以下是一个简单的示例: ```java import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import javax.websocket.CloseReason; import javax.websocket.Endpoint; import javax.websocket.EndpointConfig; import javax.websocket.MessageHandler; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; @ServerEndpoint("/websocket") public class WebSocketServer extends Endpoint { private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(10); @Override public void onOpen(Session session, EndpointConfig config) { session.addMessageHandler(new MessageHandler.Whole<ByteBuffer>() { @Override public void onMessage(ByteBuffer message) { // 处理消息 } }); executorService.scheduleAtFixedRate(new Runnable() { @Override public void run() { try { session.getBasicRemote().sendText("Hello, World!"); } catch (IOException e) { e.printStackTrace(); } } }, 0, 1, TimeUnit.SECONDS); } @Override public void onClose(Session session, CloseReason closeReason) { executorService.shutdown(); } } ``` 在上面的示例中,我们使用了Java WebSocket API中的`@ServerEndpoint`注解来定义一个WebSocket服务器端点,`Endpoint`类提供了一个回调方法,可以在WebSocket连接建立时处理请求。我们在`onOpen`方法中注册了一个`MessageHandler`来处理来自客户端的消息,并使用`ScheduledExecutorService`定时向客户端发送消息。在连接关闭时,我们关闭了`ScheduledExecutorService`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值