与Java服务器和Javascript客户端建立WebSocket连接

Establishing WebSocket connection with Java server and Javascript client

本文关键字:建立 WebSocket 连接 客户端 Javascript Java 服务器      更新时间:2023-09-26

我正在尝试用基于javascript的客户端和基于java的服务器实现WebSockets。我想我已经做了所有正确的步骤,但是由于某种原因,我无法将两者都连接起来。

当服务端套接字接收到一个连接时,它处理形成一个websocket-accept响应,然后发送回客户端,但是客户端套接字中的连接立即关闭,奇怪的是没有握手问题。

有谁知道可能是什么问题吗?

下面是我用java实现的服务器代码:

package server;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import server.message.Message;
import server.message.SpeakMessage;

public class Server implements ConnectionListener {
    private static final int PORT = 1509;
    private MessageDispatcher dispatcher = new MessageDispatcher();
    private List<ConnectionManager> clients = new ArrayList<>();
    public void listen() {
        try (ServerSocket server = new ServerSocket(PORT)) {
            System.out.printf("Listening on port %d...%n", PORT);
            while (true) {
                System.out.println("Waiting for connection...");
                Socket client = server.accept();
                System.out.println("Incoming connection - Attempting to establish connection...");
                ConnectionManager manager = new ConnectionManager(client, dispatcher, this);
                manager.start();
            }
        } catch (IOException e) {
            System.out.println("Unable to start server");
            e.printStackTrace();
        }
        System.exit(0);
    }
    public void execute() {
        try {
            while (true) {
                if (dispatcher.isEmpty()) {
                    Thread.sleep(100);
                    continue;
                }
                Message msg = dispatcher.read();
                if (msg instanceof SpeakMessage)
                    broadcast(MessageEncoder.spoke(((SpeakMessage) msg).getText()));
            }
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }
    public static void main(String[] args) {
        final Server server = new Server();
        new Thread(new Runnable() {
            @Override
            public void run() {
                server.listen();
            }
        }).start();
        server.execute();
    }
    public synchronized void broadcast(byte[] message) {
        for (ConnectionManager client : clients) {
            client.send(message);
        }
    }
    @Override
    public synchronized void clientConnected(ConnectionManager who) {
        clients.add(who);
        System.out.println("Connected client " + clients.size());
    }
    @Override
    public synchronized void clientDisconnected(ConnectionManager who) {
        clients.remove(who);
    }
}

下面是server的子类ConnectionManager:

package server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.Socket;
import java.security.MessageDigest;
import java.util.Properties;
import server.message.HandshakeMessage;
import server.message.Message;

public class ConnectionManager {
    private static final int CLIENT_VERSION = 1;
    private Socket socket;
    private MessageDecoder decoder = new MessageDecoder();
    private MessageDispatcher dispatcher;
    private ConnectionListener listener;
    public ConnectionManager(Socket connection, MessageDispatcher dispatcher, ConnectionListener listener) {
        socket = connection;
        this.dispatcher = dispatcher;
        this.listener = listener;
    }
    public void start() {
        Thread t = new Thread(new ChannelReader());
        t.setName("Client thread");
        t.setDaemon(true);
        t.start();
    }
    public void send(byte[] data) {
        if (socket == null)
            return;
        try {
            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            dos.write(data);
            dos.flush();
        } catch (IOException e) {
            disconnect("Client closed the connection");
        }
    }
    private class ChannelReader implements Runnable {
        private boolean accepted = false;
        private String ret = null;
        @Override
        public void run() {
            try {
                DataInputStream in = new DataInputStream(socket.getInputStream());
                while (socket != null && socket.isConnected()) {
                    int len = in.readShort();
                    if (len < 0) {
                        disconnect("Invalid message length.");
                    }
                    String s;
                    readLine(in);
                    Properties props = new Properties();
                    while((s=readLine(in)) != null && !s.equals("")) {
                        String[] q = s.split(": ");
                        props.put(q[0], q[1]);
                    }
                    if(props.get("Upgrade").equals("websocket") && props.get("Sec-WebSocket-Version").equals("13")) { // check if is websocket 8
                        String key = (String) props.get("Sec-WebSocket-Key");
                        String r = key + "" + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; // magic key
                        MessageDigest md = MessageDigest.getInstance("SHA-1");
                        md.reset();
                        md.update(r.getBytes());
                        byte[] sha1hash = md.digest();

                        String returnBase = base64(sha1hash);

                        ret = "HTTP/1.1 101 Switching Protocols'r'n";
                            ret+="Upgrade: websocket'r'n";
                            ret+="Connection: Upgrade'r'n";
                            ret+="Sec-WebSocket-Accept: "+returnBase;
                    } else {
                        disconnect("Client got wrong version of websocket");
                    }
                    Message msg = decoder.decode((String) props.get("Sec-WebSocket-Protocol"));
                    if (!accepted) {
                        doHandshake(msg);
                    } else if (dispatcher != null) {
                        dispatcher.dispatch(msg);
                    }
                }
            } catch (Exception e) {
                disconnect(e.getMessage());
                e.printStackTrace();
            }
        }
        private void doHandshake(Message msg) {
            if (!(msg instanceof HandshakeMessage)) {
                disconnect("Missing handshake message");
                return;
            }
            HandshakeMessage handshake = (HandshakeMessage) msg;
            if (handshake.getVersion() != CLIENT_VERSION) {
                disconnect("Client failed in handshake.");
                return;
            }
            send(ret.getBytes());
            accepted = true;
            listener.clientConnected(ConnectionManager.this);
        }   
        private String base64(byte[] input) throws ClassNotFoundException, 
        SecurityException, NoSuchMethodException, IllegalArgumentException, 
        IllegalAccessException, InvocationTargetException, InstantiationException {
            Class<?> c = Class.forName("sun.misc.BASE64Encoder");
            java.lang.reflect.Method m = c.getMethod("encode", new Class<?>[]{byte[].class});
            String s = (String) m.invoke(c.newInstance(), input);
            return s;
        }
        private String readLine(InputStream in) {
            try{
                String line = "";
                int pread;
                int read = 0;
                while(true) {
                    pread = read;
                    read = in.read();
                    if(read!=13&&read!=10)
                        line += (char) read;
                    if(pread==13&&read==10) break;
                }
                return line;
            }catch(IOException ex){
            }
            return null;
        }
    }
    public synchronized void disconnect(String message) {
        System.err.println(message);
        if (socket != null) {
            try {
                socket.close();
            } catch (IOException e) {
            }
        }
        socket = null;
        listener.clientDisconnected(ConnectionManager.this);
    }
}

和MessageDispatcher:

package server;
import java.util.Queue;
import java.util.concurrent.LinkedBlockingDeque;
import server.message.Message;

public class MessageDispatcher {
    Queue<Message> messageQueue = new LinkedBlockingDeque<>();
    public void dispatch(Message message) {
        messageQueue.offer(message);
    }
    public Message read() {
        return messageQueue.poll();
    }
    public boolean isEmpty() {
        return messageQueue.isEmpty();
    }
}

和这里我的客户端代码实现在javascript:

var canvas, // Canvas DOM element
    ctx,    // Canvas rendering context
    socket; // Socket connection
function init() {
    // Initialise the canvas
    canvas = document.getElementById("gameCanvas");
    ctx = canvas.getContext("2d");
    // Maximise the canvas
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    // Initialise socket connection
    if (window.WebSocket) { 
        socket = new WebSocket("ws://localhost:1509/", ["1", "YURI"]);
        socket.onopen = onSocketConnected();
        socket.onclose = onSocketDisconnect();
        socket.onmessage = onSocketMessage();
        socket.onerror = onSocketError();
    } else {
        alert("The browser does not support websocket.");
    }
};
// Socket message
function onSocketMessage(message) {
    console.log('Message: ' + message.data);
};
// Socket error
function onSocketError(error) {
    console.log('Error: ' + error.data);
};
// Socket connected
function onSocketConnected() {
    console.log("Connected to socket server");
};
// Socket disconnected
function onSocketDisconnect() {
    console.log("Disconnected from socket server");
};

我认为,这是因为你在Java服务器端使用Socket包,在客户端使用WebSocket API。你的想法很好,但是技术不对。保持WebSocket在客户端(Javascript),因为你没有很多其他的可能性,但尝试JWebSocket在服务器端(Java)。事实上,WebSocket使用的是TCP/IP,但是它自己的通信协议是基于TCP/IP的。Java套接字包是纯粹的TCP/IP。用JWebSocket重写你的服务器,所有关于JWebSocket的细节可以在下面找到:http://jwebsocket.org/。我希望我的回答能对你有所帮助。

必须使用"'r'n'r'n"

指定返回包的结束
  ret = "HTTP/1.1 101 Switching Protocols'r'n";
   ret+="Upgrade: websocket'r'n";
   ret+="Connection: Upgrade'r'n";
   ret+="Sec-WebSocket-Accept: "+returnBase + "'r'n'r'n";

public class WSKeyGenerator {
    private final static String MAGIC_KEY =
            "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    public static String getKey(String strWebSocketKey) throws
            NoSuchAlgorithmException {
        strWebSocketKey += MAGIC_KEY;
        MessageDigest shaMD = MessageDigest.getInstance("SHA-1");
        shaMD.reset();
        shaMD.update(strWebSocketKey.getBytes());
        byte messageDigest[] = shaMD.digest();
        BASE64Encoder b64 = new BASE64Encoder();
        return b64.encode(messageDigest);
    }
}

我建议使用http://websocket.org/echo.html来检查服务器的websocket功能