当前位置:网站首页>Use websocket to realize a web version of chat room (fishing is more hidden)
Use websocket to realize a web version of chat room (fishing is more hidden)
2022-07-27 03:46:00 【Java notes shrimp】
Click on the official account , utilize Fragment time to learn
WebSocket brief introduction
WebSocket The protocol is a completely redesigned protocol , For the purpose of Web It provides a practical solution to the problem of two-way data transmission on the Internet , It enables the transmission of messages between the client and the server at any time , therefore , This requires them to process message receipts asynchronously
WebSocket characteristic :
HTML5The agreement in , Achieve two-way with client and server , Message based text or binary data communicationIt is suitable for the scene with strong real-time data requirements , Like correspondence 、 live broadcast 、 Share desktop , It is especially suitable for frequent interaction between client and server , Such as real-time sharing 、 Multi person cooperation and other platforms
Adopt a new protocol , The back end needs to be implemented separately
Not all browsers support
WebSocket Communication handshake
From the standard of HTTP perhaps HTTPS The protocol switches to WebSocket when , A mechanism called handshake will be used , therefore , Use WebSocket The application will always use HTTP/S For a start , Then perform the upgrade . The exact moment when this upgrade occurs is specific to the application ; It could happen at boot time , It can also happen when a specific URL after
Here is WebSocket Identification information for requests and responses :

Client requests :
ConnectionAttributeUpgrade, Indicates that the client wants to upgrade the connectionUpgradeAttribute is marked withWebsocket, I want to upgrade toWebsocketagreementSec-WebSocket-Keyattribute , Represents a random string , The server will use this data to construct a SHA-1 A summary of the information . hold “Sec-WebSocket-Key” Add a special string “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”, And then calculate SHA-1 Abstract , after BASE-64 code , Make the result “Sec-WebSocket-Accept” Head value , Return to the client . Do this , We can try to avoid ordinary HTTP The request was mistaken for Websocket agreement .Sec-WebSocket-Versionattribute , Expressing support forWebsocketedition ,RFC6455The required version is 13, All previous versions of the draft should be discarded
Server response :
UpgradeAttribute is marked withwebsocketConnectionTell the client that it is about to upgradeWebsocketagreementSec-WebSocket-AcceptThis is confirmed by the server , And encryptedSec-WebSocket-Key
Netty by WebSocket Data frames provide support
from IETF released WebSocket RFC, Defined 6 Seed frame ,Netty One for each of them POJO Realization

actual combat
First , Definition WebSocket Server side , One of them is the creation of Netty Provide ChannelGroup All variables that have been used by the client to log channel, And this ChannelGroup It is used to complete the function of group sending and single chat
// Definition websocket Server side
public class WebSocketServer {
private static EventLoopGroup bossGroup = new NioEventLoopGroup(1);
private static EventLoopGroup workerGroup = new NioEventLoopGroup();
private static ServerBootstrap bootstrap = new ServerBootstrap();
private static final int PORT =8761;
// establish DefaultChannelGroup, Used to save all connected WebSocket Channel, Group hair and one-on-one functions can be used
private final static ChannelGroup channelGroup =
new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
public static void startServer(){
try {
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new WebSocketServerInitializer(channelGroup));
Channel ch = bootstrap.bind(PORT).sync().channel();
System.out.println(" Open browser access : http://127.0.0.1:" + PORT + '/');
ch.closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}finally{
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
startServer();
}
} Next , initialization Pipeline, To current Pipeline Register all required ChannelHandler, It mainly includes : Used for processing HTTP Requesting encoding and decoding HttpServerCodec、 Custom processing HTTP Requested HttpRequestHandler、 Used for processing WebSocket Frame data and upgrade handshake WebSocketServerProtocolHandler And custom processing TextWebSocketFrame Data frame and handshake complete the event WebSocketServerHanlder
public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel>{
/*websocket Access path */
private static final String WEBSOCKET_PATH = "/ws";
private ChannelGroup channelGroup;
public WebSocketServerInitializer(ChannelGroup channelGroup){
this.channelGroup=channelGroup;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// be used for HTTP Encoding and decoding of requests
ch.pipeline().addLast(new HttpServerCodec());
// Used to write the contents of a file
ch.pipeline().addLast(new ChunkedWriteHandler());
// be used for http Aggregation of requests
ch.pipeline().addLast(new HttpObjectAggregator(64*1024));
// be used for WebSocket Reply data compression transmission
ch.pipeline().addLast(new WebSocketServerCompressionHandler());
// Handle http request , Right websocket Processing of requests
ch.pipeline().addLast(new HttpRequestHandler(WEBSOCKET_PATH));
// according to websocket standard , Handle upgrade handshake and all kinds of websocket Data frame
ch.pipeline().addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, "", true));
// Yes websocket Data processing , Main treatment TextWebSocketFrame Data frames and handshake completion Events
ch.pipeline().addLast(new WebSocketServerHanlder(channelGroup));
}
}HttpRequestHandler Used for processing HTTP request , The first step is to confirm the current HTTP Whether the request points to WebSocket Of URI, If so HttpRequestHandler Will call FullHttpRequest On the object retain Method , And by calling fireChannelRead(msg) Method to forward it to the next ChannelInboundHandler( The reason for calling retain Method , Because it calls channelRead0 After method completion , Will release resources )
Next , Read the specified path on the disk index.html The contents of the document , Encapsulate the content as ByteBuf object , after , Construct a FullHttpResponse The response object , take ByteBuf Add in , And set the request header information . Last , call writeAndFlush Method to flush all written messages
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
private static final File INDEX = new File("D:/ Study /index.html");
private String websocketUrl;
public HttpRequestHandler(String websocketUrl)
{
this.websocketUrl = websocketUrl;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
if(websocketUrl.equalsIgnoreCase(msg.getUri())){
// If it's time to HTTP The request points to websocketUrl Of URL, So go straight to the next one ChannelInboundHandler To deal with
ctx.fireChannelRead(msg.retain());
}else{
// Generate index The specific content of the page , And send it to the browser
ByteBuf content = loadIndexHtml();
FullHttpResponse res = new DefaultFullHttpResponse(
HTTP_1_1, OK, content);
res.headers().set(HttpHeaderNames.CONTENT_TYPE,
"text/html; charset=UTF-8");
HttpUtil.setContentLength(res, content.readableBytes());
sendHttpResponse(ctx, msg, res);
}
}
public static ByteBuf loadIndexHtml(){
FileInputStream fis = null;
InputStreamReader isr = null;
BufferedReader raf = null;
StringBuffer content = new StringBuffer();
try {
fis = new FileInputStream(INDEX);
isr = new InputStreamReader(fis);
raf = new BufferedReader(isr);
String s = null;
// Read file contents , And print it
while((s = raf.readLine()) != null) {
content.append(s);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
try {
fis.close();
isr.close();
raf.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
return Unpooled.copiedBuffer(content.toString().getBytes());
}
/* Send reply */
private static void sendHttpResponse(ChannelHandlerContext ctx,
FullHttpRequest req,
FullHttpResponse res) {
// The wrong request is processed (code<>200).
if (res.status().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(),
CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
HttpUtil.setContentLength(res, res.content().readableBytes());
}
// Send reply .
ChannelFuture f = ctx.channel().writeAndFlush(res);
// For requests that are not long or wrong, close the connection directly
if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
} Ahead HttpRequestHandler Processors are just used to manage HTTP Request and respond to , And the actual transmission of WebSocket Data frames are processed by WebSocketServerHanlder Conduct ( Only one pair TextWebSocketFrame Type of data frame for processing ).
WebSocketServerHanlder Processing by rewriting userEventTriggered Method , And listen for successful handshake Events , When the new client WebSocket After a successful handshake , It will write notification messages to ChannelGroup All in channel To notify all connected clients , And then it puts this new channel Add to the ChannelGroup in , And for every one of them channel Randomly generated a user
after , If you receive TextWebSocketFrame When the news , According to the current situation channel Get the user , And parse the sent text frame information , Confirm whether it is a group chat or a single chat , Last , structure TextWebSocketFrame Response content , adopt writeAndFlush Scour
/**
* Yes websocket Text data frame for processing
*
*/
public class WebSocketServerHanlder extends SimpleChannelInboundHandler<TextWebSocketFrame>{
private ChannelGroup channelGroup;
public WebSocketServerHanlder(ChannelGroup channelGroup){
this.channelGroup=channelGroup;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
// Get current channel user name
String userName=UserMap.getUser(ctx.channel().id().asLongText());
// Text frame
String content= msg.text();
System.out.println("Client: "+ userName+" received [ "+content+" ]");
String toName = null;
// Judge whether it's a single chat or a group ( A single chat will pass [email protected] msg This format is used to transmit text frames )
if(content.contains("@")){
String[] str= content.split("@");
content=str[1];
// Get single chat users
toName = str[0];
}
if(null!=toName){
Iterator<Channel> it=channelGroup.iterator();
while(it.hasNext()){
Channel channel=it.next();
// Find the specified user
if(UserMap.getUser(channel.id().asLongText()).equals(toName)){
// Single chat
channel.writeAndFlush(new TextWebSocketFrame(userName+"@"+content));
}
}
}else{
channelGroup.remove(ctx.channel());
// Mass sending realizes
channelGroup.writeAndFlush(new TextWebSocketFrame(userName+"@"+content));
channelGroup.add(ctx.channel());
}
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
// Detect events , If it's a handshake success , Do some business processing
if(evt==WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE){
String channelId = ctx.channel().id().asLongText();
// Random for the current channel Specify a user name
UserMap.setUser(channelId);
System.out.println(" New client connections :"+UserMap.getUser(channelId));
// Notify all connected WebSocket Client the new client is connected
channelGroup.writeAndFlush(new TextWebSocketFrame(UserMap.getUser(channelId)+" Join the group chat "));
// New WebSocket Channel Add to ChannelGroup in
channelGroup.add(ctx.channel());
}else{
super.userEventTriggered(ctx, evt);
}
}
}index.html Content
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title> be based on WebSocket Realize the group chat of web version </title>
</head>
<body>
<script type="text/javascript">
var userName= null;
var socket;
var myDate = new Date();
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket = new WebSocket("ws://127.0.0.1:8761/ws");
socket.onmessage = function(event) {
var info = document.getElementById("jp-container");
var dataObj=event.data;
if(dataObj.indexOf("@")!=-1){
var arr = dataObj.split('@');
var sendUser;
var acceptMsg;
for(var i=0;i<arr.length;i++){
if(i==0){
sendUser = arr[i];
}else{
acceptMsg =arr[i];
}
}
if(userName==sendUser){
return;
}
var talk= document.createElement("div");
talk.setAttribute("class", "talk_recordboxme");
talk.innerHTML = sendUser+':';
var recordtext= document.createElement("div");
recordtext.setAttribute("class", "talk_recordtextbg");
talk.appendChild(recordtext);
var talk_recordtext=document.createElement("div");
talk_recordtext.setAttribute("class", " talk_recordtext");
var h3=document.createElement("h3");
h3.innerHTML =acceptMsg;
talk_recordtext.appendChild(h3);
var span=document.createElement("span");
span.innerHTML =myDate.toLocaleTimeString();
span.setAttribute("class", "talk_time");
talk_recordtext.appendChild(span);
talk.appendChild(talk_recordtext);
}else{
var talk= document.createElement("div");
talk.style.textAlign="center";
var font = document.createElement("font");
font.color='#212121';
font.innerHTML = dataObj+': '+myDate.toLocaleString( );
talk.appendChild(font);
}
info.appendChild(talk);
};
socket.onopen = function(event) {
console.log("Socket Already opened ");
};
socket.onclose = function(event) {
console.log("Socket closed ");
};
} else {
alert("Your browser does not support Web Socket.");
}
function send(message) {
if (!window.WebSocket) { return; }
if (socket.readyState == WebSocket.OPEN) {
var info = document.getElementById("jp-container");
var talk= document.createElement("div");
talk.setAttribute("class", "talk_recordbox");
var user = document.createElement("div");
user.setAttribute("class", "user");
talk.appendChild(user);
var recordtext= document.createElement("div");
recordtext.setAttribute("class", "talk_recordtextbg");
talk.appendChild(recordtext);
var talk_recordtext=document.createElement("div");
talk_recordtext.setAttribute("class", " talk_recordtext");
var h3=document.createElement("h3");
h3.innerHTML =message;
talk_recordtext.appendChild(h3);
var span=document.createElement("span");
span.innerHTML =myDate.toLocaleTimeString();
span.setAttribute("class", "talk_time");
talk_recordtext.appendChild(span);
talk.appendChild(talk_recordtext);
info.appendChild(talk );
socket.send(message);
} else {
alert("The socket is not open.");
}
}
</script>
<br>
<br>
<div class="talk">
<div class="talk_title"><span> Group chat </span></div>
<div class="talk_record" style="background: #EEEEF4;">
<div id="jp-container" class="jp-container">
</div>
</div>
<form onsubmit="return false;">
<div class="talk_word">
<input class="add_face" id="facial" type="button" title=" Add expression " value="" />
<input class="messages emotion" autocomplete="off" name="message" value=" Enter the text here " onFocus="if(this.value==' Enter the text here '){this.value='';}" onblur="if(this.value==''){this.value=' Enter the text here ';}" />
<input class="talk_send" type="button" title=" send out " value=" send out " onclick="send(this.form.message.value)" />
</div>
</form>
</div>style
body{
font-family:verdana, Arial, Helvetica, " Song style ", sans-serif;
font-size: 12px;
}
body ,div ,dl ,dt ,dd ,ol ,li ,h1 ,h2 ,h3 ,h4 ,h5 ,h6 ,pre ,form ,fieldset ,input ,P ,blockquote ,th ,td ,img,
INS {
margin: 0px;
padding: 0px;
border:0;
}
ol{
list-style-type: none;
}
img,input{
border:none;
}
a{
color:#198DD0;
text-decoration:none;
}
a:hover{
color:#ba2636;
text-decoration:underline;
}
a{blr:expression(this.onFocus=this.blur())}/* Get rid of a The dotted box of the label , Selected areas to avoid */
:focus{outline:0;}
.talk{
height: 480px;
width: 335px;
margin:0 auto;
border-left-width: 1px;
border-left-style: solid;
border-left-color: #444;
}
.talk_title{
width: 100%;
height:40px;
line-height:40px;
text-indent: 12px;
font-size: 16px;
font-weight: bold;
color: #afafaf;
background:#212121;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #434343;
font-family: " Microsoft YaHei ";
}
.talk_title span{float:left}
.talk_title_c {
width: 100%;
height:30px;
line-height:30px;
}
.talk_record{
width: 100%;
height:398px;
overflow: hidden;
border-bottom-width: 1px;
border-bottom-style: solid;
border-bottom-color: #434343;
margin: 0px;
}
.talk_word {
line-height: 40px;
height: 40px;
width: 100%;
background:#212121;
}
.messages {
height: 24px;
width: 240px;
text-indent:5px;
overflow: hidden;
font-size: 12px;
line-height: 24px;
color: #666;
background-color: #ccc;
border-radius: 3px;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
}
.messages:hover{background-color: #fff;}
.talk_send{
width:50px;
height:24px;
line-height: 24px;
font-size:12px;
border:0px;
margin-left: 2px;
color: #fff;
background-repeat: no-repeat;
background-position: 0px 0px;
background-color: transparent;
font-family: " Microsoft YaHei ";
}
.talk_send:hover {
background-position: 0px -24px;
}
.talk_record ul{ padding-left:5px;}
.talk_record li {
line-height: 25px;
}
.talk_word .controlbtn a{
margin: 12px;
}
.talk .talk_word .order {
float:left;
display: block;
height: 14px;
width: 16px;
background-repeat: no-repeat;
background-position: 0px 0px;
}
.talk .talk_word .loop {
float:left;
display: block;
height: 14px;
width: 16px;
background-repeat: no-repeat;
background-position: -30px 0px;
}
.talk .talk_word .single {
float:left;
display: block;
height: 14px;
width: 16px;
background-repeat: no-repeat;
background-position: -60px 0px;
}
.talk .talk_word .order:hover,.talk .talk_word .active{
background-position: 0px -20px;
text-decoration: none;
}
.talk .talk_word .loop:hover{
background-position: -30px -20px;
text-decoration: none;
}
.talk .talk_word .single:hover{
background-position: -60px -20px;
text-decoration: none;
}
/* Discussion area */
.jp-container .talk_recordbox{
min-height:80px;
color: #afafaf;
padding-top: 5px;
padding-right: 10px;
padding-left: 10px;
padding-bottom: 0px;
}
.jp-container .talk_recordbox:first-child{border-top:none;}
.jp-container .talk_recordbox:last-child{border-bottom:none;}
.jp-container .talk_recordbox .talk_recordtextbg{
float:left;
width:10px;
height:30px;
display:block;
background-repeat: no-repeat;
background-position: left top;}
.jp-container .talk_recordbox .talk_recordtext{
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;
background-color:#b8d45c;
width:240px;
height:auto;
display:block;
padding: 5px;
float:left;
color:#333333;
}
.jp-container .talk_recordbox h3{
font-size:14px;
padding:2px 0 5px 0;
text-transform:uppercase;
font-weight: 100;
}
.jp-container .talk_recordbox .user {
float:left;
display:inline;
height: 45px;
width: 45px;
margin-top: 0px;
margin-right: 5px;
margin-bottom: 0px;
margin-left: 0px;
font-size: 12px;
line-height: 20px;
text-align: center;
}
/* Speaking by yourself */
.jp-container .talk_recordboxme{
display:block;
min-height:80px;
color: #afafaf;
padding-top: 5px;
padding-right: 10px;
padding-left: 10px;
padding-bottom: 0px;
}
.jp-container .talk_recordboxme .talk_recordtextbg{
float:right;
width:10px;
height:30px;
display:block;
background-repeat: no-repeat;
background-position: left top;}
.jp-container .talk_recordboxme .talk_recordtext{
-moz-border-radius:5px;
-webkit-border-radius:5px;
border-radius:5px;
background-color:#fcfcfc;
width:240px;
height:auto;
padding: 5px;
color:#666;
font-size:12px;
float:right;
}
.jp-container .talk_recordboxme h3{
font-size:14px;
padding:2px 0 5px 0;
text-transform:uppercase;
font-weight: 100;
color:#333333;
}
.jp-container .talk_recordboxme .user{
float:right;
height: 45px;
width: 45px;
margin-top: 0px;
margin-right: 10px;
margin-bottom: 0px;
margin-left: 5px;
font-size: 12px;
line-height: 20px;
text-align: center;
display:inline;
}
.talk_time{
color: #666;
text-align: right;
width: 240px;
display: block;
}test
First , Start three windows
Group chat
Single chat

summary
this paper , be based on Netty One of them was actually fighting WebSocket Protocol implementation of the web version of the chat server , You can see from the code that , be based on Netty Of WebSocket The implementation of is still very simple 、 Easy to implement .
however WebSocket There are still limitations in the use of the protocol , For example, you need browser support . But, after all, WebSocket On behalf of Web An important advance in technology , Can broaden our horizons , In some specific work scenarios , It can help us solve some problems
source :blog.csdn.net/wzljiayou/article/details/110506164
recommend :
The most comprehensive java Interview question bank
PS: Because the official account platform changed the push rules. , If you don't want to miss the content , Remember to click after reading “ Looking at ”, Add one “ Star standard ”, In this way, each new article push will appear in your subscription list for the first time . spot “ Looking at ” Support us !边栏推荐
- Double disk: the main differences between DFS and BFS, the differences in ideology, and the differences in code implementation
- Quick sequencing and optimization
- J-3-point practice in the second game of 2022 Niuke multi school
- Number of square arrays (day 81)
- 智能体重秤方案主控采用CSU18M91
- OC message mechanism
- 复盘:图像有哪些基本属性?关于图像的知识你知道哪些?图像的参数有哪些
- 【常用搜索问题】111
- Volatile keyword and its function
- Solution to Chinese garbled code in console header after idea connects to database to query data
猜你喜欢

How to optimize MySQL

飞腾腾锐 D2000 荣获数字中国“十大硬核科技”奖

FastBoot brush machine

客户端发送一条sql如何与服务器交互

Contour detection based on OpenCV (2)

复盘:图像有哪些基本属性?关于图像的知识你知道哪些?图像的参数有哪些

Add support for @data add-on in idea

九方智投是正规公司吗?一起聊聊九方智投

网络安全/渗透测试工具AWVS14.9下载/使用教程/安装教程

Double disk: the main differences between DFS and BFS, the differences in ideology, and the differences in code implementation
随机推荐
Banyan data model of Bairong
Volatile keyword and its function
Introduction to redis
C语言力扣第43题之字符串相乘。优化竖式
Kettle读取按行分割的文件
如何进行 360 评估
九方智投是正规公司吗?一起聊聊九方智投
app端接口用例设计方法和测试方法
深圳家具展首日,金可儿展位三大看点全解锁!
数据库使用安全策略
PIP3 setting alicloud
Tool class of localdatetime sorted out by yourself
客户端发送一条sql如何与服务器交互
How to interact with the server when the client sends an SQL message
DNS record type and explanation of related terms
docker 创建mysql 8.x容器,支持mac ,arm架构芯片
How to conduct 360 assessment
Source code analysis of openfeign
2022牛客多校第二场的J -- 三分做法
unity之二维数组实现正六边形地图