一、 什么是Websocket?
1、websocket技术应用场景
使用java技术快速学习一个简单的在线聊天室系统,该系统具备很强的扩展性,可以根据业务需要,制作在线客服系统、web版的微信、QQ即时通信系统等,使用较为流行的技术,采用积木式的编程思路。
web领域的实时推送技术,也被称为Realtime技术,这种技术要达到的目的是绕过用户不需要刷新浏览器就可以获得实时更新。他有着广泛的应用前景,比如在线聊天室,在线客服系统、评论系统、WebIM等。
2、websocket协议概述
webSocket protocol是HTML5的一种新协议。他实现了浏览器与服务器全双工通信(full-duplex)一开始的握手需要借助HTTP请求完成。
websocket是真正实现了全双工通信的服务器向客户端推的互联网技术。
他是一种在单个TCP连接上进行全双工通讯的协议。webSocket通信协议与2011年被IETF定义为标准RFC 6455,Websocket API 被 W3c定为标准。
3、全双工和单工的区别
全双工: 是通讯传输的一个术语。通讯允许数据在两个方向上同时传输,它在能力相当于两单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A->B , B-A) 指A->B的同时 B->A是瞬时同步的。
单工、半双工,所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆马车通过,当目前有两辆马车时,这种情况就需要一辆等待另一辆通过后再通过,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台
4、推的技术和拉的技术(了解)
推送(PUSH)技术是一种建立在客户服务器上的机制,就是由服务器主动将信息发往客户端的技术,就像是广播电台播音。
同传统的拉Pull技术相比,最主要的区别在于推送Push技术是由服务器主动向客户机发送信息,而拉PULL技术则是由客户机主动请求信息。PUSH技术的优势在于信息的主动性和及时性。
简单来说,相对于服务端:拉的技术是被动向客户机端提供数据,推的技术是主动向客户端提供数据
5、互联网技术
互联网技术的定义:互联网技术指在计算机技术的基础上开发建立的一种信息技术(直译:Internet Technology);简称:IT.
该技术把互联网上分散的资源融为有机整体,实现资源的全面共享和有机协作,使人们能够透明的使用资源的整体能力并按需获取信息。
6、Wenbsocket的优越性
以前不管使用HTTP轮询或是使用TCP长连接等方式制作在线聊天系统,都有天然缺陷,随着Html5的发展,其中有一个新的协议Websocket protocol,可实现浏览器与服务器全双工通信,它可以做到:浏览器和服务器只需要一次握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间可以数据互相传输。这个新的协议的特点正好适合这种在线即时通信。
传统的Http协议的实现方式:
http协议可以多次请求,因为每次请求之后,都会关闭连接,下次重新请求数据,需要再次打开连接
传统的Socket技术:
长连接
客户端 —先连接上去 ----- 服务端
好处: 可以实现客户端与服务端的双向通信
缺点: 如果大家都不说话,就造成了资源浪费
其他特点包括:
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
ws://example.com:80/some/path
二、实例要求及实现:
- Http 协议是无状态的, 浏览器和服务器间的请求响应一次,下一次会重新创建连接.
- 要求:实现基于 webSocket 的长连接的全双工的交互
- 改变 Http 协议多次请求的约束,实现长连接了, 服务器可以发送消息给浏览器
- 客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知
- 运行界面
2.1 MyServer 代码
package com.netty.webSocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import java.nio.channels.ServerSocketChannel;
public class MyServer {
public static void main(String[] args) throws Exception {
//创建两个线程池组
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workGroup = new NioEventLoopGroup();
//创建netty启动类
ServerBootstrap serverBootstrap=new ServerBootstrap();
try {
serverBootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//因为基于http,使用http的编码和解码器
pipeline.addLast(new HttpServerCodec());
//是以块方式写的,添加chunkedWrite处理器
pipeline.addLast(new ChunkedWriteHandler());
/*
* 说明:
* 1、因为http数据在传输过程中是分段,httpObjectAggregator,就是可以将多个段聚合
* 2、这就是为什么,当浏览器发送大量数据时,就会发出多次http请求
* */
pipeline.addLast(new HttpObjectAggregator(8192));
/*
* 说明:
* 1、对应webSocket,他的数据是以帧(frame)形式传递
* 2、可以看到websocketFrame 下面有六个子类
* 3、浏览器请求时 ws://localhost:7000/hello 表示请求的url
* 4、WebSocketServerProtocolHandler 核心功能是将一个http协议升级为ws协议,即websocket协议,保持长连接
*
* */
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
//自定义的handler,处理业务逻辑
pipeline.addLast(new MyTestWebSocketFrameHandler());
}
});
//启动服务器
ChannelFuture channelFuture = serverBootstrap.bind(7000).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
2.2 代码
package com.netty.webSocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.time.LocalDateTime;
//这里TextWebSocketFrame 类型,表示一个文本帧(frame)
public class MyTestWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("服务器收到的消息:" + msg.text());
//回复消息
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器时间"+ LocalDateTime.now()+msg.text()));
}
//当web客户端连接后,就会触发
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//id表示唯一的值, LongText 是唯一的 ShortTest 不是唯一
System.out.println("handlerAdded 被调用" + ctx.channel().id().asLongText());
System.out.println("handlerAdded 被调用" + ctx.channel().id().asShortText());
ctx.channel().writeAndFlush(new TextWebSocketFrame("连接成功..."));
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved 被调用" + ctx.channel().id().asLongText());
ctx.channel().writeAndFlush("连接关闭.....");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常发生" + cause.getMessage());
ctx.close();//关闭连接
}
}
2.3 hello.tml 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
var socket;
//判断当前浏览器是否支持websocket
if(window.WebSocket){
//GO ON
socket = new WebSocket("ws://localhost:7000/hello");
//相当于channelRead0,ev 收到服务器回送的消息
//当有消息时会被触发
socket.onmessage=function (ev){
var rt=document.getElementById("responseText");
rt.value=rt.value+"\n"+ev.data;
//当连接开启时会被触发
}
//连接关闭时会被触发
socket.onclose=function (ev){
var rt=document.getElementById("responseText");
rt.value=rt.value+"\n"+"连接关闭了...";
}
socket.onopen=function (ev){
var rt=document.getElementById("responseText");
rt.value="连接开启了....";
}
}else {
alert("当前浏览器不支持Websocket");
}
//向服务端发送消息
function send(message){
if(!window.socket){//先判断socket是否创建好
return ;
}
if(socket.readyState==WebSocket.OPEN){
//通过socket 发送消息
socket.send(message);
}else{
alert("当前浏览器不支持websocket!");
}
}
</script>
<form onsubmit="return false">
<textarea name="message" style="height: 300px;width: 300px"></textarea>
<input type="button" value="发送消息" onclick="send(this.form.message.value)"/>
<textarea id="responseText" style="height:300px;width:300px"></textarea>
<input type="button" value="清空内容" onclick="document.getElementById(responseText)">
</form>
</body>
</html>