Netty学习——基于Netty的WebSocket协议开发
程序员文章站
2022-04-23 11:52:45
...
基于Netty的WebSocket协议开发
WebSocket简介
Java的Socket是Java网络编程的基础,Java的Socket是在应用层的,这和C/C++的Socket不同,C/C++的Socket是可以获取TCP头数据的。Socket是一种全双工的通信方式,也就是说通信双方可以同时互发消息,也叫CS模式,而HTTP协议是一种无状态协议,是基于BS模式的,服务器不能主动给客户端发消息,只能由客户端发起请求,服务器给予响应。HTTP的这种BS模式在HTTP的Web1.0和Web2.0还是很好用的,但是现在用户交互越来越丰富,很多时候也需要服务器能够主动给客户端发送消息数据,以此来增加用户体验度,或者是实现实时网络通信的效果。然而由于HTTP的特性,这在以前是个很大的技术难题。有一些产品基于轮询和长连接做出了一些解决方案,比如说gmail的在线聊天。但由于长连接轮询都比较耗资源,不能像Socket那样灵活,因而基于HTTP的这些解决方案都没能得到较好的认可。
HTML5开始为为浏览器与服务器通信提供WebSocket解决方案并提供了相应的API,WebSocket与Socket非常相似,并且Web Socket和Socket的API风格也非常的相似。在使用WebSocket时,只需要像Socket那样服务器端和客户端进行一次握手操作,就可以生成一个服务器端-客户端数据传输的双向通道,客户端向服务器发送数据的同时服务器也可以向客户端发送数据,因此,WebSocket也是全双工的网络通信模式。WebSocket之所以比HTTP的长连接轮询更优秀,更重要的原因是它和Socket一样对代理、防火墙路由器透明,而且每次的数据传输没有携头部信息以为Cookie之类的额外数据,这样就可以尽量减少网络流量,提高网络通信的效率。
WebSocket建立连接时,首先客户端会向服务器端发送一个HTTP请求,这个请求包含了一些额外的头部信息,比如"Upgrade:WebSocket"表示这个请求要创建一个WebSocket连接。
用Netty实现WebSocket
Netty已经实现了WebSocket的细节,我们在使用时只需要创建一个WebSocket的握手对象即可,在接收消息时,要判断消息是普通Http消息还是WebSocket的消息,如果是普通的消息,则通过获取请求头中的Upgrade中的数据,如果是"websocket"的话,就表示这个Http消息是用来创建WebSocket连接的,这时候,就可以创建一个Websocket握手对象。如果消息对象是一个WebSocket消息,就取出其中的内容,进行相应的逻辑处理,最后再写回一个WebScoket消息即完成了一次客户端向服务器请求WebSocket网络通信过程,当然我们也可以主动给客户端发送消息,我们在handler
active之后就每秒钟来查询是否已经创建了WebSocket握手对象,如果已经创建了,就向客户端写一条消息,这就是服务器主动给客户端发送消息,实现网络双工通信,下面是实现这个例子的Java代码
package study.netty;
import java.util.TimerTask;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.util.Timer;
public class NettyWebSocketServer {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-codec",new HttpServerCodec())
.addLast("aggregator",new HttpObjectAggregator(65535))
.addLast("http-chunked",new ChunkedWriteHandler())
.addLast("handler",new WebSocketServerHandler());
}
});
try {
bootstrap.bind(8080).sync().channel().closeFuture().sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private static class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{
private WebSocketServerHandshaker handshaker;
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {//建立连接的请求
handleHttpRequest(ctx,(FullHttpRequest)msg);
}else if (msg instanceof WebSocketFrame){//WebSocket
handleWebsocketFrame(ctx,(WebSocketFrame)msg);
}
}
private void handleWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
if (frame instanceof CloseWebSocketFrame) {//关闭
handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
}else if (frame instanceof PingWebSocketFrame) {//ping消息
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
}else if (frame instanceof TextWebSocketFrame) {//文本消息
String request = ((TextWebSocketFrame)frame).text();
ctx.channel().write(new TextWebSocketFrame("websocket return:"+request));
}
}
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
if(request.getDecoderResult().isSuccess()&&"websocket".equals(request.headers().get("Upgrade"))) {
System.out.println("create WebSocket connection");
WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket", null, false);
handshaker = factory.newHandshaker(request);//通过创建请求生成一个握手对象
if(handshaker != null) {
handshaker.handshake(ctx.channel(),request);
}
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
new java.util.Timer().schedule(new TimerTask() {
@Override
public void run() {
if(handshaker !=null) {
ctx.channel().write(new TextWebSocketFrame("server:主动给客户端发消息"));
ctx.flush();
}
}
}, 1000,1000);
}
}
}
下面是客户端的代码,客户端是一个网页,上面的输入框用来输入消息发送给服务器,下面的文本域是用来显示历史消息的。
<html>
<head>
<title>test</title>
</head>
<body>
<form onsubmit="return false;">
<input type="text" name="message">
<button onclick="send(this.form.message.value)">send</button>
<br>
<h3>return messages</h3>
<textarea id="returns" rows="10" cols="30"></textarea>
</form>
</body>
<script type="text/javascript">
var socket
if(!window.WebSocket){
window.WebSocket = window.MozWebSocket
}
if(window.WebSocket){
socket = new WebSocket("ws://localhost:8080/websocket")
socket.onmessage = function(event){
var returns = document.getElementById("returns")
returns.value = returns.value + "\n"
returns.value = returns.value + event.data
}
socket.onopen = function(event){
var returns = document.getElementById("returns")
returns.value = returns.value + "\n"
returns.value = returns.value + "已经连接上WebSocket服务器"
}
socket.onclose = function(event){
var returns = document.getElementById("returns")
returns.value = returns.value + "\n"
returns.value = returns.value + "断开与Socket服务器的连接"
}
}
function send(msg){
if(!socket){
alert()
return
}
if(socket.readyState == WebSocket.OPEN){
socket.send(msg)
}else{
alert("WebSocket还未连接成功")
}
}
</script>
</html>
运行Java程序,然后打开网页,就会显示已经连接上WebSocket服务器,并且每过一秒就会自动接收到服务器发来的消息,如果输入数据的话,服务器会把数据返回并在前面加上一个"return:"
上一篇: 我的netty之旅(2)
下一篇: 豫妃:仅仅5年就封妃,她死后乾隆罢朝三日