协议:计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则。
HTPP协议 :超文本传输协议,应用层协议,无状态; 默认端口:80
HTPPS :Http+加密+认证+完整性保护 默认端口:443
WebSocket :是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
协议状态:下一次传输可以“记住”这次传输信息的能力
http 工作过程:
- 对相应网址进行DNS域名解析,得到对应的IP地址
- 根据这个IP,找到对应的服务器,发起TCP的三次握手
- 建立TCP连接后发起HTTP请求
- 服务器响应HTTP请求,浏览器得到html代码
- 浏览器解析html代码,并请求html代码中的资源(如js、css图片等)(先得到html代码,才能去找这些资源)
- 浏览器对页面进行渲染呈现给用户
备注:
1.DNS域名解析采用的是递归查询的方式;
过程是:先去找DNS缓存->缓存找不到就去找根域名服务器->根域名又会去找下一级,这样递归查找之后,找到了,给我们的web浏览器
2.为什么HTTP协议要基于TCP来实现? TCP是一个端到端的可靠的面相连接的协议,HTTP基于传输层TCP协议不用担心数据传输的各种问题(当发生错误时,会重传)
3.最后一步浏览器是如何对页面进行渲染的?
a)解析html文件构成 DOM树,
b)解析CSS文件构成渲染树,
c)边解析,边渲染 ,
d)JS 单线程运行,JS有可能修改DOM结构,意味着JS执行完成前,后续所有资源的下载是没有必要的,所以JS是单线程,会阻塞后续资源下载
https 工作原理:
- 首先HTTP请求服务端生成证书,客户端对证书的有效期、合法性、域名是否与请求的域名一致、证书的公钥(RSA加密)等进行校验;
- 客户端如果校验通过后,就根据证书的公钥的有效, 生成随机数,随机数使用公钥进行加密(RSA加密);
- 消息体产生的后,对它的摘要进行MD5(或者SHA1)算法加密,此时就得到了RSA签名;
- 发送给服务端,此时只有服务端(RSA私钥)能解密
- 解密得到的随机数,再用AES加密,作为密钥(此时的密钥只有客户端和服务端知道)。
请求方法:
Get:请求指定的页面信息,并返回实体主体。
Hard:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
Post:向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
Put:客户端向服务器传送的数据取代指定的文档的内容。
Delete:请求服务器删除指定的页面
Connect:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。
Options:允许客户端查看服务器的性能。
Trace:回显服务器收到的请求,主要用于测试或诊断
报文格式:
状态行格式:
1xx:指示信息--表示请求已接收,继续处理。
2xx:成功--表示请求已被成功接收、理解、接受。
3xx:重定向--要完成请求必须进行更进一步的操作。
4xx:客户端错误--请求有语法错误或请求无法实现。
5xx:服务器端错误--服务器未能实现合法的请求。
http1.0 / 1.1 / 2.0的区别:
http 1.0
- 链接无法复用,即不支持持久链接;HTTP 1.0规定浏览器与服务器只保持短暂的连接;默认不支持
–需要在request中增加”Connection: keep-alive“- 线头阻塞
–请求队列的第一个请求因为服务器正忙(或请求格式问题等其他原因),导致后面的请求被阻塞。
http 1.1
- 默认支持长连接,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,一定程度上弥补了
–HTTP1.1增加了一个Connection字段,通过设置Keep-Alive可以保持HTTP连接不断开- 带宽优化
–请求头引入了range头信息,允许只请求资源的某个部分- 缓存处理
–HTTP1.1则引入了例如ETag,If-None-Match等更多可供选择的缓存头来控制缓存策略。- HOST头域
–HTTP1.1则引入了例如ETag,If-None-Match等更多可供选择的缓存头来控制缓存策略。- 断点传输
http 2.0
支持多路复用
–多路复用允许同时通过单一的 HTTP 2.0 连接发起多重的请求-响应消息,减少了因http链接多二引起的网络拥塞
–解决了慢启动针对突发性和短时性的http链接低效的问题。将通信的基本单位缩小为帧
–即应用层(HTTP)和传输层(TCP or UDP)之间增加一个二进制分帧层。首部压缩
–http 2.0支持DEFLATE和HPACK 算法的压缩。HTTP2.0使用encoder来压缩需要传输的header大小。服务端推送
–在 HTTP 2.0 中,服务器可以对客户端的一个请求发送多个响应。
WebSocket:
定义: HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
WebSocket为应用层协议,其定义在TCP/IP协议栈之上。WebSocket连接服务器的URI以“ws”或者“wss”开头。ws开头的默认TCP端口为80,wss开头的默认端口为443。
应用场景:
- 社交聊天
- 弹幕
- 多玩家游戏
- 协同编辑
- 股票基金实时报价
- 视频会议/聊天
- 基于位置的应用
- …
Spring boot + WebSocket 整合实例Demo
/**
* @author heyonghao
* @Title: WebSocketConfig
* @ProjectName xlkb2b
* @Description: config 类用来注册我们的 websocket bean
* @date 2019/3/12 001210:00
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
/**
* @author heyonghao
* @Title: ServerEndpoint
* @ProjectName xlkb2b
* @Description: 接受客户端发送过来的信息和发送给客户端信息。
* @date 2019/3/12 001210:55
*/
@Component
@ServerEndpoint("/notifyWebsocket/{username}")
public class NotifyWebsocket {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在线人数
*/
public static int onlineNumber = 0;
/**
* 以用户的姓名为key,WebSocket为对象保存起来
*/
private static Map<String, NotifyWebsocket> clients = new ConcurrentHashMap<String, NotifyWebsocket>();
/**
* 会话
*/
private Session session;
/**
* 用户名称
*/
private String username;
/**
* 建立连接
* @param username 用户名
* @param session webSocket sesson
*/
@OnOpen
public void onOpen(@PathParam("username") String username, Session session) {
//@PathParam url中直接在斜杠后面添加参数值
onlineNumber++;
logger.info("现在来连接的客户id:"+session.getId()+"用户名:"+username);
this.username = username;//初始化 用户名,为当前链接的用户
this.session = session;
logger.info("有新连接加入! 当前在线人数" + onlineNumber);
try {
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
//先给所有人发送通知,说我上线了
Map<String,Object> map1 = new HashMap<>();
map1.put("messageType",1);
map1.put("username",username);
sendMessageAll(JSON.toJSONString(map1),username);
//把自己的信息加入到map当中去
clients.put(username, this);
//给自己发一条消息:告诉自己现在都有谁在线
Map<String,Object> map2 = new HashMap<>();
map2.put("messageType",3);
//移除掉自己
Set<String> set = clients.keySet();
map2.put("onlineUsers",set);
sendMessageTo(JSON.toJSONString(map2),username);
}
catch (IOException e){
logger.info(username+"上线的时候通知所有人发生了错误");
}
}
@OnError
public void onError(Session session, Throwable error) {
logger.info("服务端发生了错误"+error.getMessage());
//error.printStackTrace();
}
/**
* 连接关闭
*/
@OnClose
public void onClose()
{
onlineNumber--;
//webSockets.remove(this);
clients.remove(username);
try {
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String,Object> map1 = new HashMap<>();
map1.put("messageType",2);
map1.put("onlineUsers",clients.keySet()); //.keySet() map包含的键的 set 视图
map1.put("username",username);
sendMessageAll(JSON.toJSONString(map1),username);
}
catch (IOException e){
logger.info(username+"下线的时候通知所有人发生了错误");
}
logger.info("有连接关闭! 当前在线人数" + onlineNumber);
}
/**
* 收到客户端的消息
*
* @param message 消息
* @param session 会话
*/
@OnMessage
public void onMessage(String message, Session session)
{
try {
logger.info("来自客户端消息:" + message+"客户端的id是:"+session.getId());
JSONObject jsonObject = JSON.parseObject(message);
String textMessage = jsonObject.getString("message");
String fromusername = jsonObject.getString("username");
String tousername = jsonObject.getString("to");
//如果不是发给所有,那么就发给某一个人
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String,Object> map1 = new HashMap<>();
map1.put("messageType",4);
map1.put("textMessage",textMessage);
map1.put("fromusername",fromusername);
if(tousername.equals("All")){
map1.put("tousername","所有人");
sendMessageAll(JSON.toJSONString(map1),fromusername);
}
else{
map1.put("tousername",tousername);
sendMessageTo(JSON.toJSONString(map1),tousername);
}
}
catch (Exception e){
logger.info("发生了错误了");
}
}
/**
* 发送消息 给单个人
* @param message
* @param ToUserName
* @throws IOException
*/
public void sendMessageTo(String message, String ToUserName) throws IOException {
for (NotifyWebsocket item : clients.values()) {
if (item.username.equals(ToUserName) ) {
item.session.getAsyncRemote().sendText(message);
break;
}
}
}
/**
* 消息发送给所有人
* @param message
* @param FromUserName
* @throws IOException
*/
public void sendMessageAll(String message,String FromUserName) throws IOException {
for (NotifyWebsocket item : clients.values()) {
item.session.getAsyncRemote().sendText(message);
}
}
public static synchronized int getOnlineCount() {
return onlineNumber;
}
前端代码:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>websocket</title>
<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.min.js"></script>
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
</head>
<body>
<div style="margin: auto;text-align: center">
<h1>Welcome to websocket</h1>
</div>
<br/>
<div style="margin: auto;text-align: center">
<select id="onLineUser">
<option>--所有--</option>
</select>
<input id="text" type="text" />
<button onclick="send()">发送消息</button>
</div>
<br>
<div style="margin-right: 10px;text-align: right">
<button onclick="closeWebSocket()">关闭连接</button>
</div>
<hr/>
<div id="message" style="text-align: center;"></div>
<input type="text" th:value="${username}" id="username" style="display: none" />
</body>
<script type="text/javascript">
var webSocket;
var username=$("#username").val();
if ("WebSocket" in window)
{
webSocket = new WebSocket("ws://192.168.1.22:8080/websocket/"+document.getElementById('username').value);
//连通之后的回调事件
webSocket.onopen = function()
{
webSocket.send( username+"已经上线了"); //将数据发送到服务器
console.log("已经连通了websocket");
setMessageInnerHTML(username+" --已上线");
};
//接收后台服务端的消息
webSocket.onmessage = function (evt)
{
var received_msg = evt.data;
console.log("数据已接收:" +received_msg);
var obj = JSON.parse(received_msg);
console.log("可以解析成json:"+obj.messageType);
//1代表上线 2代表下线 3代表在线名单 4代表普通消息
if(obj.messageType==1){
//把名称放入到selection当中供选择
var onlineName = obj.username;
var option = "<option>"+onlineName+"</option>";
$("#onLineUser").append(option);
setMessageInnerHTML(onlineName+"上线了");
}
else if(obj.messageType==2){
$("#onLineUser").empty();
var onlineName = obj.onlineUsers;
var offlineName = obj.username;
var option = "<option>"+"--所有--"+"</option>";
for(var i=0;i<onlineName.length;i++){
if(!(onlineName[i]==document.getElementById('username').value)){
option+="<option>"+onlineName[i]+"</option>"
}
}
$("#onLineUser").append(option);
setMessageInnerHTML(offlineName+"下线了");
}
else if(obj.messageType==3){
var onlineName = obj.onlineUsers;
var option = null;
for(var i=0;i<onlineName.length;i++){
if(!(onlineName[i]==document.getElementById('username').value)){
option+="<option>"+onlineName[i]+"</option>"
}
}
$("#onLineUser").append(option);
console.log("获取了在线的名单"+onlineName.toString());
}
else{
setMessageInnerHTML(obj.fromusername+"对"+obj.tousername+"说:"+obj.textMessage);
}
};
//连接关闭的回调事件
webSocket.onclose = function()
{
console.log("连接已关闭...");
setMessageInnerHTML("连接已经关闭....");
};
}
else{
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
function closeWebSocket() {
//直接关闭websocket的连接
webSocket.close();
}
function send() {
var selectText = $("#onLineUser").find("option:selected").text();
if(selectText=="--所有--"){
selectText = "All";
}
else{
setMessageInnerHTML(document.getElementById('username').value+"对"+selectText+"说:"+ $("#text").val());
}
var message = {
"message":document.getElementById('text').value,
"username":document.getElementById('username').value,
"to":selectText
};
webSocket.send(JSON.stringify(message));
$("#text").val("");
}
</script>
</html>