Hem*_*lia 8 java html5 webserver client websocket
HTML5客户端通过在html5 websocket客户端中提供客户端来减少prograamers的工作量.对于许多程序员来说,学习如何在java中使用这个带有服务器的html5 websocket客户端将是有益的.
我想创建一个HTML5客户端与Java服务器通信的示例,但我无法找到如何做到这一点的方式.任何人都可以点亮它吗?
我在http://java.dzone.com/articles/creating-websocket-chat上找到了一个演示,但它对我不起作用..
And*_*dak 19
我已经实现了一个简单的 java服务器端示例,我们可以看一下.我首先创建一个在端口2005上侦听连接的ServerSocket
public class WebsocketServer {
public static final int MASK_SIZE = 4;
public static final int SINGLE_FRAME_UNMASKED = 0x81;
private ServerSocket serverSocket;
private Socket socket;
public WebsocketServer() throws IOException {
    serverSocket = new ServerSocket(2005);
    connect();
}
private void connect() throws IOException {
    System.out.println("Listening");
    socket = serverSocket.accept();
    System.out.println("Got connection");
    if(handshake()) {
         listenerThread();
    }
}
正如websocket协议的RFC标准中所定义的,当客户端通过websocket连接时,必须进行握手.所以让我们来看看handshake()方法,它非常难看,所以会逐步走过它:第一部分读取客户端握手.
private boolean handshake() throws IOException {
    PrintWriter out = new PrintWriter(socket.getOutputStream());
    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    //This hashmap will be used to store the information given to the server in the handshake
    HashMap<String, String> keys = new HashMap<>();
    String str;
    //Reading client handshake, handshake ends with CRLF which is again specified in the RFC, so we keep on reading until we hit ""...
    while (!(str = in.readLine()).equals("")) {
        //Split the string and store it in our hashmap
        String[] s = str.split(": ");
        System.out.println(str);
        if (s.length == 2) {
            keys.put(s[0], s[1]);
        }
    }
根据RFC - 第1.2节,客户端握手看起来像这样(这是chrome给我的版本22.0.1229.94 m)!
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:2005
Origin: null
Sec-WebSocket-Key: PyvrecP0EoFwVnHwC72ecA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
现在我们可以使用keys-map在握手过程中创建相应的响应.引用RFC:
为了证明接收到握手,服务器必须获取两条信息并将它们组合以形成响应.第一条信息来自| Sec-WebSocket-Key | 客户端握手中的头字段.对于此头字段,服务器必须获取该值并将其与字符串形式的全局唯一标识符"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"连接,这不太可能被不了解该字符串的网络端点使用WebSocket协议.然后在服务器的握手中返回此并置的SHA-1散列(160位),base64编码.
这就是我们要做的!使用魔术字符串连接Sec-WebSocket-Key,使用SHA-1散列函数对其进行散列,并对其进行Base64编码.这就是下一个丑陋的单线所做的事情.
String hash;
try {
    hash = new BASE64Encoder().encode(MessageDigest.getInstance("SHA-1").digest((keys.get("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()));
} catch (NoSuchAlgorithmException ex) {
    ex.printStackTrace();
    return false;
}
然后我们只需将创建的新哈希返回到"Sec-WebSocket-Accept"字段中的预期响应.
    //Write handshake response
    out.write("HTTP/1.1 101 Switching Protocols\r\n"
            + "Upgrade: websocket\r\n"
            + "Connection: Upgrade\r\n"
            + "Sec-WebSocket-Accept: " + hash + "\r\n"
            + "\r\n");
    out.flush();
    return true;
}
我们现在已经在客户端和服务器之间建立了成功的websocket连接.所以现在怎么办?我们如何让他们互相交谈?我们可以从服务器向客户端发送消息开始.NB!我们从这一点开始,不再与HTTP客户端交谈了.现在我们必须通信发送纯字节,并解释传入的字节.那我们该怎么做呢?
来自服务器的消息必须采用称为"帧"的特定格式,如RFC中所述 - 第5.6节.当从服务器发送消息时,RFC指出第一个字节必须指定它是什么类型的帧.值为0x81的字节告诉客户端我们正在发送"单帧未屏蔽文本消息",基本上是 - 文本消息.后续字节必须表示消息的长度.接下来是数据或有效负载.好吧,好吧......让我们实现!
public void sendMessage(byte[] msg) throws IOException {
        System.out.println("Sending to client");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        BufferedOutputStream os = new BufferedOutputStream(socket.getOutputStream());
        //first byte is kind of frame
        baos.write(SINGLE_FRAME_UNMASKED);
        //Next byte is length of payload
        baos.write(msg.length);
        //Then goes the message
        baos.write(msg);
        baos.flush();
        baos.close();
        //This function only prints the byte representation of the frame in hex to console
        convertAndPrint(baos.toByteArray());
        //Send the frame to the client
        os.write(baos.toByteArray(), 0, baos.size());
        os.flush();
}
因此,要向客户端发送消息,我们只需调用sendMessage("Hello,client!".getBytes()).
那不是太难吗?如何从客户端收到消息?嗯,它有点复杂,但挂在那里!
来自客户端的帧发送几乎与来自服务器的帧发送的结构相同.第一个字节是消息类型,第二个字节是有效负载长度.然后有一个区别:接下来的四个字节代表一个掩码.什么是掩码,为什么来自客户端的消息被屏蔽,但服务器消息不是?从RFC - 5.1节,我们可以看到:
...客户端必须屏蔽它发送给服务器的所有帧...服务器不得屏蔽它发送给客户端的任何帧.
所以简单的答案是:我们必须这样做.那么我们为什么要这样,你可能会问?我没有告诉你阅读RFC吗?
继续,在帧中的四字节掩码之后,屏蔽的有效负载继续.还有一件事,客户端必须将帧中第9个最左位设置为1,告诉服务器消息被屏蔽(在RFC中查看整齐的ASCII艺术帧 - 第5.2节).第9个最左边的位对应于我们第二个字节中最左边的位,但是,嘿,这是我们的有效负载长度字节!这意味着来自客户端的所有消息的有效负载长度字节均等于0b10000000 = 0x80 +实际有效负载长度.因此,要找出实际有效载荷长度,我们必须从有效载荷长度字节(帧中的第二个字节)中减去0x80,或128或0b10000000(或您可能更喜欢的任何其他数字系统).
哇,好吧..听起来很复杂...对于你"TLDR"-guys,摘要:从第二个字节中减去0x80以获得有效载荷长度......
public String reiceveMessage() throws IOException {
    //Read the first two bytes of the message, the frame type byte - and the payload length byte
    byte[] buf = readBytes(2);
    System.out.println("Headers:");
    //Print them in nice hex to console
    convertAndPrint(buf);
    //And it with 00001111 to get four lower bits only, which is the opcode
    int opcode = buf[0] & 0x0F;
    //Opcode 8 is close connection
    if (opcode == 8) {
        //Client want to close connection!
        System.out.println("Client closed!");
        socket.close();
        System.exit(0);
        return null;
    } 
    //Else I just assume it's a single framed text message (opcode 1)
    else {
        final int payloadSize = getSizeOfPayload(buf[1]);
        System.out.println("Payloadsize: " + payloadSize);
        //Read the mask, which is 4 bytes, and than the payload
        buf = readBytes(MASK_SIZE + payloadSize);
        System.out.println("Payload:");
        convertAndPrint(buf);
        //method continues below!
现在我们已经阅读了整个消息,现在是时候取消屏蔽它,这样我们就可以了解有效负载.为了取消屏蔽它,我创建了一个方法,它接受掩码和有效负载作为参数,并返回解码的有效负载.所以通过以下方式完成调用:
    buf = unMask(Arrays.copyOfRange(buf, 0, 4), Arrays.copyOfRange(buf, 4, buf.length));
    String message = new String(buf);
    return message;
    }
}
现在unMask方法相当甜蜜而且很小
private byte[] unMask(byte[] mask, byte[] data) {
        for (int i = 0; i < data.length; i++) {
              data[i] = (byte) (data[i] ^ mask[i % mask.length]);
        }
        return data;
}
getSizeOfPayload也是如此:
private int getSizeOfPayload(byte b) {
    //Must subtract 0x80 from (unsigned) masked frames
    return ((b & 0xFF) - 0x80);
}
就这样!您现在应该能够使用纯套接字在两个方向上进行通信.为了完整起见,我将添加完整的Java类.它能够使用websockets与客户端接收和发送消息.
package javaapplication5;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
import sun.misc.BASE64Encoder;
/**
 *
 * @author
 * Anders
 */
public class WebsocketServer {
    public static final int MASK_SIZE = 4;
    public static final int SINGLE_FRAME_UNMASKED = 0x81;
    private ServerSocket serverSocket;
    private Socket socket;
    public WebsocketServer() throws IOException {
    serverSocket = new ServerSocket(2005);
    connect();
    }
    private void connect() throws IOException {
    System.out.println("Listening");
    socket = serverSocket.accept();
    System.out.println("Got connection");
    if(handshake()) {
        listenerThread();
    }
    }
    private boolean handshake() throws IOException {
    PrintWriter out = new PrintWriter(socket.getOutputStream());
    BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    HashMap<String, String> keys = new HashMap<>();
    String str;
    //Reading client handshake
    while (!(str = in.readLine()).equals("")) {
        String[] s = str.split(": ");
        System.out.println();
        System.out.println(str);
        if (s.length == 2) {
        keys.put(s[0], s[1]);
        }
    }
    //Do what you want with the keys here, we will just use "Sec-WebSocket-Key"
    String hash;
    try {
        hash = new BASE64Encoder().encode(MessageDigest.getInstance("SHA-1").digest((keys.get("Sec-WebSocket-Key") + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()));
    } catch (NoSuchAlgorithmException ex) {
        ex.printStackTrace();
        return false;
    }
    //Write handshake response
    out.write("HTTP/1.1 101 Switching Protocols\r\n"
        + "Upgrade: websocket\r\n"
        + "Connection: Upgrade\r\n"
        + "Sec-WebSocket-Accept: " + hash + "\r\n"
        + "\r\n");
    out.flush();
    return true;
    }
    private byte[] readBytes(int numOfBytes) throws IOException {
    byte[] b = new byte[numOfBytes];
    socket.getInputStream().read(b);
    return b;
    }
    public void sendMessage(byte[] msg) throws IOException {
    System.out.println("Sending to client");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    BufferedOutputStream os = new BufferedOutputStream(socket.getOutputStream());
    baos.write(SINGLE_FRAME_UNMASKED);
    baos.write(msg.length);
    baos.write(msg);
    baos.flush();
    baos.close();
    convertAndPrint(baos.toByteArray());
    os.write(baos.toByteArray(), 0, baos.size());
    os.flush();
    }
    public void listenerThread() {
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
        try {
            while (true) {
            System.out.println("Recieved from client: " + reiceveMessage());
            }
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        }
    });
    t.start();
    }
    public String reiceveMessage() throws IOException {
    byte[] buf = readBytes(2);
    System.out.println("Headers:");
    convertAndPrint(buf);
    int opcode = buf[0] & 0x0F;
    if (opcode == 8) {
        //Client want to close connection!
        System.out.println("Client closed!");
        socket.close();
        System.exit(0);
        return null;
    } else {
        final int payloadSize = getSizeOfPayload(buf[1]);
        System.out.println("Payloadsize: " + payloadSize);
        buf = readBytes(MASK_SIZE + payloadSize);
        System.out.println("Payload:");
        convertAndPrint(buf);
        buf = unMask(Arrays.copyOfRange(buf, 0, 4), Arrays.copyOfRange(buf, 4, buf.length));
        String message = new String(buf);
        return message;
    }
    }
    private int getSizeOfPayload(byte b) {
    //Must subtract 0x80 from masked frames
    return ((b & 0xFF) - 0x80);
    }
    private byte[] unMask(byte[] mask, byte[] data) {
    for (int i = 0; i < data.length; i++) {
        data[i] = (byte) (data[i] ^ mask[i % mask.length]);
    }
    return data;
    }
    private void convertAndPrint(byte[] bytes) {
    StringBuilder sb = new StringBuilder();
    for (byte b : bytes) {
        sb.append(String.format("%02X ", b));
    }
    System.out.println(sb.toString());
    }
    public static void main(String[] args) throws IOException, InterruptedException, NoSuchAlgorithmException {
    WebsocketServer j = new WebsocketServer();
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    while (true) {
        System.out.println("Write something to the client!");
        j.sendMessage(br.readLine().getBytes());
    }
    }
}
一个简单的html客户端:
<!DOCTYPE HTML>
<html>
<body>
<button type="button" onclick="connect();">Connect</button>
<button type="button" onclick="connection.close()">Close</button>
<form>
<input type="text" id="msg" />
<button type="button" onclick="sayHello();">Say Hello!</button>
<script>
var connection;
function connect() {
    console.log("connection");
    connection = new WebSocket("ws://localhost:2005/");
    // Log errors
connection.onerror = function (error) {
  console.log('WebSocket Error ');
  console.log(error);
};
// Log messages from the server
connection.onmessage = function (e) {
  console.log('Server: ' + e.data); 
  alert("Server said: " + e.data);
};
connection.onopen = function (e) {
console.log("Connection open...");
}
connection.onclose = function (e) {
console.log("Connection closed...");
}
}
function sayHello() {
    connection.send(document.getElementById("msg").value);
}
function close() {
    console.log("Closing...");
    connection.close();
}
</script>
</body>
</html>
希望这会清除一些东西,并且我对它有所了解:)
| 归档时间: | 
 | 
| 查看次数: | 13947 次 | 
| 最近记录: |