目录
目录
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();
}
}