java+websocket实现网页聊天室

核心技术websocket

前提

1、tocmat7.02版本以上

2、浏览器支持websocket通讯

3、这个是html5的功能


客户端和服务器建立连接

jsp/html页面

1、浏览器和后台服务器要通信,首先是url

通俗易懂的格式如下    "ws://localhost:8080/项目名/请求名"   

如果要携带参数的话, "ws://localhost:8080/项目名/请求名/"+参数 

 var target = "ws://"+window.location.host+"/LawerSys/websocket"+"/"+username;

2、与后台服务器建立连接
  var socket = new WebSocket(target);

Java后台部分

1、新建一个普通的类,在这个类上面加上@ServerEndpoint("/请求名");

这个"/请求名"与html中 "ws://localhost:8080/项目名/请求名"    请求名一致;

html页面与后台之所以可以建立的连接,就是根据@ServerEndpoint来识别websocket通讯类,根据("/请求名")和前台的请求相匹配的对应的类;

2、new 一个方法,来和前台建立连接,

 websocket通讯,浏览器和服务器只会建立一次连接,因此,我们需要在建立连接握手的时候,把这个会话存储到集合里面

 1、一般是用map集合,因为key是不能重复的

2、如果是List集合的话,无法区分是自己发的信息还是别人发的信息

3、集合存在意义,就是实现循环遍历转发给连接服务器的客户端浏览器页面

 但是我不能说open()这个方法是建立连接的它就连接的,需要标识@Onopen


 

注意:value可以是java类的类型

注意要有static关键字,如果没有这个关键字,添加集合的长度一直都是1;

原因是两个浏览器访问后台服务器,然后虽然执行了添加操作,但是执行结束,这个集合就销毁了,动态分配内存,所以需要static关键字;

 value也可以是Session,我试过这两个,都没有问题

 

 1、由于jsp请求是基于http请求,无状态的,就是独立的线程,两个jsp是没有任何关系的;sesion的作用就是标识,给这个jsp页面做唯一标识;

2、把session会话存储到map集合里面(两个人聊天),如果是多人聊就是CopyOnWriteArraySet集合;目的是转发信息

在open方法里面,添加会话,username是唯一标识符,这个符号是区分浏览器的,一个浏览器一个标识符

现在浏览器和服务器可以建立连接了,当然你现在测试肯定有问题,启动都会报错,因为其他的方法没有 

 在java后台添加关闭和错误的方法

@OnClose
	public void close(){
		System.out.println("关闭");
	}
	@OnError
	public void error(Throwable t) {
		// 添加处理错误的操作
		System.out.println("发生错误");
		t.printStackTrace();
	}

 


客户端和服务器接收和发送消息

现在浏览器要发送消息和接收消息

1、有一个文本框一个按钮,点击按钮实现发送消息

<!-- 消息框 -->
<table class="table">
	<caption>咨询页面</caption>
	 <tr>
         <td>
		  <textarea class="form-control" rows="5" id="content"></textarea>
		 </td>
      </tr>
       <tr>
         <td>
		    <input type="text" class="form-control" placeholder="昵称" id="user">
		 </td>
      </tr>
      <tr>
         <td>
        <input type="text" class="form-control" placeholder="消息输入" id="mess">
		  </td>
		  <td>
	   <button type="button" class="btn btn-success" id="sendmes">发送</button>
		  </td>
      </tr>
     
</table>

2、js代码(点击按钮触发事件,最主要的是socket.send()方法,这个是websocket的发送消息的方法)

 	//1.只有点击按钮以后,才可以发送消息
   	document.getElementById("sendmes").onclick = function(){
   	var mess=$("#mess").val();
   		if(mess!=null&&mess!=""){
   	     socket.send("消息内容:"+mess);
   	        }else{
   	                alert("发送的消息或用户名不能为空");
                  	}
      }
      //1.只有点击按钮以后,才可以发送消息

3、接收来自客户端的信息,然后把消息显示在框里面

接收客户端 socket.onmessage=function(evt){}就是接受服务器发送的信息

	socket.onmessage=function(evt){
   	     var s=evt.data;
   	     //把数据显示在文本框里面
   	     var cs=$("#content").val();
   	     $("#content").val(cs+s);
   	}

服务器负责接收各个浏览器的消息,然后把消息转发给连接服务器子节点的客户端

1、一个无返回值的方法  注解@OnMessaage   代表服务器一旦接收消息,就触发这个方法

2、循环遍历连接服务器的客户端们,判断这个会话是来是这个请求自己发的,还是其他请求发的,目的在于在浏览器上面显示在左边还是右边;

3、this.session.getAsyncRemote().sendText()是异步发送消息,非阻塞的

以下这个代码只是服务器转发给客户端,没有区分是本身还是他人

@OnMessage
	public void getMessage(String str, Session session) { 
		for (String s : map.keySet()) {
			System.out.println("this:"+this+"客户端的信息:" + str);
			// 服务器把信息分为是自己的还是其他人发的,再加上时间,然后群发到连接的用户session会话里面
			String time = sim.format(new Date());
			String info = str + "时间:" + time;
			// 会话异步发送消息
			map.get(s).session.getAsyncRemote().sendText(info);
		}

	}

 


多人聊天

1、用了富文本编辑器https://blog.csdn.net/qq_37591637/article/details/89378284

2、websocket网页技术

 <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>即时群聊</title>
   <link href='umeditor/themes/default/css/umeditor.css' type="text/css" rel="stylesheet" >  
   <script src="js/jquery-3.3.1.min.js"></script>
  <script type="text/javascript" src="umeditor/umeditor.js"></script>    
  <script type="text/javascript" src="umeditor/umeditor.config.js"></script>    
  <script type="text/javascript" src="umeditor/lang/zh-cn/zh-cn.js"></script>
<style>
.title {
	text-align: center;
}
.chat-content-container {
	height: 29rem;
	overflow-y: scroll;
	border: 1px solid silver;
}
</style>
</head>
<body>
	<!-- 标题 -->
	<div class="title">
		<div class="am-g am-g-fixed">
			<div class="am-u-sm-12">
				<h1 class="am-text-primary">律师平台</h1>
			</div>
		</div>
	</div>
	<!-- 聊天内容框开始 -->
	<div class="chat-content">
		<div class="am-g am-g-fixed chat-content-container">
			<div class="am-u-sm-12">
				<ul id="message-list" class="am-comments-list am-comments-list-flip"></ul>
			</div>
		</div>
	</div>
	<!-- 聊天内容框结束 -->
	<div class="message-input am-margin-top">
		<!-- 输入内容框开始 -->
		<div class="am-g am-g-fixed">
			<div class="am-u-sm-12">
				<form class="am-form">
					<div class="am-form-group">
						<script type="text/plain" id="myEditor" style="width: 100%;height: 8rem;"></script>
					</div>
				</form>
			</div>
		</div>
		<!-- 输入昵称框开始 -->
		<div class="am-g am-g-fixed am-margin-top">
			<div class="am-u-sm-6">
				<div id="message-input-nickname" class="am-input-group am-input-group-primary">
					<span class="am-input-group-label"><i class="am-icon-user"></i></span>
					<input id="nickname" type="text" class="am-form-field" placeholder="Please enter nickname"/>
				</div>
			</div>
			<div class="am-u-sm-6">
				<button id="send" type="button" class="am-btn am-btn-primary">
					<i class="am-icon-send"></i>发送
				</button>
			</div>
		</div>
	</div>	
	<script>
	$(function() {
		    //随机方法   生成id模拟用户
		    function rand(num){
		    	return parseInt(Math.random()*1000000+1);
		    }
			// 初始化消息输入框
			var um = UM.getEditor('myEditor');
			// 使昵称框获取焦点
			$('#nickname')[0].focus();
			// 新建WebSocket对象,最后的/WebSocket跟服务器端的@ServerEndpoint("/websocket")对应
			//var socket = new WebSocket('ws://${pageContext.request.getServerName()}:${pageContext.request.getServerPort()}${pageContext.request.contextPath}/websocket');
			//var socket = new WebSocket("ws://localhost:8080/Chat/websocket");
			var target = "ws://"+window.location.host+"/LawerSys/websocket"+"/"+rand();
			//alert(target);
			var socket = new WebSocket(target);
			// 处理服务器端发送的数据
			socket.onmessage = function(event) {
				addMessage(event.data);
			};
			
			
			// 点击Send按钮时的操作
			$('#send').on('click', function() {
				var nickname = $('#nickname').val();
				//alert(um.getContent()); //内容
				//alert(nickname);	//昵称
				if (!um.hasContents()) {	// 判断消息输入框是否为空
					// 消息输入框获取焦点
					um.focus();
					// 添加抖动效果
					$('.edui-container').addClass('am-animation-shake');
					setTimeout("$('.edui-container').removeClass('am-animation-shake')", 1000);
				} else if (nickname == '') {	// 判断昵称框是否为空
					//昵称框获取焦点
					$('#nickname')[0].focus();
					// 添加抖动效果
					$('#message-input-nickname').addClass('am-animation-shake');
					setTimeout("$('#message-input-nickname').removeClass('am-animation-shake')", 1000);
				} else {
					// 发送消息
					socket.send(JSON.stringify({
						content : um.getContent(),
						nickname : nickname
					}));
					// 清空消息输入框
					um.setContent('');
					// 消息输入框获取焦点
					um.focus();
				}
			});
 
			// 把消息添加到聊天内容中
			function addMessage(message) {
				message = JSON.parse(message);
				var messageItem = '<li class="am-comment '
						+ (message.isSelf ? 'am-comment-flip' : 'am-comment')
						+ '">'
						+ '<a href="javascript:void(0)" ><img src="assets/images/'
						+ (message.isSelf ? 'self.jpg' : 'others.jpg')
						+ '" alt="" class="am-comment-avatar" width="48" height="48"/></a>'
						+ '<div class="am-comment-main"><header class="am-comment-hd"><div class="am-comment-meta">'
						+ '<a href="javascript:void(0)" class="am-comment-author">'
						+ message.nickname + '</a> <time>' + message.date
						+ '</time></div></header>'
						+ '<div class="am-comment-bd">' + message.content
						+ '</div></div></li>';
				$(messageItem).appendTo('#message-list');
				// 把滚动条滚动到底部
				$(".chat-content-container").scrollTop($(".chat-content-container")[0].scrollHeight);
			}
		});
	</script>
	
</body>
</html>

java代码

package cn.com.socket;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import net.sf.json.JSONObject;
@ServerEndpoint("/websocket/{userId}")
public class ServerSocket {
	// 日期格式化
	private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm");	
	//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
	private static int onlineCount = 0;
	//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
	private static Map<String,ServerSocket> webSocketSet = new ConcurrentHashMap<String,ServerSocket>();
	//与某个客户端的连接会话,需要通过它来给客户端发送数据
	private Session session;
	private String userId;

	@OnOpen
	public void open(@PathParam("userId")String userIds,Session session) {
		// 添加初始化操作
		System.out.println("---初始化----userId:"+userIds);
		this.session = session;
		//获取当前登录用户的id
		this.userId = userIds;
		webSocketSet.put(userIds,this);     //加入set中
		System.out.println("有新连接加入!当前在线人数为" +webSocketSet.size());
	}
	

	@OnMessage
	public void getMessage(String message, Session session1) {
		// 把客户端的消息解析为JSON对象
		JSONObject jsonObject = JSONObject.fromObject(message);
		// 在消息中添加发送日期
		jsonObject.put("date", DATE_FORMAT.format(new Date()));
		// -----------------------把消息发送给所有连接的会话--------------------------------
		System.out.println("来自客户端的消息"+this.userId+":" + message);
		Set<String> str=webSocketSet.keySet();
        for(String item: str){
             try {
            	 //当前用户右侧显示,非本用户左侧显示
            	
            	 if(this.userId.equals( webSocketSet.get(item).userId)){jsonObject.put("isSelf", true);}
            	 else{jsonObject.put("isSelf", false);}
     			 // 发送JSON格式的消息
            	 webSocketSet.get(item).sendMessage(jsonObject.toString());
             } catch (IOException e) {
                 e.printStackTrace();
                 continue;
             }
         }
        	
	}
	
 
	@OnClose
	public void close() {
		// 添加关闭会话时的操作
		webSocketSet.remove(this);  //从set中删除
	}
 
	
	@OnError
	public void error(Throwable t) {
		// 添加处理错误的操作
		System.out.println("发生错误");
		t.printStackTrace();
	}
	
	

     public synchronized  void  sendMessage(String message) throws IOException{
         this.session.getAsyncRemote().sendText(message);//非阻塞式的
     }
 
}
 

两人聊天室

html代码不需要改的

package cn.com.socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/serverSocket")
public class MySocket {
	// 时间
	private SimpleDateFormat sim = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	// 一个线程集合存储session线程会话
	private Map<MySocket,String> map = new ConcurrentHashMap<MySocket,String>();
	private Session session;
	@OnOpen
	public void open(Session session) {
		// 把当前的session存储起来,以便后来的时候服务器转发数据
		this.session = session;
		  System.out.println(+map.size());
		map.put(this,"");
	}

	@OnClose
	public void close() {
		System.out.println("关闭连接");
	}

	@OnMessage
	public void getMessage(String str, Session session) { 
		for (MySocket s : map.keySet()) {
			System.out.println("this:"+this+"客户端的信息:" + str);
			// 服务器把信息分为是自己的还是其他人发的,再加上时间,然后群发到连接的用户session会话里面
			String time = sim.format(new Date());
			String info = str + "时间:" + time;
			// 会话异步发送消息
			s.session.getAsyncRemote().sendText(info);
		}

	}

	@OnError
	public void error(Throwable t) {
		// 添加错误操作
	}

}

  • 4
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qq_37591637

请给我持续更新的动力~~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值