目录
一、简介
二、java服务端
1、引入包
2、配置
3、代码实现
三、H5客户端
1、代码实现
一、简介
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。
WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
现在,很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。
在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
二、java服务端
1.引入包(gradle管理)
compile 'org.springframework.boot:spring-boot-starter-websocket:2.0.4.RELEASE'
2.配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 简介:
*
* @Author: hzj
* @Date: 2019/7/8 0008 17:44
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
3.代码实现
import com.getoncar.common.exception.BaseException;
import com.getoncar.common.exception.CustomException;
import com.getoncar.entity.Agent_Entity;
import com.getoncar.entity.Message_Entity;
import com.getoncar.host.agent.config.websupport.ResponseVo;
import com.getoncar.model.AgentSendDialogueRequest;
import com.getoncar.model.MessageModel;
import com.getoncar.service.MessageAgentService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.EOFException;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static com.getoncar.service.MessageAgentService.oneMessageCache;
/**
* 简介:
*
* @Author: hzj
* @Date: 2019/7/9 0009 9:05
*/
@Slf4j
@Api(description ="websocket对话中心-hzj")
@RequestMapping(value = "/websocketController")
@ServerEndpoint(value = "/websocket/{userId}")
@Component //此注解千万千万不要忘记,它的主要作用就是将这个监听器纳入到Spring容器中进行管理
@RestController
public class WebSocketController {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
public static int onlineCount = 0;
//concurrent包的线程安全Set[ConcurrentHashMap],用来存放每个客户端对应的MyWebSocket对象。
public static ConcurrentHashMap<String,WebSocketController> webSocketSet = new ConcurrentHashMap<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
public Session session;
//接收参数中的用户ID
public Long userId;
//接收用户中的平台类型
public Long platformType;
public Session getSession() {
return session;
}
public void setSession(Session session) {
this.session = session;
}
private static ExecutorService executorService = Executors.newCachedThreadPool();
private static MessageAgentService messageService;
@Autowired
public void setMessageService(MessageAgentService messageService) {
WebSocketController.messageService = messageService;
}
@ApiOperation(value ="服务器群发消息")
@ApiImplicitParams(
@ApiImplicitParam(paramType = "query",name = "content",value = "消息内容",dataType = "String")
)
@GetMapping("/ServerSendInfo")
public void ServerSendInfo(String content) throws IOException {
sendInfos("这是服务器给你推送的消息:"+content);
}
/**
* 连接建立成功调用的方法
* 接收url中的参数
*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") Long userId) {
log.info("进入websocket连接");
if(webSocketSet.containsKey(userId+"")){
log.info("重复登陆-"+userId);
try {
WebSocketController before = webSocketSet.get(userId+"");
if(before.getSession().isOpen()){
log.info("发送被迫下线通知");
before.getSession().getBasicRemote().sendText("你的会话再另一地方登陆,被迫下线");
before.getSession().close();//关闭会话
}
webSocketSet.remove(before);
log.info("重复登陆"+userId+"连接"+before.getSession().getId()+"被服务器主动关闭!当前在线人数为" + getOnlineCount());
} catch (IOException e) {
e.printStackTrace();
return;
}
}
int maxSize = 200 * 1024; // 200K
// 可以缓冲的传入二进制消息的最大长度
session.setMaxBinaryMessageBufferSize(maxSize);
// 可以缓冲的传入文本消息的最大长度
session.setMaxTextMessageBufferSize(maxSize);
this.session = session;
this.userId = userId;
this.platformType = platformType;
webSocketSet.put(userId+"",this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新连接加入!当前在线人数为" + getOnlineCount() + " userId==== " + userId + " platformType==== " + platformType);
// try {
// sendMessage("连接成功");
// } catch (IOException e) {
// log.error("websocket IO异常");
// }
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(CloseReason reason) {
try {
webSocketSet.remove(this); //从set中删除
webSocketSet.remove(this.userId+""); //从set中删除
System.out.println("连接关闭***************"+this.userId);
subOnlineCount(); //在线数减1
log.info("有一连接"+this.session.getId()+"关闭!当前在线人数为" + getOnlineCount());
log.info("连接"+this.session.getId()+"关闭原因:"+reason.getCloseCode()+"-"+reason.toString());
}catch (Exception e){
log.info("异常情况");
e.printStackTrace();
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
//{"car_dealer_id": 198,"car_resource_id": 88, "content": "H你好啊","token":"56bd2cbf1e1349f29cdbbbc54ffc1b95"}
log.info("来自客户端"+session.getId()+"的消息:" + message);
if(message.equals("_0_")){
log.info("心跳");
System.err.println("心跳");
}else {
Runnable t = new Runnable() {
@Override
public void run() {
JSONObject json = JSONObject.fromObject(message);
AgentSendDialogueRequest asdr = (AgentSendDialogueRequest)JSONObject.toBean(json,AgentSendDialogueRequest.class);
Long receiverId = 0L;
Agent_Entity agent_entity = messageService.getAgentByAccessToken(asdr.getToken());
if(agent_entity != null) {
Long agent_id = agent_entity.getId();
asdr.setFromAgentId(agent_id);
receiverId = (asdr.getCar_dealer_id());//接收者id
try {
if (session.isOpen()) { //先确认 session是否已经打开 使用session.isOpen() 为true 则发送消息。
sendInfo(receiverId, JSONObject.fromObject(asdr).toString()); //把发送者userId的消息发给-->receiverId接收者id
}
} catch (EOFException e) {
log.info("报错EOFException" + e.getMessage());
System.err.println("报错EOFException" + e.getMessage());
} catch (IOException e) {
log.info("报错IOException" + e.getMessage());
e.printStackTrace();
}finally {
//入库操作
}
}
}
};
executorService.submit(t);
}
}
/**
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误" + error);
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
synchronized (this.session) {
this.session.getBasicRemote().sendText(message);
}
}
/**
* 私发
*
* @param message
* @throws IOException
*/
public static void sendInfo(Long userId, String message) throws IOException {
for (WebSocketController item : webSocketSet.values()) {
try {
if (item.userId.equals(userId)) {
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}
/**
* 群发自定义消息
*/
public static void sendInfos(String message) throws IOException {
log.info(message);
for (WebSocketController item : webSocketSet.values()) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketController.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketController.onlineCount--;
}
}
三、H5客户端
1.代码实现
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<!--<meta name="viewport" content="width=device-width, initial-scale=1">-->
<title>PC端聊天窗口</title>
<script src="jQuery-2.1.4.min.js" type="text/javascript"></script>
<link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600" rel="stylesheet">
<link rel="stylesheet" href="webChat/css/reset.min.css">
<link rel="stylesheet" href="webChat/css/style.css">
</head>
<body>
<!--连接服务器-->
<div style="margin: 10px 10px;">
<input type="text" placeholder="请输入账号" id="phone" style="width: 200px;height: 30px;"/>
<input type="button" id="connect" value="连接服务器" style="width: 200px;height: 30px;background: #FF0B0A"/>
</div>
<div class="wrapper">
<div class="container" style="width: 86%;">
<div class="left" style="overflow-y: auto;overflow-x: hidden;">
<div class="top" style="position: absolute;top: -3%;height: 73px;width: 37%;z-index: 999">
<input type="text" placeholder="昵称、车商名、店名、店址查询" id="putSearch" style="width: 206px;padding-left: 6px" />
<a href="javascript:;" class="search" onclick="select()"></a>
</div>
<ul class="people" id="chatList" style="margin-top: 18%;">
</ul>
</div>
<div class="right" id="chatLog" style="overflow-y: auto;overflow-x: hidden;">
<div class="top"><span>To: <span class="name">Dog Woofson</span></span></div>
<!--<div class="chat" data-chat="person1" >-->
<!--<div class="conversation-start">-->
<!--<span>Today, 6:48 AM</span>-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Hello,-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--it's me.-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--I was wondering...-->
<!--</div>-->
<!--</div>-->
<!--<div class="chat" data-chat="person2">-->
<!--<div class="conversation-start">-->
<!--<span>Today, 5:38 PM</span>-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Hello, can you hear me?-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--I'm in California dreaming-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--... about who we used to be.-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--Are you serious?-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--When we were younger and free...-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--I've forgotten how it felt before-->
<!--</div>-->
<!--</div>-->
<!--<div class="chat" data-chat="person3">-->
<!--<div class="conversation-start">-->
<!--<span>Today, 3:38 AM</span>-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Hey human!-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Umm... Someone took a shit in the hallway.-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--... what.-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--Are you serious?-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--I mean...-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--It’s not that bad...-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--But we’re probably gonna need a new carpet.-->
<!--</div>-->
<!--</div>-->
<!--<div class="chat" data-chat="person4">-->
<!--<div class="conversation-start">-->
<!--<span>Yesterday, 4:20 PM</span>-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--Hey human!-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--Umm... Someone took a shit in the hallway.-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--... what.-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Are you serious?-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--I mean...-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--It’s not that bad...-->
<!--</div>-->
<!--</div>-->
<!--<div class="chat" data-chat="person5">-->
<!--<div class="conversation-start">-->
<!--<span>Today, 6:28 AM</span>-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Wasup-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Wasup-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--Wasup for the third time like is <br />you blind bitch-->
<!--</div>-->
<!--</div>-->
<!--<div class="chat" data-chat="person6">-->
<!--<div class="conversation-start">-->
<!--<span>Monday, 1:27 PM</span>-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--So, how's your new phone?-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--You finally have a smartphone :D-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--Drake?-->
<!--</div>-->
<!--<div class="bubble me">-->
<!--Why aren't you answering?-->
<!--</div>-->
<!--<div class="bubble you">-->
<!--howdoyoudoaspace-->
<!--</div>-->
<!--</div>-->
<div class="write">
<a href="javascript:;" class="write-link attach"></a>
<input type="text" id="content" placeholder="请输入发送的消息" />
<a href="javascript:;" class="write-link smiley"></a>
<a href="javascript:;" class="write-link send" id="send"></a>
</div>
</div>
</div>
</div>
<!--<script src="webChat/js/index.js"></script>-->
<div style="text-align:center;margin:1px 0; font:normal 14px/24px 'MicroSoft YaHei';color: #dddddd;">
<p>适用浏览器:360、FireFox、Chrome、Opera、傲游、搜狗、世界之窗. 不支持Safari、IE8及以下浏览器。</p>
</div>
<script>
var loginUrl = "//localhost:8080/messageController/getAgentByAccessToken_web";
var token = "1";
var url = "ws://localhost:8080/websocket/";
var selectAllDealerUrl = "//localhost:8080/messageController/selectAllDealer";
var chatLogUrl = "//localhost:8080/messageController/InformationListShowTwo";
$(document).ready(function() {
alert("请先在左上角输入1-180其中一个数字进行连接");
});
var car_dealer_id_send = "";
var oPhone = document.getElementById('phone');
var oUl=document.getElementById('content');
var oConnect=document.getElementById('connect');
var oSend=document.getElementById('send');
var oInput=document.getElementById('message');
var chatList=document.getElementById('chatList');
var chatLog=document.getElementById('chatLog');
var ws=null;
var agentId = null;
var agentType = null;
oConnect.onclick=function(){
console.log("oPhone="+oPhone.value);
if(oPhone.value==""){
alert("请先在左上角输入1-180其中一个数字进行连接");
return;
}
//登陆
//把聊天列表列表查询出来
$.ajax({
url:loginUrl, //请求的url地址
contentType: "application/json",
dataType:"json", //返回格式为json
async:true,//请求是否异步,默认为异步,这也是ajax重要特性
data:{"token":oPhone.value}, //参数值
type:"GET", //请求方式
// headers:{"token":token},
beforeSend:function(){
//请求前的处理
chatList.innerHTML += "<h2 class='time' style='font-size: 18px;color:red;'>正在加载聊天列表中...请稍等</h2>";
},
success:function(data){
//请求成功时处理
console.log(data.data);
if(data.result == "success"){
console.log("登陆agent_id="+data.data.agentId+"--nickName="+data.data.nickName+"--name身份="+data.data.contact);
agentId = data.data.agentId;
//连接webScoket start
ws=new WebSocket(url+agentId);
ws.onopen=function(){
oConnect.value = data.data.nickName+"-已连接服务器";
token = oPhone.value;
console.log(oPhone.value+"连接成功--token="+token+"--连接地址="+(url+agentId));
//把聊天列表查询出来
selectChatList("");
}
ws.onmessage=function(result){
var jsons = JSON.parse(result.data);
console.log("服务器发送的数据="+result.data+"--jsons.content="+jsons.content+"--jsons.fromAgentId="+jsons.fromAgentId);
//如果处于当前聊天对象窗口-直接添加新消息到聊天记录
if(car_dealer_id_send == jsons.fromAgentId){
chatLog.innerHTML += "<div class='bubble you'>"+jsons.content+"</div>";
//划到最底部
chatLog.scrollTop = chatLog.scrollHeight; //滚动到最下面
}else{
//如果未处于当前聊天对象窗口-添加红点样式
$('#id'+jsons.fromAgentId).addClass("search");
document.getElementById('id'+jsons.fromAgentId).class="search";
}
}
ws.onclose=function(){
oUl.innerHTML+="<li>客户端已断开连接</li>";
};
ws.onerror=function(evt){
console.log("onerror了,重连地址="+url+agentId);
ws=new WebSocket(url+agentId);
};
//开启心跳 + 断线重联
setInt;
//连接webSocket end
}else{
alert("登陆失败,请重新连接");
}
},
complete:function(){
//请求完成的处理
},
error:function(){
//请求出错处理
}
});
};
function selectChatList(putSearch) {
//把聊天列表列表查询出来
$.ajax({
url:selectAllDealerUrl, //请求的url地址
contentType: "application/json",
dataType:"json", //返回格式为json
async:true,//请求是否异步,默认为异步,这也是ajax重要特性
data:{"select":putSearch}, //参数值
type:"GET", //请求方式
// headers:{"token":token},
beforeSend:function(){
//请求前的处理
chatList.innerHTML="";
chatList.innerHTML += "<h2 class='time' style='font-size: 18px;color:red;'>正在加载聊天列表中...请稍等</h2>";
},
success:function(data){
//请求成功时处理
chatList.innerHTML="";
console.log(data.data);
if(data.result == "success"){
$.each(data.data,function(i,value){
if(value.notify_type=="MESSAGE"){
console.log(i+"--"+value.notify_type+"--"+value.car_dealer_id);
chatList.innerHTML +=
// "<li class='person' data-chat='person"+i+"' onclick='inTwo("+value.car_dealer_id+")'>"+
"<li class='person' data-chat='person"+i+"' onclick=\"inTwo(" + value.car_dealer_id + ",'" + value.shop_name + "');\" >"+
"<a class='a' id='id"+value.car_dealer_id+"' style='background-color:red;width: 10px;height: 10px;color: #FF0B0A;'></a>"+
"<img src='webChat/img/thomas.jpg' alt='' />"+
"<span class='name'>"+value.shop_name+"</span>"+
"<span class='time'>"+value.sender_type+"</span>"+
// "<span class='preview'>"+value.car_dealer_id+"</span>"+
"</li>";
}
})
}else{
alert("聊天列表加载失败");
}
},
complete:function(){
//请求完成的处理
},
error:function(){
//请求出错处理
}
});
}
function inTwo(car_dealer_id,shop_name) {
car_dealer_id_send = car_dealer_id;
console.log("car_dealer_id===="+car_dealer_id+"--shop_name="+shop_name);
//去除红点
$('#id'+car_dealer_id).removeClass("search");
//把聊天记录查询出来
$.ajax({
url:chatLogUrl, //请求的url地址
contentType: "application/json",
dataType:"json", //返回格式为json
async:true,//请求是否异步,默认为异步,这也是ajax重要特性
data:{"notify_type":"MESSAGE","car_dealer_id":car_dealer_id}, //参数值
type:"GET", //请求方式
headers:{"token":token},
beforeSend:function(){
//请求前的处理
chatLog.innerHTML += "<h1 class='time' style='position:fixed;top:-87px;z-index:999;font-size: 22px;color:red;'>正在加载聊天记录中...请稍等</h1>";
},
success:function(data){
//请求成功时处理
chatLog.innerHTML ="";
// console.log(data.data.messageModels);
if(data.result == "success"){
chatLog.innerHTML ="<div class='top' style='position:fixed;top:-47px;z-index:999;width: 62.4%;'><span>正再与: <span class='name' style='color: #FF0B0A;'>"+shop_name+"</span> 聊天</span></div>";
$.each(data.data.messageModels,function(i,value){
// console.log("消息内容="+value.content);
// chatLog.innerHTML += "<div class='bubble you'>"+value.content+"</div>";
if(value.sender_type == "DEALER"){
chatLog.innerHTML += "<div class='bubble you'>"+value.content+"--"+value.created_at+"</div>";
}
if(value.sender_type=="AGENT"){
chatLog.innerHTML += "<div class='bubble me'>"+value.content+"--"+value.created_at+"</div>";
}
})
chatLog.scrollTop = chatLog.scrollHeight; //滚动到最下面
chatLog.innerHTML +=
"<div class='write' style='position:fixed;bottom:-7%;left: 42%;width: 52%;'>"+
"<a href='javascript:;' class='write-link attach'></a>"+
"<input type='text' id='content' placeholder='请输入发送的消息' style='background: #eb7350;' />"+
// "<a href='javascript:;' class='write-link smiley'></a>"+
"<a href='javascript:;' class='write-link send' id='send' onclick='sendOn(car_dealer_id_send)'></a>"+
"</div>";
}else{
alert("聊天记录加载失败");
}
},
complete:function(){
//请求完成的处理
},
error:function(){
//请求出错处理
}
});
}
function sendOn(car_dealer_id){
console.log("sendOn接收者id="+car_dealer_id);
var json=
{
"car_dealer_id": car_dealer_id,
"car_resource_id": 1,
"content": $("#content").val(),
"token":token
};
console.log("点击了发送数据="+$("#content").val()+"---"+oUl.value);
if(ws){
ws.send(JSON.stringify(json));
chatLog.innerHTML += "<div class='bubble me'>"+$("#content").val()+"</div>";
chatLog.scrollTop = chatLog.scrollHeight; //滚动到最下面
}
}
//心跳 + 断线重连
var times= 1000*50;
var setInt=setInterval(function heartbeat(){
if(ws.readyState==1){ // 0=正在连接 1=表示连接成功,可以通信了
ws.send('_0_');
console.log("心跳发送"+ws+"(每"+(times/1000)+"秒一次)");
times = 1000*50;
}else if(ws.readyState==3){ // 2=连接正在关闭 3=连接已经关闭,或者打开连接失败。
times = 2000;
console.log("断线重连url="+url+agentId+"(每"+(times/1000)+"秒一次) heartbeat"+ws);
ws = new WebSocket(url+agentId);
}
},times);
//搜索
function select() {
var putSearch = document.getElementById('putSearch');
console.log(putSearch.value);
selectChatList(putSearch.value);
$("#putSearch").val(""); //设置搜索框的值为空
}
</script>
</body>
</html>
HTML、css、js资料下载地址
链接:https://pan.baidu.com/s/1VXYBiz5vKrMmlvWpqYw-4A
提取码:2xtq
开发过程如有疑问可交流讨论 WX:18637815946