一种被称作“Upgrade handshake(升级握手)”的机制能够将标准的HTTP或者HTTPS协议转成WebSocket。所以,应用程序如果使用了WebSocket,那么它都是以HTTP/S开始,之后再进行升级,升级会发生在什么时候是不确定的,要根据具体的应用来决定:可能是在应用启动的时候,也可能是当一个特定的URL被请求的时候。
在 WebSocket介绍 中讲解了WebSocket的相关事宜,接下来我们做一个基于WebSocket的客户端(也指Browser)与服务器间的请求响应DEMO.
在我们的应用中,要想升级协议为 \WebSocket,只有当URL请求以"/ws"结束时才可以,如果没有达到该要求,服务器仍将使用基本的 HTTP/S,一旦连接升级,之后的数据传输都将使用 WebSocket 。
public class MyServer {
private static final int port = 8989;
public static void main(String[] args) throws InterruptedException {
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.group(bossGroup, workerGroup);
sb.handler(new LoggingHandler(LogLevel.INFO));
sb.channel(NioServerSocketChannel.class);
sb.childHandler(new WebSocketChannelInitializer());
//端口绑定
ChannelFuture channelFuture = sb.bind(new InetSocketAddress(port)).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//因为这是基于http协议的,所以使用http编解码器。前面已经讲过了
pipeline.addLast(new HttpServerCodec());
//以块的方式去写
pipeline.addLast(new ChunkedWriteHandler());
/*特别重要,http数据在传输过程是分段的,
*HttpObjectAggregator, 而他就是将多个段聚合起来*/
pipeline.addLast(new HttpObjectAggregator(8989));
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
pipeline.addLast(new WebSocketFrameHandler());
}
}
1.扩展 ChannelInitializer
2.添加 ChannelHandler 到 ChannelPipeline
initChannel()方法用于设置所有新注册的Channel到ChannelPipeline中。总结如下:
表格 | Value |
---|
WebSocketServerProtocolHandler用于处理我们没有使用的Frame,在(2)中会详细展开
TextWebSocketFrameHandler用来处理WebSocket的frame(帧),用来发送聊天消息。
//这里的泛型是TextWebSocketFrame,表示一个文本帧(Frame)
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext,
TextWebSocketFrame textWebSocketFrame) throws Exception {
System.out.println("收到客户端消息:" + textWebSocketFrame.text());
channelHandlerContext.writeAndFlush(new TextWebSocketFrame("服务器时间" + LocalDateTime.now())); //根据绑定特定协议的处理器来定义参数类型
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//id表示唯一,有长有短,长的asLongText,唯一。短的asShortText()不唯一
System.out.println("handlerAdded的ID:"+ctx.channel().id().asShortText());
}
/*一个很有趣的现象,如果客户端刷新一下,实际上会调用这个方法,因为连接断了,新建了一个连接*/
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerRemoved的ID:"+ctx.channel().id().asLongText());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常" + ctx.channel().id().asLongText());
ctx.close();
}
}
WebSocket"Request for Comments"(RFC) 定义了六种不同的 frame; Netty都提供了一个POJO实现 ,见下表:
名称 | 描述 |
---|---|
BinaryWebSocketFrame | contains binary data |
TextWebSocketFrame | contains text data |
ContinuationWebSocketFrame | contains text or binary data that belongs to a previous BinaryWebSocketFrame or TextWebSocketFrame |
CloseWebSocketFrame | represents a CLOSE request and contains close status code and a phrase |
PingWebSocketFrame | requests the transmission of a PongWebSocketFrame |
PongWebSocketFrame | sent as a response to a PingWebSocketFrame |
我们的程序只需要使用下面4个帧类型:
CloseWebSocketFrame、PingWebSocketFrame、PongWebSocketFrame、TextWebSocketFrame
在上面我们只使用了TextWebSocketFrame,是因为其他Frame会由 WebSocketServerProtocolHandler自动处理。TA处理所有规定的WebSocket帧类型和升级握手本身。
对于webSocket,数据传输是以frame(帧)的形式传递,"/ws"表示的websocket的地址。
<html lang="en">
<head> <meta charset="UTF-8"> <title>Titletitle> head>
<body>
<script>
var socket;
if(window.WebSocket){
socket=new WebSocket("ws://localhost:8989/ws");
//相当于channelRead0,ev收到服务器端发来的消息
socket.onmessage=function (ev) {
var rt=document.getElementById("responseText");
rt.value=rt.value+"\n"+ev.data;
}
//相当于连接开启
socket.onopen=function (ev) {
var rt=document.getElementById("responseText");
rt.value="连接开启";
}
//相当于连接关闭
socket.onclose=function (ev) {
var rt=document.getElementById("responseText");
rt.value=rt.value+"\n"+"连接关闭";
}
}
else { alert("您的浏览器不适合WebSocket"); }
function send(message) {
if (!window.socket){ return; }
if (socket.readyState == WebSocket.OPEN){ socket.send(message); }
else { alert("连接未开启"); }
}
script>
<form onsubmit="return false">
<textarea name="message" style="height: 200px;width: 400px">textarea>
<input type="button" value="发送数据" onclick="send(this.form.message.value)">
<textarea id="responseText" style="width: 400px;height: 200px">textarea>
<input type="button" onclick="document.getElementById('responseText').value=''" value="清空内容">
form>
body>
html>
注:在channelRead0()中的writeAndFlush()方法里面传入的是object类型,但是这里要传一个TextWebSocketFrame对象,表示要根据传入xxxContext的实际类型定义要返回内容,否则传送不了.
输入一段字符串数据,点击发送数据,服务器控制台上打印了连接服务器的ChannelID、客户端发送的数据,如下图:
此时如果关闭了浏览器(注:断网、断电不会检测到),服务器的handlerRemoved()方法自动别调用 —> 输出了以下内容:
`handlerRemoved的ID:d4258bfffe5eb145-00001de8-00000001-0d93c897363c01c4-88ecd330
基于WebSocket的HTTP请求响应图如下:
1. Client连接到服务器并加入聊天
2. HTTP请求页面或WebSocket升级握手
3. 服务器处理所有客户端/用户
4.1 如果服务器响应URI"/"的请求,直接返回void.
4.2 如果访问的是URI"/ws",则处理WebSocket升级握手
5. 升级握手完成后 ,通过 WebSocket 发送聊天消息
字段 | Value |
---|---|
101状态码 | 表示由HTTP/S协议转成ws协议(协议升级) |
Connection | 必须设置 Upgrade,表示客户端希望连接升级。 |
Upgrade | 必须设置 Websocket,表示希望升级到 Websocket 协议。 |
Sec-WebSocket-Key | 是随机的字符串,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要。把 “Sec-WebSocket-Key” 加上一个特殊字符串 “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算 SHA-1 摘要,之后进行 BASE-64 编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端。如此操作,可以尽量避免普通 HTTP 请求被误认为 Websocket 协议。 |
Sec-WebSocket-Version | 表示支持的 Websocket 版本。RFC6455 要求使用的版本是 13,之前草案的版本均应当弃用。 |
Origin | 可选,通常用来表示在浏览器中发起此 Websocket 连接所在的页面,类似于 Referer。但是,与 Referer 不同的是,Origin 只包含了协议和主机名称。 |
其他一些定义在 HTTP 协议中的字段,如 Cookie 等,也可以在 Websocket 中使用。
软件通信有七层结构:
1. 下3层结构偏向与数据通信
2. 上3层更偏向于数据处理
3. 中间的传输层则是连接上三层与下三层之间的桥梁。每一层都做不同的工作,上层协议依赖与下层协议。
一、Socket并不是一个协议,Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。当两台主机通信时,让Socket去组织数据,以符合指定的协议。
二、WebSocket则是一个典型的应用层协议。
三、总的来说:Socket 是传输控制层协议的抽象,WebSocket是应用层协议。
附:TCP连接则更依靠于底层的IP协议,IP协议的连接则依赖于链路层等更低层次。