HTTP(应用层协议)
默认是80端口,最早推出于1991年
请求/响应
客户端:
HttpResponseDecoder 解码器,处理服务端的响应
HttpRequestEncoder编码器,处理服务端请求
服务端:
HttpRequestDecoder 解码器,处理客户端的请求
HttpResponseEncoder编码器,处理客户端的响应
编解码器
客户端编码解码器HttpClientCodeC:HttpRequestEncoder+HttpResponseDecoder
服务端编码解码器HttpServerCodeC:HttpRequestDecoder+HttpResponseEncoder
压缩
HttpContentCompressor压缩,用于服务端
HttpContentDecompressor解压缩,用于客户端
聚合
由于http的请求和响应,可能由很多部分组成,需要聚合成一个完整的消息
HttpObjectAggregator ->
FullHttpRequest /
FullHttpResponse
发展历程
1) 0.9版本
GET /index.html
服务端只能返回html格式,传输过程只能处理文字
2) 1.0版本
支持任何格式的内容,包括图像、视频、二进制等等
引入了POST命令、HEAD命令
增加了请求头、状态码,以及权限、缓存等
GET / HTTP/1.0
User-Agent:Mozilla/1.0
Accept: */*
HTTP/1.0 200 OK
Content-Type: text/plain
Content-Encoding: gzip
<html>
<body> hello world </body>
</html>
a、 Content-Type
服务端通知客户端,当前数据的格式
示例: text/html 、 image/png 、 application/pdf 、 video/mp4
前面是一级类型,后面是二级类型,用斜杠分隔; 还可以增加其他参数,如编码格式
Content-Type: text/plain; charset=utf-8
b、Content-Encoding
表示数据压缩的方式,gzip、compress、deflate
对应客户端的字段为 Accept-Encoding,代表接收哪些压缩方式
c、缺点和问题
每个TCP连接只能发送一个请求,发送完毕连接关闭,使用成本很高,性能较差
Connection: keep-alive - 非标准字段
3) 1.1版本
GET / HTTP/1.1
User-Agent: PostmanRuntime/7.26.8
Accept: */*
Postman-Token: be754386-04ec-4a76-9817-bdd27ddd4b93
Host: cn.bing.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
HTTP/1.1 200 OK
Cache-Control: private, max-age=0
Content-Length: 45542
Content-Type: text/html; charset=utf-8
Content-Encoding: br
Vary: Accept-Encoding
P3P: CP="NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND"
Set-Cookie: SRCHD=AF=NOFORM; domain=.bing.com; expires=Thu, 15-Dec-2022 08:16:19 GMT; path=/
Set-Cookie: SRCHUID=V=2&GUID=55418C8D9DA04D47B721FCE030EB283B&dmnchg=1; domain=.bing.com; expires=Thu, 15-Dec-2022 08:16:19 GMT; path=/
Set-Cookie: SRCHUSR=DOB=20201215; domain=.bing.com; expires=Thu, 15-Dec-2022 08:16:19 GMT; path=/
Set-Cookie: _SS=SID=1DEB2B301FB66F20330024911EF56EF5; domain=.bing.com; path=/
Set-Cookie: _EDGE_S=F=1&SID=1DEB2B301FB66F20330024911EF56EF5; path=/; httponly; domain=bing.com
Set-Cookie: _EDGE_V=1; path=/; httponly; expires=Sun, 09-Jan-2022 08:16:19 GMT; domain=bing.com
Set-Cookie: MUID=03DC0275D7906F4E3BE10DD4D6D36EAA; samesite=none; path=/; secure; expires=Sun, 09-Jan-2022 08:16:19 GMT; domain=bing.com
Set-Cookie: MUIDB=03DC0275D7906F4E3BE10DD4D6D36EAA; path=/; httponly; expires=Sun, 09-Jan-2022 08:16:19 GMT
X-MSEdge-Ref: Ref A: 835BA58441E44FAAA96A2E12620C9519 Ref B: BJ1EDGE0608 Ref C: 2020-12-15T08:16:19Z
Date: Tue, 15 Dec 2020 08:16:18 GMT
a、持久连接,含义为默认不关闭tcp连接,可以被多个请求复用。大多时候,浏览器对同一个域名,允许同时建立6个连接
b、管道机制,支持客户端发送多个请求,管理请求的顺序的。服务器还是按照接受请求的顺序,返回对应的响应结果
c、Content-Length, 用来区分数据包的重要字段
d、支持PUT、DELETE、PATCH等命令
缺点和问题
当部分请求耗时较长时,仍会阻塞后续请求的处理速度,这种现象叫做“队头阻塞”/“线头阻塞”
4) 2.0版本
解决队头阻塞的问题,使用的是多路复用的方式
代码Demo
public class HttpServer {
public static void main(String[] args) {
// 可以自定义线程的数量
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 默认创建的线程数量=CPU处理器数量 * 2
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
// 当连接被阻塞时 BACKLOG代表的是 阻塞队列的长度
.option(ChannelOption.SO_BACKLOG, 128)
// 设置连接为保持活动的状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new MyHttpInitializer());
try {
ChannelFuture future = serverBootstrap.bind(9988).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class MyHttpInitializer extends ChannelInitializer<Channel> {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//先对请求解码,后对响应编码
pipeline.addLast("codec", new HttpServerCodec());
// 压缩数据
pipeline.addLast("compressor", new HttpContentCompressor());
// 聚合成完整的消息,参数代表可以处理的最大值(此时是512kb)
pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
pipeline.addLast(new MyHttpHandler());
}
}
/**
* 泛型需要设置为 FullHttpRequest
* 筛选msg为此类型的消息才处理
*/
public class MyHttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
/**
* DefaultFullHttpResponse是一个默认的完整http响应
* 设定版本号、响应码、响应数据
* 设置响应头,HttpHeaders来接收
* 设置请求或响应头字段时,可以使用HttpHeaderNames
* 设置字段值时,可以使用HttpHeaderValues
* 设置包的大小时,调用readableBytes方法
*/
DefaultFullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer("This is http netty demo".getBytes())
);
HttpHeaders headers = response.headers();
headers.add(HttpHeaderNames.CONTENT_TYPE, HttpHeaderValues.TEXT_PLAIN + ";charset=UTF-8");
headers.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
ctx.write(response);
}
/**
* 刷新
*
* @param ctx
* @throws Exception
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
// super.channelReadComplete(ctx);
}
}
运行服务端打开浏览器或者postman 输入ip+端口
WebSocket
websocket是由浏览器发起的
协议标识符 http://127.0.0.1:8080 ws://127.0.0.1:7777
GET ws://127.0.0.1:7777 HTTP/1.1
Host: 127.0.0.1
Upgrade: websocket # 升级为ws
Connection: Upgrade # 此链接需要升级
Sec-WebSocket-key: client-random-string ... # 标识加密相关信息
HTTP/1.1 101
Upgrade: websocket
Connection: Upgrade
响应码 101 代表本次协议需要更改为websocket,连接建立后,支持文本信息及二进制信息
Websocket实现的原理:
http的缺陷:通信只能由客户端发起。因此需要一种服务端能够主动推送的能力—websocket
通过http协议进行连接的建立(握手和回答),建立连接后不再使用http,而tcp自身是支持双向通信的,所以能达到“全双工”的效果
通信使用的单位叫帧 frame
客户端:发送时将消息切割成多个帧
服务端:接收时,将关联的帧重新组装
WebSocket客户端
var ws = new WebSocket("ws://127.0.0.1:7777/hello");
ws.onopen = function(ev){
ws.send("hello"); //建立连接后发送数据
}
代码Demo
设计一个样式,左右两个各有一个文本框,中间放一个发送按钮
左侧文本框用来发送数据,右侧文本框用来显示数据
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello WebSocket</title>
</head>
<body>
<script>
var socket;
// 判断当前浏览器是否支持websockt
if (!window.WebSocket) {
alert("不支持websocket")
} else {
socket = new WebSocket("ws://127.0.0.1:7777/hello");
//设置开启连接的方法
socket.onopen = function (ev) {
var tmp = document.getElementById("respText");
tmp.value = "连接已开启";
}
//设置关闭连接的方法
socket.onclose = function (ev) {
var tmp = document.getElementById("respText");
tmp.value = tmp.value + "\n" + "连接已关闭";
}
//设置接收数据的方法
socket.onmessage = function (ev) {
var tmp = document.getElementById("respText");
tmp.value = tmp.value + "\n" + ev.data;
}
}
function send(message) {
//先判断socket是否已经创建
if (!window.socket) {
return
}
//判断socket的状态
//CONNECTING正在连接 CLOSING正在关闭
//CLOSED已经关闭或打开连接失败
//OPEN连接成功可以正常通信
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert("连接未开启");
}
}
</script>
<!--防止表单自动提交-->
<form onsubmit="return false">
<textarea name="message" style="height: 400px;width: 400px"></textarea>
<input type="button" value="发送" onclick="send(this.form.message.value)">
<textarea id="respText" style="height: 400px;width: 400px"></textarea>
</form>
</body>
</html>
public class WebSocketServer {
public static void main(String[] args) {
//可以自定义线程的数量
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
//默认创建的线程数量=CPU处理器数量 * 2
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
//当连接被阻塞时BACKLOG代表的是阻塞队列的长度
.option(ChannelOption.SO_BACKLOG, 128)
//置连接为保持活动的状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new WebSocketInitializer());
try {
ChannelFuture future = serverBootstrap.bind(7777).sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//增加编解码器的另一种方式
pipeline.addLast(new HttpServerCodec());
//块方式写的处理器 用于处理较大数据
pipeline.addLast(new ChunkedWriteHandler());
//聚合
pipeline.addLast(new HttpObjectAggregator(512 * 1024));
//声明websocket的请求路径
//ws://127.0.0.1:7777/hello
//是将http协议升级为websocket协议,并且使用101作为响应码
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
pipeline.addLast(new WebSocketHandler());
}
}
/**
* 泛型
* 代表的是处理数据的单位
* TextWebSocketFrame是文本信息帧
*/
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("msg : " + msg.text());
Channel channel = ctx.channel();
TextWebSocketFrame resp = new TextWebSocketFrame("hello client from websocket server");
channel.writeAndFlush(resp);
}
}
run 上面那个HTML文件 在打开服务端 刷新