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);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
.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());
pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024));
pipeline.addLast(new MyHttpHandler());
}
}
public class MyHttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
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);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
}
运行服务端打开浏览器或者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
设计一个样式,左右两个各有一个文本框,中间放一个发送按钮
左侧文本框用来发送数据,右侧文本框用来显示数据
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello WebSockettitle>
head>
<body>
<script>
var socket;
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) {
if (!window.socket) {
return
}
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);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler())
.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));
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
pipeline.addLast(new WebSocketHandler());
}
}
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文件 在打开服务端 刷新