HTML5 客户端通过在 html5 websocket 客户端中提供客户端来减少程序员的工作量。学习如何在 java 中将这个 html5 websocket 客户端与服务器一起使用对许多程序员来说都是有益的。
我想创建一个与 java 服务器通信的HTML5 客户端示例,但我无法找到如何做到这一点的方法。任何人都可以点亮它吗?
我在http://java.dzone.com/articles/creating-websocket-chat上找到了一个演示,但它对我不起作用..
HTML5 客户端通过在 html5 websocket 客户端中提供客户端来减少程序员的工作量。学习如何在 java 中将这个 html5 websocket 客户端与服务器一起使用对许多程序员来说都是有益的。
我想创建一个与 java 服务器通信的HTML5 客户端示例,但我无法找到如何做到这一点的方法。任何人都可以点亮它吗?
我在http://java.dzone.com/articles/creating-websocket-chat上找到了一个演示,但它对我不起作用..
我已经实现了一个简单的java 服务器端示例,我们可以看看。我首先创建一个 ServerSocket,它侦听端口 2005 上的连接
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();
}
}
正如RFC 标准中定义的 websocket 协议,当客户端通过 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
现在我们可以使用键映射在握手过程中创建相应的响应。引用 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 连接。所以现在怎么办?我们如何让他们互相交谈?我们可以从服务器向客户端发送消息开始。注意!从这一点开始,我们不再使用 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())。
那不是太难了吗?接收来自客户端的消息怎么样?好吧,它有点复杂,但坚持下去!
来自客户端的帧 sendt 的结构几乎与来自服务器的帧 sendt 的结构相同。第一个字节是消息类型,第二个字节是有效载荷长度。然后有一个区别:接下来的四个字节代表一个掩码。什么是掩码,为什么来自客户端的消息被屏蔽,而服务器消息却没有?从 RFC - 第 5.1 节,我们可以看到:
...客户端必须屏蔽它发送给服务器的所有帧...服务器不能屏蔽它发送给客户端的任何帧。
所以简单的答案是:我们必须这样做。那么为什么我们必须这样做,你可能会问?我不是告诉过你阅读 RFC 吗?
继续前进,在帧中的四字节掩码之后,被掩码的有效载荷紧随其后。还有一件事,客户端必须将帧中最左边的第 9 位设置为 1,以告诉服务器消息已被屏蔽(查看 RFC 中整洁的 ASCII-art 帧 - 第 5.2 节)。最左边的第 9 位对应于我们第二个字节中的最左边的位,但是嘿,那是我们的有效载荷长度字节!这意味着来自我们客户端的所有消息的有效负载长度字节等于 0b10000000 = 0x80 + 实际有效负载长度。因此,要找出真正的有效载荷长度,我们必须从有效载荷长度字节(帧中的第二个字节)中减去 0x80、128 或 0b10000000(或您可能喜欢的任何其他数字系统)。
哇,好吧..听起来很复杂...对于“TLDR”-伙计们,摘要:从第二个字节中减去 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>
希望这会澄清一些事情,并且我对此有所了解:)
使用来自客户端的 jQuery ajax 请求,并在服务器端使用休息服务。
这里关于使用 Rest Service 创建战争模块
这里是关于 jQuery ajax
要编写 Java 套接字服务器,您只需要创建主程序
try
{
final ServerSocket ss = new ServerSocket(8001);
while (true)
{
final Socket s = ss.accept();
// @todo s.getInputStream();
}
}
catch (final IOException ex)
{
//
}
它是服务器部分的主要级联
尝试阅读此博客。它涵盖了如何使用 spring 框架来完成你的工作。如果尚未添加,应尽快添加全面支持。
http://keaplogik.blogspot.com.au/2012/05/atmosphere-websockets-comet-with-spring.html?m=1
我还建议检查春季发行说明。
You are running GlassFish. Web sockets are not enabled by default in it. To enable them you must execute the following single-line command on your domain:
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true
HttpServlet.init(...)
method is called by the servlet container to indicate to a servlet that the servlet is being placed into service.* So, your log message there doesn't represent the truth.
这与上面的代码相同,只是它允许您从客户端接收超过 126 个字节的消息。很多web socket源代码都没有搞清楚分片。
// Modified code from Anders, - Christopher Price
package GoodExample;
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;
public class JfragWS {
public static final int MASK_SIZE = 4;
public static final int SINGLE_FRAME_UNMASKED = 0x81;
private ServerSocket serverSocket;
private Socket socket;
public JfragWS() throws IOException {
serverSocket = new ServerSocket(1337);
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"
+ "Origin: http://face2fame.com\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());
System.out.println("Enter data to send");
}
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
t.start();
}
public String reiceveMessage() throws IOException {
String EasyBytes = null;
byte[] buf = readBytes(2); // our initial header
convertAndPrint(buf);
//System.exit(0);
EasyBytes = (String.format("%02X ", buf[1]));
int payloadadder = 0;
if (EasyBytes.contains("FE")){ // Indicates extended message
byte[] buf2 = readBytes(1);
int a = (buf2[0] & 0xff) + 1; // if byte is zero there is one extra fragment so add 1!
System.out.println("Number of extra bytes" + a);
payloadadder = 2; // account for original header size
byte[] adder = null;
//String MagnificentString = "";
for (int x = 0; x < a; x++){
if(x==0){
adder = readBytes(1);
//MagnificentString += String.format("%02X ", adder[0]);
payloadadder += ((adder[0] & 0xFF) - 0x80);}
if(x==1){
payloadadder = (buf[1] & 0xFF) + (adder[0] & 0xFF);
}
if(x>1){
payloadadder = (Integer.parseInt((String.format("%02X", buf2[0]) + String.format("%02X", adder[0])), 16));
//System.out.println(String.format("%02X", buf2[0]) + String.format("%02X", adder[0]));
}
}
System.out.println("Overflow in byte/s " + payloadadder);
//System.out.println("Our Hex String " + MagnificentString);
//System.exit(0);
}
//convertAndPrint(buf);
//dont use this byte[] buf2 = readBytes(4);
System.out.println("Headers:");
//convertAndPrint(buf2);// Check out the byte sizes
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 {
int payloadSize = 0;
if (payloadadder <= 0){
payloadSize = getSizeOfPayload(buf[1]);}
else {
payloadSize = getSizeOfPayload(buf[1]) + payloadadder;
}
// if (extendedsize>=126){
//payloadSize = extendedsize;}
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
int a = b & 0xff;
//System.out.println("PAYLOAD SIZE INT" + a);
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 boolean convertAndPrintHeader(byte[] bytes) {
StringBuilder sb = new StringBuilder();
String CaryOverDetection = new String();
// We must test byte 2 specifically for this. In the next step we add length bytes perhaps?
//for(int i = 0; i < bytes.length; i++) {
//}
for (byte b : bytes) {
CaryOverDetection = (String.format("%02X ", b));
if (CaryOverDetection.contains("FE")){
return false;
}
sb.append(String.format("%02X ", b));
}
System.out.println(sb.toString());
return true;
}
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 {
JfragWS j = new JfragWS();
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
while (true) {
System.out.println("Write something to the client!");
j.sendMessage(br.readLine().getBytes());
}
}
}
您还可以使用现有的框架来实现这一点,例如:jWebsocket