一、主要目的

   猜测WebSocket服务器主要的处理逻辑大纲。

 

二、WebSocket服务器主要逻辑

  (一)循环接收TCP客户端连接

     每个连接用一个线程处理。

// 监听80端口

ServerSocket s = new ServerSocket(80);

 

// 循环处理连接

while(true){

    // 监听客户端

    Socket client = s.accept();

 

    // 创建线程

    Task task = new Task(client);

 

    // 启动线程

    task.start();

}

 

 (二)线程处理逻辑

   1、客户端用HTTP的GET请求来握手。

   2、服务端判断HTTP请求报文:

        如果握手成功,设置Sec-WebSocket-Accept头部,返回101响应。

        如果握手不成功,返回403响应。

   3、握手成功后,用WebSocket格式来全双工通信。

  主要逻辑如下:

// 线程子类Task

public class Task  extends Thread{

    // 引用套接字

    private Socket s;

 

    public Task(Socket s){

        this.s = s;

    }

 

    // 描述线程逻辑

   public void run(){

       // 封装成Request对象

       Request req = parseReq(s.getInputStream());

 

       // 封装成Response对象

       Response resp = parseResp(s.getOutputStream());

 

       // 判断url路径为/chat和方法为GET

       // 判断版本是13

       if(req.getPath() == "/chat" && req.getMethod() == "GET" && req.getHead("Sec-WebSocket-Version") == "13"){

          // 计算Sec-WebSocket-Accept值

 

          // 获得请求头Sec-WebSocket-Key的值

          String key = req.getHead("Sec-WebSocket-Key");

 

         // 在后面拼接通用码

         key = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

 

         // 计算sha1摘要值

         byte[] hash = Sha1Util.digest(key);

 

          // 把摘要值转成base64格式字符串

         String accept = Base64.encode(hash);

 

          // 把结果设置到响应头

          resp.setHead("Sec-WebSocket-Accept",  accept);

 

          // 握手成功,返回101

           resp.setCode(101);

 

           // 输出响应头

           resp.outputHead();

 

           // 开始循环WebSocket通信

           loop(req.getInput(),  resp.getOutput());

 

       } else {

           // 握手失败,返回403

           resp.setCode(403);

 

           // 输出响应头

           resp.outputHead();

       }

 

       // 关闭套接字

       s.close();

   }

}

 

  (三)监听WebSocket的数据包

// 循环方法

public void  loop(InputStream in,  OutputStream out) throws IOException{

      // 建立事件处理对象

      Listener listen = new Listener();

 

      // 建立发送对象

      Sender s = new Sender(out);

 

      // 调用建立连接的方法

      listen.open(s);

 

      // 循环处理

      while(true){

           // 读取一个WebSocket数据包

           Data d = readData(in);

 

           // 判断操作码类型

           if(d.getOpcode() == 1){

               // 类型为1是文本数据

               // 调用事件监听器的data()方法

               // 转成UTF-8字符串

               listen.data(s,   

                  new String(d.getBody(),  "UTF-8"));

           } else if(d.getOpcode() == 2){

                // 类型为2,是二进制类型

 

                // 调用监听器的bindata()方法

                listen.bindata(s,  d.getBody());

 

           } else if(d.getOpcode() == 8) {

               // 类型为8是客户端主动关闭

               // 调用监听器的close()方法

               listen.close(s);

 

               // 跳出循环

               break;

           }

      }

}

 

        说明:WebSocket数据包操作码值为1是文本类型,值为2是二进制类型,值为8是对方主动关闭。

         读者可以处理其他的数据包类型。

 

  (四)事件处理器的方法

public class Listener{

 

    // 处理打开事件

    public void open(Sender sender){

            System.out.println("新连接");

    }

 

    // 处理字符串数据接受事件

    public void data(Sender s,  String str){

            // 打印收到的字符串信息

            System.out.println("接收结果:" + str);

 

            // 服务器回复

            s.sendText("我是服务器,收到了");

    }

 

     // 处理接收二进制数据

     public void bindata(Sender s,  byte[] array){

            // 存储或者处理二进制数据

     }

 

    // 处理关闭事件

    public void close(Sender s){

            // 关闭输出

            s.close();

    }

}

 

三、总结

  1、本文只是大概介绍WebSocket服务器的处理逻辑。

  2、本文的WebSocket服务器绑定在HTTP的"/chat"资源路径,实际需要用户自己修改。

  3、WebSocket数据包的读取解析和发送打包比较复杂,需要读者自己钻研。

     例如:判断负载数据的长度,计算数据的掩码。

   4、本文没有提供一些获取URL路径参数的方法。

   5、本文没有为每个连接创建会话信息。

     需要自定义一个会话类,指定Socket属性。

 

 

 

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐