目錄
目錄
WS協議簡介
HTTP協議是半雙工通信,同一時刻只有一個方向上的數據傳送,基於請求-響應式工作模式,只能客戶端主動向服務器發請求,服務器無法向客戶端主動發請求,而WebSocket 是 HTML5 開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。它有以下特點:
- 單一的TCP連接,採用全雙工模式通信
- 無頭部信息、Cookie和身份驗證
- 服務器可雙主動發送消息給客戶端,不需要客戶端輪詢
- 通過ping/pong 幀保持鏈接激活
服務器實現類
服務端
public class WebsocketServer {
private int port;
public WebsocketServer(int port){
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGoup = new NioEventLoopGroup(8);
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGoup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.DEBUG))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// websocket協議是基於http之上的升級,因此加入HttpServerCodec
pipeline.addLast(new HttpServerCodec());
// 以塊進行寫操作,因此加入ChunkedWriteHandler
pipeline.addLast(new ChunkedWriteHandler());
// http數據在傳輸過程中會分段,因此加入 HttpObjectAggregator將多個段聚合
pipeline.addLast(new HttpObjectAggregator(4096));
// 加入websocket協議處理器,將 http 協議升級爲 ws 協議 , 保持長連接
pipeline.addLast(new WebSocketServerProtocolHandler("/chat"));
// 加入業務處理器
pipeline.addLast(new MyTextWebSocketFrame());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(7777).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGoup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new WebsocketServer(7777).run();
}
}
業務處理器
public class MyTextWebSocketFrame extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
public void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("服務器收到消息 " + msg.text());
// 回覆消息
ctx.channel().writeAndFlush(new TextWebSocketFrame("服務器回覆消息:你好,"
+ ctx.channel().remoteAddress().toString()
+"當前時間是" + LocalDateTime.now() ));
}
// 當 web 客戶端連接後, 觸發方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(channel.id().asLongText() + "加入了");
}
// 當 web 客戶端斷開連接後, 觸發方法
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
Channel channel = ctx.channel();
System.out.println(channel.id().asLongText() + "離開了");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
客戶端實現
基於HTML5實現
客戶端使用HTML5內置的WebSocket對象進行與服務端通信
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Websocket客戶端</title>
<script type="text/javascript">
var websocket;
if (window.WebSocket){
// 創建ws連接
websocket = new WebSocket("ws://localhost:7777/chat")
websocket.open = function(ev){
// 提示連接建立
let responseContent = document.getElementById("responseContent");
responseContent.value += ("\n連接建立。。。")
}
websocket.onmessage = function (ev) {
// 回顯內容到頁面
let responseContent = document.getElementById("responseContent");
responseContent.value += ("\n" + ev.data)
}
websocket.close = function (ev) {
// 提示連接斷開
let responseContent = document.getElementById("responseContent");
responseContent.value += ("\n連接斷開。。。")
}
}else{
alert("當前瀏覽器不支持 websocket")
}
function sendMsg() {
let sendContent = document.getElementById("sendContent");
let content = sendContent.value
alert(content)
if(!window.websocket)return
if (websocket.readyState == WebSocket.OPEN){
// 通過websocket發送消息到服務器
websocket.send(content)
}else{
alert("連接沒有建立")
}
}
</script>
</head>
<body>
<form onsubmit="return false">
<label>消息:</label><input id="sendContent" size="80" ></input><button onclick="sendMsg()">發送</button><br/>
<label>響應:</label><textarea id="responseContent" cols="80" rows="10" ></textarea><br/>
</form>
</body>
</html>
基於OKHttp的實現
在安卓移動應用開發中,網絡請求框架okhttp已支持Websocket,因此我們使用okhttp創建websocket客戶端
public class WebsocketClient {
OkHttpClient client = null;
public WebsocketClient(){
client = new OkHttpClient.Builder()
.retryOnConnectionFailure(true)
//允許失敗重試
.readTimeout(5, TimeUnit.SECONDS)
//設置讀取超時時間
.writeTimeout(5, TimeUnit.SECONDS)
//設置寫的超時時間
.connectTimeout(5, TimeUnit.SECONDS)
//設置連接超時時間
.build();
}
public void send(){
Request request = new Request.Builder().url("ws://localhost:7777/chat").build();
WebSocket webSocket = client.newWebSocket(request, new WebSocketListener() {
@Override
public void onOpen(WebSocket webSocket, Response response) {
super.onOpen(webSocket, response);
//連接成功
}
@Override
public void onMessage(WebSocket webSocket, String text) {
super.onMessage(webSocket, text);
//接收服務器消息 text
System.out.println("服務器響應:" + text);
}
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
super.onMessage(webSocket, bytes);
//如果服務器傳遞的是byte類型的
String msg = bytes.utf8();
System.out.println("服務器響應:" + msg);
}
@Override
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
super.onFailure(webSocket, t, response);
//連接失敗調用 異常信息t.getMessage()
t.printStackTrace();
}
});
client.dispatcher().executorService().shutdown();//內存不足時釋放
boolean rs = webSocket.send("發送的消息");
if (rs){
System.out.println("發送完畢!");
}
}
public static void main(String[] args) throws Exception {
new WebsocketClient().send();
}
}