使用http协议的问题
场景:客户端的展示随着服务端维护的状态的改变而实时改变。
可能会采取的方式:
1 轮询:client按设置好的时间间隔主动访问server,有可能server返回有用数据,有可能server无有用数据返回,但都是一个建立连接-request-response-关闭连接的过程。
2 长轮询:client访问server,若server有数据返回,则返回,关闭连接,client继续发请求;若server没有数据返回,连接保持,等待直到server有数据返回,关闭连接,client继续发请求。建立连接-request-(wait)-response-关闭连接。
3 推:client和server之间维护长连接,当server有返回的时候主动推给client。
问题:
http协议是一种无状态的,基于请求响应模式的协议。
半双工协议:可以在客户端和服务端2个方向上传输,但是不能同时传输。同一时刻,只能在一个方向上传输。
响应数据不实时,空轮询对资源的浪费。 HTTP消息冗长(轮询中每次http请求携带了大量无用的头信息)。
HTTP1.0 每个请求会打开一个新连接,一般打开和关闭连接花费的时间远大于数据传输的时间,对于HTTPS更是。
HTTP1.1 服务器不会在发送响应后立即关闭连接,可以在同一个socket上等待客户端的新请求
Websocket 协议
WebSocket是一种规范,是Html5规范的一部分。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并被RFC7936所补充规范。WebSocket API也被W3C定为标准。
单个 TCP 连接上进行全双工通讯的协议。
浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。websocket是全双工,没有严格的clientserver概念。
opening handshake
request:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket //请求升级到WebSocket 协议
Connection: Upgrade //通道类型,keep-alive:通道长连,close:请求完毕后通道断开,Upgrade:升级协议
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== //客户端随机生成的Key,校验服务器合法性,生成方式:随机16字节再被base64编码
Sec-WebSocket-Protocol: chat, superchat //子协议,特定于具体应用的消息格式或编排
Sec-WebSocket-Version: 13
Origin: http://example.com
response:
HTTP/1.1 101 Switching Protocols //非101仍然是http
Upgrade: websocket //服务端协议已切换到WebSocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
//indicates whether the server is willing to accept the connection.
//用于校验WebSocket服务端是否合法,生成方式:客户端请求参数中的 Sec-WebSocket-Key+258EAFA5-E914-47DA-95CA-C5AB0DC85B11(GUID),
//SHA-1 hash 再进行base64
//GUID:which is unlikely to be used by network endpoints that do not understand the WebSocket protocol.
Sec-WebSocket-Protocol: chat
close handshake
fin:0,后续还有帧;1 本条消息的最后一帧
rsv1,rsv2,rsv3:不实用扩展协议,为0
Opcode:
websocket协议:https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17 https://tools.ietf.org/html/rfc6455#section-5
生命周期
4个生命周期事件:
打开事件 OnOpen;消息事件 OnMessage;错误事件 OnError;关闭事件 OnClose。
websocket简单实现
Java Websocket示例
//注解式
package echo;
import javax.websocket.OnMessage;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint(value = "/echo")
public class EchoServer {
@OnMessage
public String echo(String message){
return "I get this ("+message +") so I am sending it back";
}
}
//编程式
package echo;
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.MessageHandler;
import javax.websocket.Session;
import java.io.IOException;
/**
* 编程式websocket
*/
public class ProgrammaticEchoServer extends Endpoint {
@Override
public void onOpen(Session session, EndpointConfig endpointConfig) {
final Session mySession = session;
mySession.addMessageHandler(
new MessageHandler.Whole<String>() {
@Override
public void onMessage(String s) {
try {
mySession.getBasicRemote().sendText("I got this by prommmatic method ("+
s+") so I am sending it back ");
}catch (IOException io){
io.printStackTrace();
}
}
});
}
}
package echo;
import javax.websocket.Endpoint;
import javax.websocket.server.ServerApplicationConfig;
import javax.websocket.server.ServerEndpointConfig;
import java.util.HashSet;
import java.util.Set;
public class ProgrammaticEchoServerAppConfig implements ServerApplicationConfig {
@Override
public Set<ServerEndpointConfig> getEndpointConfigs(Set<Class<? extends Endpoint>> set) {
Set configs = new HashSet<ServerEndpointConfig>();
ServerEndpointConfig curSec = ServerEndpointConfig.Builder.create(ProgrammaticEchoServer.class,"/programmaticecho").build();
configs.add(curSec);
return configs;
}
/**
* 获取所有通过注解注册的endpoint
* @param set
* @return
*/
@Override
public Set<Class<?>> getAnnotatedEndpointClasses(Set<Class<?>> set) {
return null;
}
}
//客户端
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title> new document </title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Web Socket Echo CLient</title>
<script language="javascript" type="text/javascript">
var echo_websocket;
function init() {
output = document.getElementById("output");
}
function send_echo() {
var wsUri = "ws://localhost:8080/programmaticecho";//注解式为ws://localhost:8080/echo
writeToScreen("Connecting to "+wsUri);
echo_websocket = new WebSocket(wsUri);
echo_websocket.onopen = function (evt) {
writeToScreen("Connected!");
doSend(textID.value);
};
echo_websocket.onmessage = function (evt) {
writeToScreen("Received message: "+evt.data);
echo_websocket.close();
};
echo_websocket.onerror = function (evt) {
writeToScreen('<span style= "color"red;">ERROR:<span> '+evt.data);
echo_websocket.close();
};
}
function doSend(message) {
echo_websocket.send(message);
writeToScreen("Sent message: "+message);
}
function writeToScreen(message) {
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load",init,false);
</script>
</head>
<body>
<h1>Echo Server</h1>
<div style="text-align: left;">
<form action=""\>
<input onclick="send_echo()" value="Press to send" type = "button"/>
<input id="textID" name = "message" value="Hello Web Sockets" type = "text"/>
</form>
</div>
<br/>
<div id="output"></div>
</body>
</html>
netty对websocket的支持
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoop;
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.net.InetSocketAddress;
public class WebsocketNettyServer {
public static void main(String[] args) throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup wokerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>(){
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//websocket协议本身是基于http协议的,所以这边也要使用http解编码器
pipeline.addLast(new HttpServerCodec());
//以块的方式来写的处理器
pipeline.addLast(new ChunkedWriteHandler());
//netty是基于分段请求的,HttpObjectAggregator的作用是将请求分段再聚合,参数是聚合字节的最大长度
pipeline.addLast(new HttpObjectAggregator(8192));
//ws://server:port/context_path
//参数指的是contex_path
pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
//websocket定义了传递数据的6中frame类型
pipeline.addLast(new TextWebSocketFrameHandler());
}
});
ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync();
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
wokerGroup.shutdownGracefully();
}
}
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>{
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
System.out.println("收到消息"+textWebSocketFrame.text());
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
System.out.println("有新的连接加入");
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("连接关闭");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("啊哦,出错了");
ctx.fireExceptionCaught(cause);
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket客户端</title>
</head>
<body>
<script type="text/javascript">
var socket;
//如果浏览器支持WebSocket
if(window.WebSocket){
//参数就是与服务器连接的地址
socket = new WebSocket("ws://localhost:8899/ws");
//客户端收到服务器消息的时候就会执行这个回调方法
socket.onmessage = function (event) {
var ta = document.getElementById("responseText");
ta.value = ta.value + "\n"+event.data;
}
//连接建立的回调函数
socket.onopen = function(event){
var ta = document.getElementById("responseText");
ta.value = "连接开启";
}
//连接断掉的回调函数
socket.onclose = function (event) {
var ta = document.getElementById("responseText");
ta.value = ta.value +"\n"+"连接关闭";
}
}else{
alert("浏览器不支持WebSocket!");
}
//发送数据
function send(message){
if(!window.WebSocket){
return;
}
//当websocket状态打开
if(socket.readyState == WebSocket.OPEN){
socket.send(message);
}else{
alert("连接没有开启");
}
}
</script>
<form onsubmit="return false">
<textarea name = "message" style="width: 400px;height: 200px"></textarea>
<input type ="button" value="发送数据" onclick="send(this.form.message.value);">
<h3>服务器输出:</h3>
<textarea id ="responseText" style="width: 400px;height: 300px;"></textarea>
<input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空数据">
</form>
</body>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。