[英]using html5 client with a server in java
HTML5客戶端通過在html5 websocket客戶端中提供客戶端來減少程序員的工作量。 對於許多程序員來說,學習如何在java中使用這個帶有服務器的html5 websocket客戶端將是有益的。
我想創建一個HTML5客戶端與Java服務器通信的示例,但我無法找到如何做到這一點的方式。 任何人都可以點亮它嗎?
我在http://java.dzone.com/articles/creating-websocket-chat上找到了一個演示,但它對我不起作用..
我已經實現了一個簡單的 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>
希望這會清除一些東西,並且我對它有所了解:)
使用來自客戶端的jQuery ajax請求,以及服務器端的其他服務。
這里是關於使用Rest Service創建war模塊
在這里abour 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
我還建議檢查彈簧釋放說明。
您正在運行GlassFish。 默認情況下,Web套接字未啟用。 要啟用它們,您必須在域上執行以下單行命令:
asadmin set configs.config.server-config.network-config.protocols.protocol.http-listener-1.http.websockets-support-enabled=true
servlet容器調用HttpServlet.init(...)
方法,以向servlet指示servlet正在投入服務。 *所以,你的日志消息並不代表真相。
您也可以使用現有框架來實現這一點: jWebsocket
這是上面相同的代碼,它只允許您從客戶端接收超過126字節的消息。 許多Web套接字源代碼都沒有弄清楚碎片。
// 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());
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.