欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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:"