【问题标题】:Standalone WebSocket server without JEE/application server没有 JEE/应用程序服务器的独立 WebSocket 服务器
【发布时间】:2023-03-24 23:57:01
【问题描述】:

我需要在 Java SE 中实现一个相当简单的 WebSocket 服务器。它所需要做的就是接受连接并存储相应的会话,然后在触发某个事件时向所有连接的客户端发送消息。

我找不到关于如何在常规 Java SE 中执行此操作的单一教程。所有这些都需要使用 Maven 运行,或者将其部署为 WAR——这对于这个项目来说都是不可能的。我需要将其作为 Java SE 桌面应用程序运行。

我找到的教程展示了如何使用 @OnOpen@OnMessage@OnClose 等注释来实现端点。但是,它们都没有解释如何实际初始化服务器。我还需要能够为传入连接指定不同的端口号。

我错过了什么吗?我知道人们已经使用 WebSocket 制作了聊天应用程序,而这确实不需要 Web 应用程序服务器。我也没有使用 Maven,为了简单起见,我更愿意保持这种方式。

【问题讨论】:

  • Jetty 支持 websocket。
  • 如果 Maven 是绊脚石,那很容易解决。只需分析 POM 文件中的依赖关系,手动获取它们的 JAR 文件,然后手动将它们放在应用程序的类路径中。 Maven 只是一个构建工具。它是自动化的东西,你可以手动完成......如果你愿意的话。
  • 我看过 Jetty 的实现,但是教程只展示了如何实现端点代码。我需要做什么来初始化它,即这样的应用程序的“main”方法中会发生什么?
  • 谷歌搜索“websocket apache httpcomponents”。我不知道你是否会找到一个教程,但有一些点击表明它可以完成,并且可能有你可以用作示例的代码。

标签: java websocket server


【解决方案1】:

https://github.com/TooTallNate/Java-WebSocket 是 Java SE 中完整的 WebSockets 服务器和客户端实现,无需企业/Web 应用服务器。

【讨论】:

    【解决方案2】:

    Java 11 服务器代码:

    package org.treez.server.websocket;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.UnsupportedEncodingException;
    import java.net.ServerSocket;
    import java.net.Socket;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.Scanner;
    import java.util.regex.Matcher;
    import java.util.regex.Pattern;
    
    import javax.xml.bind.DatatypeConverter;;
    
    public class WebSocketServer{
    
    
        public static void main(String[] args){
    
            int portNumber = 8000;
    
            ServerSocket server;
            try {
                server = new ServerSocket(portNumber);
            } catch (IOException exception) {
                throw new IllegalStateException("Could not create web server", exception);
            }
    
    
            Socket clientSocket;
            try {
                clientSocket = server.accept(); //waits until a client connects
            } catch (IOException waitException) {
                throw new IllegalStateException("Could not wait for client connection", waitException);
            }
    
            InputStream inputStream;
            try {
                inputStream  = clientSocket.getInputStream();
            } catch (IOException inputStreamException) {
                throw new IllegalStateException("Could not connect to client input stream", inputStreamException);
            }
    
            OutputStream outputStream;
            try {
                outputStream  = clientSocket.getOutputStream();
            } catch (IOException inputStreamException) {
                throw new IllegalStateException("Could not connect to client input stream", inputStreamException);
            }
    
            try {
                doHandShakeToInitializeWebSocketConnection(inputStream, outputStream);
            } catch (UnsupportedEncodingException handShakeException) {
                throw new IllegalStateException("Could not connect to client input stream", handShakeException);
            }
    
    
            try {        
                outputStream.write(encode("Hello from Server!"));
                outputStream.flush();
            } catch (UnsupportedEncodingException e) {          
                e.printStackTrace();
            } catch (IOException e) {       
                e.printStackTrace();
            }   
    
             try {
                    printInputStream(inputStream);
                } catch (IOException printException) {
                    throw new IllegalStateException("Could not connect to client input stream", printException);
                }
    
        }
    
        //Source for encoding and decoding:
        //https://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
    
    
        private static void printInputStream(InputStream inputStream) throws IOException {
            int len = 0;            
            byte[] b = new byte[1024];
            //rawIn is a Socket.getInputStream();
            while(true){
                len = inputStream.read(b);
                if(len!=-1){
    
                    byte rLength = 0;
                    int rMaskIndex = 2;
                    int rDataStart = 0;
                    //b[0] is always text in my case so no need to check;
                    byte data = b[1];
                    byte op = (byte) 127;
                    rLength = (byte) (data & op);
    
                    if(rLength==(byte)126) rMaskIndex=4;
                    if(rLength==(byte)127) rMaskIndex=10;
    
                    byte[] masks = new byte[4];
    
                    int j=0;
                    int i=0;
                    for(i=rMaskIndex;i<(rMaskIndex+4);i++){
                        masks[j] = b[i];
                        j++;
                    }
    
                    rDataStart = rMaskIndex + 4;
    
                    int messLen = len - rDataStart;
    
                    byte[] message = new byte[messLen];
    
                    for(i=rDataStart, j=0; i<len; i++, j++){
                        message[j] = (byte) (b[i] ^ masks[j % 4]);
                    }
    
                    System.out.println(new String(message)); 
    
                    b = new byte[1024];
    
                }
            }
        }
    
    
        public static byte[] encode(String mess) throws IOException{
            byte[] rawData = mess.getBytes();
    
            int frameCount  = 0;
            byte[] frame = new byte[10];
    
            frame[0] = (byte) 129;
    
            if(rawData.length <= 125){
                frame[1] = (byte) rawData.length;
                frameCount = 2;
            }else if(rawData.length >= 126 && rawData.length <= 65535){
                frame[1] = (byte) 126;
                int len = rawData.length;
                frame[2] = (byte)((len >> 8 ) & (byte)255);
                frame[3] = (byte)(len & (byte)255); 
                frameCount = 4;
            }else{
                frame[1] = (byte) 127;
                int len = rawData.length;
                frame[2] = (byte)((len >> 56 ) & (byte)255);
                frame[3] = (byte)((len >> 48 ) & (byte)255);
                frame[4] = (byte)((len >> 40 ) & (byte)255);
                frame[5] = (byte)((len >> 32 ) & (byte)255);
                frame[6] = (byte)((len >> 24 ) & (byte)255);
                frame[7] = (byte)((len >> 16 ) & (byte)255);
                frame[8] = (byte)((len >> 8 ) & (byte)255);
                frame[9] = (byte)(len & (byte)255);
                frameCount = 10;
            }
    
            int bLength = frameCount + rawData.length;
    
            byte[] reply = new byte[bLength];
    
            int bLim = 0;
            for(int i=0; i<frameCount;i++){
                reply[bLim] = frame[i];
                bLim++;
            }
            for(int i=0; i<rawData.length;i++){
                reply[bLim] = rawData[i];
                bLim++;
            }
    
            return reply;
        }
    
        private static void doHandShakeToInitializeWebSocketConnection(InputStream inputStream, OutputStream outputStream) throws UnsupportedEncodingException {
            String data = new Scanner(inputStream,"UTF-8").useDelimiter("\\r\\n\\r\\n").next();
    
            Matcher get = Pattern.compile("^GET").matcher(data);
    
            if (get.find()) {
                Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
                match.find();                 
    
                byte[] response = null;
                try {
                    response = ("HTTP/1.1 101 Switching Protocols\r\n"
                            + "Connection: Upgrade\r\n"
                            + "Upgrade: websocket\r\n"
                            + "Sec-WebSocket-Accept: "
                            + DatatypeConverter.printBase64Binary(
                                    MessageDigest
                                    .getInstance("SHA-1")
                                    .digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
                                            .getBytes("UTF-8")))
                            + "\r\n\r\n")
                            .getBytes("UTF-8");
                } catch (NoSuchAlgorithmException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
    
                try {
                    outputStream.write(response, 0, response.length);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            } else {
    
            }
        }
    }
    

    pom.xml:

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>TreezHttpServer</groupId>
      <artifactId>TreezHttpServer</artifactId>
      <version>0.0.1-SNAPSHOT</version>
      <build>
        <sourceDirectory>src</sourceDirectory>
        <resources>
          <resource>
            <directory>src</directory>
            <excludes>
              <exclude>**/*.java</exclude>
            </excludes>
          </resource>
        </resources>
        <plugins>
          <plugin>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.0</version>
            <configuration>
              <release>11</release>
            </configuration>
          </plugin>
        </plugins>    
    
      </build>
    
      <dependencies>
    
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-core</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>com.sun.xml.bind</groupId>
            <artifactId>jaxb-impl</artifactId>
            <version>2.2.11</version>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
            <version>1.1.1</version>
        </dependency>
    
      </dependencies>
    </project>
    

    JavaScript 客户端:

    <!DOCTYPE html>
    
    <html>
    
    <head>
    
    <meta charset="UTF-8">
    
    <title>WebSocket Client</title>
    
     <script type="text/javascript">
    
          var wsocket;      
    
          function connect() {         
    
              wsocket = new WebSocket("ws://localhost:8000");          
              wsocket.onopen = onopen;
              wsocket.onmessage = onmessage;
              wsocket.onclose = onclose; 
          }
    
          function onopen() {
              console.log("Connected!");             
              wsocket.send('hello from client');
          }
    
          function onmessage(event) { 
             console.log("Data received: " + event.data); 
          }
    
          function onclose(e) {
             console.log("Connection closed.");              
          }
    
          window.addEventListener("load", connect, false);
    
      </script>
    
    </head>
    
    <body>
    </body>
    
    </html>
    

    【讨论】:

      【解决方案3】:

      尝试使用Stefan 的代码。目前它不能很好地工作(我正在使用 Firefox)。大于 1024 字节的 Websocket 数据包被拆分为 TCP 段,因此需要重新组装。 以下是读取浏览器数据包的更新代码:

      private static void processResponse(InputStream inputStream, OutputStream outputStream) throws IOException {
          int readPacketLength = 0;
          byte[] packet = new byte[1024];
          ByteArrayOutputStream packetStream = new ByteArrayOutputStream();
      
          while(true) {
              readPacketLength = inputStream.read(packet);
      
              if(readPacketLength != -1) {
                  if ((packet[0] & (byte) 15) == (byte) 8) { // Disconnect packet
                      outputStream.write(packet, 0, readPacketLength);
                      // returning the same packet for client to terminate connection
                      outputStream.flush();
                      return;
                  }
                  byte messageLengthByte = 0;
                  int messageLength = 0;
                  int maskIndex = 2;
                  int messageStart = 0;
                  //b[0] is always text in my case so no need to check;
                  byte data = packet[1];
                  byte op = (byte) 127; // 0111 111
                  messageLengthByte = (byte) (data & op);
      
                  int totalPacketLength = 0;
                  if (messageLengthByte == (byte) 126 || messageLengthByte == (byte) 127) {
                      if (messageLengthByte == (byte) 126) {
                          maskIndex = 4;
                          // if (messageLengthInt==(byte)126), then 16-bit length is stored in packet[2] and [3]
                          ByteBuffer messageLength16Bit = ByteBuffer.allocateDirect(4);
                          messageLength16Bit.order(ByteOrder.BIG_ENDIAN);
                          messageLength16Bit.put((byte) 0x00);
                          messageLength16Bit.put((byte) 0x00);
                          messageLength16Bit.put(packet, 2, 2);
                          messageLength16Bit.flip();
                          messageLength = messageLength16Bit.getInt();
                          totalPacketLength = messageLength + 8;
                      } else {
                          maskIndex = 10;
                          // if (messageLengthInt==(byte)127), then 64-bit length is stored in bytes [2] to [9]. Using only 32-bit
                          ByteBuffer messageLength64Bit = ByteBuffer.allocateDirect(4);
                          messageLength64Bit.order(ByteOrder.BIG_ENDIAN);
                          messageLength64Bit.put(packet, 6, 4);
                          messageLength64Bit.flip();
                          messageLength = messageLength64Bit.getInt();
                          totalPacketLength = messageLength + 14;
                      }
      
                      if (readPacketLength != totalPacketLength) {
                          packetStream.write(packet, 0, readPacketLength);
      
                          int lastPacketLength = 0;
                          while (readPacketLength < totalPacketLength) {
                              packet = new byte[1024];
                              readPacketLength += lastPacketLength = inputStream.read(packet);
                              packetStream.write(packet, 0, lastPacketLength);
                          }
                          packet = packetStream.toByteArray();
                          packetStream.reset();
                      }
                  }
                  else { // using message length from packet[1]
                      messageLength = messageLengthByte;
                  }
      
                  byte[] masks = new byte[4];
                  int i=0; int j=0;
                  for(i = maskIndex; i < (maskIndex+4); i++) {
                      masks[j] = packet[i];
                      j++;
                  }
      
                  messageStart = maskIndex + 4;
      
                  byte[] message = new byte[messageLength];
                  for(i = messageStart, j = 0; i < readPacketLength; i++, j++){
                      message[j] = (byte) (packet[i] ^ masks[j % 4]);
                  }
                  System.out.println("Received message: " + new String(message));
                  packet = new byte[1024];
              }
          }
      }
      

      【讨论】:

      • 您是说您在此处发布的代码是基于另一个答案的代码的更正版本吗?
      • 是的。还添加了断开数据包管理,以便客户端正确断开连接。
      【解决方案4】:
      import java.io.IOException;
      import java.io.InputStream;
      import java.io.OutputStream;
      import java.io.UnsupportedEncodingException;
      import java.net.ServerSocket;
      import java.net.Socket;
      import java.security.MessageDigest;
      import java.security.NoSuchAlgorithmException;
      import java.util.Scanner;
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      import javax.xml.bind.DatatypeConverter;;
      
      public class WebSocketServer{
      
      
          public static void main(String[] args){
      
              int portNumber = 8000;
      
              ServerSocket server;
              try {
                  server = new ServerSocket(portNumber);
              } catch (IOException exception) {
                  throw new IllegalStateException("Could not create web server", exception);
              }
      
      
              Socket clientSocket;
              try {
                  clientSocket = server.accept(); //waits until a client connects
              } catch (IOException waitException) {
                  throw new IllegalStateException("Could not wait for client connection", waitException);
              }
      
              InputStream inputStream;
              try {
                  inputStream  = clientSocket.getInputStream();
              } catch (IOException inputStreamException) {
                  throw new IllegalStateException("Could not connect to client input stream", inputStreamException);
              }
      
              OutputStream outputStream;
              try {
                  outputStream  = clientSocket.getOutputStream();
              } catch (IOException inputStreamException) {
                  throw new IllegalStateException("Could not connect to client input stream", inputStreamException);
              }
      
              try {
                  doHandShakeToInitializeWebSocketConnection(inputStream, outputStream);
              } catch (UnsupportedEncodingException handShakeException) {
                  throw new IllegalStateException("Could not connect to client input stream", handShakeException);
              }
      
      
              try {        
                  outputStream.write(encode("Hello from Server!"));
                  outputStream.flush();
              } catch (UnsupportedEncodingException e) {          
                  e.printStackTrace();
              } catch (IOException e) {       
                  e.printStackTrace();
              }   
      
               try {
                      printInputStream(inputStream);
                  } catch (IOException printException) {
                      throw new IllegalStateException("Could not connect to client input stream", printException);
                  }
      
          }
      
          //Source for encoding and decoding:
          //https://stackoverflow.com/questions/8125507/how-can-i-send-and-receive-websocket-messages-on-the-server-side
      
          //this will handle incoming text only up to 64K only
          //it will handle multiple messages in one read and messages split over a read
          private static void printInputStream(InputStream inputStream) throws IOException {
              byte[] b = new byte[8000];//incoming buffer
              byte[] message =null;//buffer to assemble message in
              byte[] masks = new byte[4];
              boolean isSplit=false;//has a message been split over a read
              int length = 0; //length of message 
              int totalRead =0; //total read in message so far
              while (true) {
                  int len = 0;//length of bytes read from socket
                  try {
                      len = inputStream.read(b);
                  } catch (IOException e) {
                      break;
                  }
                  if (len != -1) {
                      boolean more = false;
                      int totalLength = 0;
                      do {
                          int j = 0;
                          int i = 0;
                          if (!isSplit) {
                              byte rLength = 0;
                              int rMaskIndex = 2;
                              int rDataStart = 0;
                              // b[0] assuming text
                              byte data = b[1];
                              byte op = (byte) 127;
                              rLength = (byte) (data & op);
                              length = (int) rLength;
                              if (rLength == (byte) 126) {
                                  rMaskIndex = 4;
                                  length = Byte.toUnsignedInt(b[2]) << 8;
                                  length += Byte.toUnsignedInt(b[3]);
                              } else if (rLength == (byte) 127)
                                  rMaskIndex = 10;
                              for (i = rMaskIndex; i < (rMaskIndex + 4); i++) {
                                  masks[j] = b[i];
                                  j++;
                              }
      
                              rDataStart = rMaskIndex + 4;
      
                              message = new byte[length];
                              totalLength = length + rDataStart;
                              for (i = rDataStart, totalRead = 0; i<len && i < totalLength; i++, totalRead++) {
                                  message[totalRead] = (byte) (b[i] ^ masks[totalRead % 4]);
                              }
      
                          }else {
                              for (i = 0; i<len && totalRead<length; i++, totalRead++) {
                                  message[totalRead] = (byte) (b[i] ^ masks[totalRead % 4]);
                              }
                              totalLength=i;
                          }
      
                          
                          if (totalRead<length) {
                              isSplit=true;
                          }else {
                              isSplit=false;
                              System.out.println(new String(message)); 
                              b = new byte[8000];
                          }
                          
                          if (totalLength < len) {
                              more = true;
                              for (i = totalLength, j = 0; i < len; i++, j++)
                                  b[j] = b[i];
                              len = len - totalLength;
                          }else
                              more = false;
                      } while (more);
                  } else
                      break;
              }
      
          }
      
      
          public static byte[] encode(String mess) throws IOException{
              byte[] rawData = mess.getBytes();
      
              int frameCount  = 0;
              byte[] frame = new byte[10];
      
              frame[0] = (byte) 129;
      
              if(rawData.length <= 125){
                  frame[1] = (byte) rawData.length;
                  frameCount = 2;
              }else if(rawData.length >= 126 && rawData.length <= 65535){
                  frame[1] = (byte) 126;
                  int len = rawData.length;
                  frame[2] = (byte)((len >> 8 ) & (byte)255);
                  frame[3] = (byte)(len & (byte)255); 
                  frameCount = 4;
              }else{
                  frame[1] = (byte) 127;
                  long len = rawData.length; //note an int is not big enough in java
                  frame[2] = (byte)((len >> 56 ) & (byte)255);
                  frame[3] = (byte)((len >> 48 ) & (byte)255);
                  frame[4] = (byte)((len >> 40 ) & (byte)255);
                  frame[5] = (byte)((len >> 32 ) & (byte)255);
                  frame[6] = (byte)((len >> 24 ) & (byte)255);
                  frame[7] = (byte)((len >> 16 ) & (byte)255);
                  frame[8] = (byte)((len >> 8 ) & (byte)255);
                  frame[9] = (byte)(len & (byte)255);
                  frameCount = 10;
              }
      
              int bLength = frameCount + rawData.length;
      
              byte[] reply = new byte[bLength];
      
              int bLim = 0;
              for(int i=0; i<frameCount;i++){
                  reply[bLim] = frame[i];
                  bLim++;
              }
              for(int i=0; i<rawData.length;i++){
                  reply[bLim] = rawData[i];
                  bLim++;
              }
      
              return reply;
          }
      
          private static void doHandShakeToInitializeWebSocketConnection(InputStream inputStream, OutputStream outputStream) throws UnsupportedEncodingException {
              String data = new Scanner(inputStream,"UTF-8").useDelimiter("\\r\\n\\r\\n").next();
      
              Matcher get = Pattern.compile("^GET").matcher(data);
      
              if (get.find()) {
                  Matcher match = Pattern.compile("Sec-WebSocket-Key: (.*)").matcher(data);
                  match.find();                 
      
                  byte[] response = null;
                  try {
                      response = ("HTTP/1.1 101 Switching Protocols\r\n"
                              + "Connection: Upgrade\r\n"
                              + "Upgrade: websocket\r\n"
                              + "Sec-WebSocket-Accept: "
                              + DatatypeConverter.printBase64Binary(
                                      MessageDigest
                                      .getInstance("SHA-1")
                                      .digest((match.group(1) + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
                                              .getBytes("UTF-8")))
                              + "\r\n\r\n")
                              .getBytes("UTF-8");
                  } catch (NoSuchAlgorithmException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
      
                  try {
                      outputStream.write(response, 0, response.length);
                  } catch (IOException e) {
                      // TODO Auto-generated catch block
                      e.printStackTrace();
                  }
              } else {
      
              }
          }
      }
      

      这解决了 Stefan 回答的问题。它可以在一次读取中处理多条消息,并且可以在一次读取中拆分消息。还修复了使用 int 而不是 long in 写入的问题。

      【讨论】:

      • 欢迎来到 Stack Overflow!虽然您的回答可能会解决问题,但 including an explanation 关于如何以及为什么解决问题将真正有助于提高您的帖子质量,并可能导致更多的赞成票。请记住,您正在为将来的读者回答问题,而不仅仅是现在提问的人。您可以编辑您的答案以添加解释并指出适用的限制和假设。 - From Review
      猜你喜欢
      • 1970-01-01
      • 2013-03-13
      • 1970-01-01
      • 2017-03-20
      • 1970-01-01
      • 1970-01-01
      • 2015-05-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多