认识WebSocket(二)

前几天,我们认识了WebSocket,一个是客户端,一个是服务端,二者在连接的时候通过HTTP建立一次握手,后面就一直是Tcp传输的长连接。然而,在实际开发,几乎都是多客户端的形式,来连接。那么这次我们来用WebSocket技术做一下多客户端连接服务端。

多客户端和单客户端连接服务端的区别在于,应该在服务端对每个连接成功的客户端所有区分,即在单客户端连接成功时,保存识别该客户端的唯一标识(比如数据库中的主键,唯一标识)。这样就完成了多客户端的连接。

这里我们用SpringBoot整合WebSocket框架,实现一个多用户群聊,以及广播消息的功能。

这里,我用的是1.5.20版本,比较稳定,然后只需要导入Web模块和WebSocket模块。点击finish。

pom文件:
 

<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
		<!-- thymeleaf -->
        <!-- 必须加这个starter,不然访问页面会报错:没有配置thymeleaf模板的页面后缀。。。。-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
	</dependencies>

先写配置类:

@Configuration	//表示这是个配置类,用法和 @Component 注解没有区别
public class WebSocketConfig {

	/**
	 * @Bean 注解会把该方法的返回值当做一个JavaBean,存放在Spring上下文中,以供使用
	 * ServerEndpointExporter类的作用是,会扫描所有的服务器端点,把带有  @ServerEndpoint 注解的所有类都添加进来
	 * @return
	 */
	@Bean
	public ServerEndpointExporter serverEndpointExporter() {
		return new ServerEndpointExporter();
	}
}

写WebSocket服务端:

package com.xk.springboot.websocket.server;

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.PathParam;
import javax.websocket.server.ServerEndpoint;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * wevsocket服务端
 * @author xiake
 *
 */
@Component
@ServerEndpoint("/myws/{nickname}")
public class MyWebSocketServer {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(MyWebSocketServer.class);

	/**
	 * 以用户的昵称为key,WebSocket为对象保存起来,因为是保存所有客户端对象的集合,所以必须是 static 的
	 */
	private static final Map<String, MyWebSocketServer> MORE_CHAT_USER_MAP = new ConcurrentHashMap<String, MyWebSocketServer>();
	private static int onlineNumber = 0;//房间内人数
	private Session session;//会话
	private String nickname;//用户昵称
	
	@OnOpen
	public void onOpen(Session session,@PathParam("nickname")String nickname) {
		this.session = session;
		this.nickname = nickname;
		//当客户端连接成功时,我们应该获取此客户端的唯一标识,然后保存到Map中
		String sessionId = session.getId();
		MORE_CHAT_USER_MAP.put(sessionId, this);
		//房间内人数+1
		onlineNumber++;
		//通知所有已经在线的用户,xxx加入房间
		sendMessageAll("["+nickname+"]"+"加入群聊", "系统消息");
	}
	
	@OnError
	public void onError(Session session,Throwable error) {
		LOGGER.info("服务端发生了错误 - {}",error.getMessage());
	}
	
	@OnClose
	public void onClose() {
		//此客户端断开连接后,在线人数-1,并且从集合中移除该客户端对象
		onlineNumber--;
		MORE_CHAT_USER_MAP.remove(nickname);
		//通知其它客户端
		sendMessageAll("["+nickname+"] 下线了", "系统消息");
	}
	
	@OnMessage
	public void onMessage(String message,Session session) {
		sendMessageAll(message, this.nickname);
	}
	
	/**
	 * 给其它人广播消息
	 * @param message
	 * @param nickName
	 */
	public void sendMessageOther(String message,String nickName) {
		for (MyWebSocketServer item : MORE_CHAT_USER_MAP.values()) {
			if(!item.nickname.equals(nickName)) {
				item.session.getAsyncRemote().sendText("[" + nickName + "]: " + message);
			}
		}
	}
	
	/**
	 * 给某个人发送消息
	 * @param message
	 * @param toNickName
	 */
	public void sendMessageTo(String message, String fromNickName, String toNickName) {
		for (MyWebSocketServer item : MORE_CHAT_USER_MAP.values()) {
			if(item.nickname.equals(toNickName)) {
				item.session.getAsyncRemote().sendText("[" + fromNickName + "]私聊了您: " + message);
				break;
			}
		}
	}
	
	public void sendMessageToMe(String message,String nickname) {
		this.session.getAsyncRemote().sendText("[" + nickname + "]:" + message);
	}
	
	/**
	 * 给所有人发送消息
	 * @param message
	 * @param fromNickName
	 */
	public void sendMessageAll(String message,String fromNickName) {
		for (MyWebSocketServer item : MORE_CHAT_USER_MAP.values()) {
			item.session.getAsyncRemote().sendText("[" + fromNickName + "]: " + message);
		}
	}
	
	public static synchronized int getOnLineCount() {
		return onlineNumber;
	}

}

在这上面,相当于是在单客户端连接的基础上,增加了一些代码,是用来处理多客户端的,首先写代码之前,我们的思路是一定要正确的,在多客户端连接中,我们应该区分开每个客户端,所以需要保存每个客户端的唯一标识,幸好,WebSocket的Session会话中有唯一标识ID,把ID作为key,对象作为value,保存到Map集合中,需要的时候,再从Map集合中取出来,这样就形成了多客户端互不影响。

客户端:

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8"/>
	<title>群聊功能实现</title>
	<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
	<div>
		<input type="text" id="nickname" placeholder="请输入昵称"/>
		<button id="login">登录</button>
	</div>
	<div>
		<input type="text" id="message" placeholder="输入消息"/>
		<button id="send" disabled="disabled">发送</button>
	</div>
	<div id="show"></div>
</body>

<script type="text/javascript">
	var ws = null;
	var message = null;
	
	$('#login').click(function(){
		var nickname = $('#nickname').val();
		if(nickname == null || nickname.length == 0){
			alert("请输入昵称后再登录");
			return;
		}
		
		var url = "ws://localhost:7777/myws/" + nickname;
		ws = new WebSocket(url);
		ws.onopen = function(){
			$('#nicknme').attr("disabled","disabled");//登录成功后昵称不可更改
			$('#login').attr("disabled","disabled");//登录成功后登录按钮不可点击
			$('#send').removeAttr("disabled");//登录成功后发送按钮可以被点击
			$('#show').append("<p>连接成功</p>");
		}
		ws.onmessage = function(event){
			$('#show').append("<p>" + event.data + "</p>");
		}
		ws.onerror = function(){
			alert("连接出错");
		}
		
		//发送消息
		$('#send').click(function(){
			var message = $('#message').val();
			if(message == null || message.length == 0){
				alert("消息不能为null");
				return;
			}
			ws.send(message);
		});
	});
</script>
</html>

在这里,我用html页面充当客户端来和服务端连接。需要注意的是,我们后台WebSocket的服务端是相当于一个映射路径。然后和SpringBoot整合,在配置类里配置ServerEndpointExporter对象。把映射路径托管给Spring来处理。这样Spring就不会拦截这个请求。

效果图我就不放上来了,代码copy一下,就可以运行。不过博主还是建议,代码自己写一遍,毕竟咱们是学习技术的嘛。哈哈

如果有什么问题,可以在下方留言。我看到会一一回复。

猜你喜欢

转载自blog.csdn.net/xkfanhua/article/details/89058149