WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。
———— 引自 百度百科
简介:传统的使用http协议的web应用在实时通信方面存在很大的局限性,由于服务器只能等客户端浏览器主动发出request才能响应一个response,使客户端总要向服务器发出请求才能得到想要的消息(也可以用长连接让客户端hold住这个请求等类似的方式,但是这些方法都会占用比较多的服务器资源),随着html5到来的websocket使web应用的能更方便的实现实时通信。websocket和http协议一样也是基于tcp/ip协议的应用层的协议,它只需在第一次和服务器端连接是借用http协议与服务器端握手,然后就从http协议切换成websocket协议(我们可以把这个过程看做是http升级成了webstocket),关于握手可以参照这篇博文:http://blog.csdn.net/edwingu/article/details/44040961,本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。
websocket有如此强大的功能,作为在web领域数一数二的java当然也提供了实现方法,JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持WebSocket,从7.0.47开始支持JSR-356,下面我们所要写的简易聊天室也是需要部署在Tomcat7.0.47以上的版本才能运行。
1.1 创建项目,在这里我用maven和springMVC搭建的,不用也一样源码会全部给出,下方附上项目项目源码下载
1.2 后台代码 User.java,这个没什么讲的
package com.chat.pojo;
/**
* 用户类
* @author chenxin
*
*/
public class User {
private Integer id;
private String name;
private String password;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "User [id=" + id + ", name=" + name + ", password=" + password
+ "]";
}
}
package com.chat.controller;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.chat.pojo.User;
/**
* 用户登录
* 主要是学习websocket,数据库和权限判断就不写了
* @author chenxin
*
*/
@Controller
@RequestMapping("/user")
public class UserController {
//分配user的id,需设计为线程安全的
private static int count=1;
//在线用户列表,需设计成线程安全的
private static List userList = new CopyOnWriteArrayList();
/**
* 跳转到登陆页面
* @return
*/
@RequestMapping("/tologin")
public String toregister(){
return "login";
}
/**
* 登陆
* @param user
* @param request
* @return
*/
@RequestMapping("/login")
public String login(User user,HttpServletRequest request){
//生成id
user.setId(count);
//id增长
UserController.increase();
//把用户存入session域中
request.getSession().setAttribute("user", user);
//把登陆用户传入用户列表中
userList.add(user);
return "index";
}
/**
* 得到在线人数及用户名
* @param request
* @return
*/
@RequestMapping("/getAll")
public @ResponseBody Collection getAllUser(HttpServletRequest request){
return UserController.userList;
}
/**
* 下线,当页面关闭时前台页面通过ajax调用该handler
* @return
*/
@RequestMapping("/downLine")
public void downLine(HttpServletRequest request){
//得到session中的user
User user = (User)request.getSession().getAttribute("user");
//遍历用户列表,删除自己
for(User item:userList){
if(user.getId()==item.getId())
userList.remove(item);
}
}
//用来id增长
private static synchronized void increase(){
UserController.count++;
}
}
package com.ssm.websocket;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import com.chat.pojo.User;
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
* 由于此类不是servlet类,要通过另一种方法才能的到HttpSession
* configurator 属性 通过GetHttpSessionConfigurator类得到httpsession
*/
@ServerEndpoint(value="/websocket" ,configurator=GetHttpSessionConfigurator.class)//得到httpSession
public class WebSocketChat {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//当前会话的httpession
private HttpSession httpSession;
/**
* 连接建立成功调用的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
* @param config 有
*/
@OnOpen
public void onOpen(Session session,EndpointConfig config){
//得到httpSession
this.httpSession = (HttpSession) config.getUserProperties()
.get(HttpSession.class.getName());
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
//获得消息发来时间
SimpleDateFormat sd = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sd.format(new Date());
System.out.println("来自客户端的消息:" + message);
//替换表情
message = replaceImage(message);
//得到当前用户名
User user = (User)this.httpSession.getAttribute("user");
String name = user.getName();
//群发消息
for(WebSocketChat item: webSocketSet){
try {
item.sendMessage(name+" "+time+""+message);
} catch (IOException e) {
e.printStackTrace(); continue;
}
}
}
/** * 发生错误时调用
* @param session
* @param error
* @OnError
*/
public void onError(Session session, Throwable error){
System.out.println("发生错误");
error.printStackTrace();
}
/** * 这个方法与上面几个方法不一样。没有用注解,是根据自己需要添加的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
public void sendMessage(String message,String userid) throws IOException{ }
//对count的加减获取同步
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketChat.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketChat.onlineCount--;
}
//替换表情
private String replaceImage(String message){
for(int i=1;i<11;i++){
if(message.contains("<:"+i+":>")){
message = message.replace("<:"+i+":>", "");
}
}
return message;
}
}
package com.ssm.websocket;
import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;
/*
* 获取HttpSession的配置类
* 只要在websocket类上@ServerEndpoint注解中加入configurator=GetHttpSessionConfigurator.class
* 再在 @OnOpen注解的方法里加入参EndpointConfig config
* 通过config得到httpSession
*/
public class GetHttpSessionConfigurator extends Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec,
HandshakeRequest request, HandshakeResponse response) {
// TODO Auto-generated method stub
HttpSession httpSession=(HttpSession) request.getHttpSession();
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
2. 前台页面及js
2.1 login.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
用户登录
用户登录
快速登陆,无需注册
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
聊天室
聊天室
当前在线用户
发送内容不能为空
2.3 chat.js 里面是前台发送websocket请求的核心代码
$(function () {
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/chat/websocket");
}
else {
alert('当前浏览器版本过低不支持websocket!!!')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
}
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
showMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
//同时退出聊天室(下线)
window.onbeforeunload = function () {
closeWebSocket();
downLine();
}
//将是否连接websocket消息显示在网页上
function setMessageInnerHTML(innerHTML) {
$("#message").append(innerHTML+"");
}
//将消息显示在页面上
function showMessageInnerHTML(innerHTML){
// document.getElementById('chat-dialog-con').innerHTML += innerHTML + '
';
$("#chat-dialog-con ul").append(""+innerHTML+" ");
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = $("#txtInput").val();
websocket.send(message);
}
//添加表情
for (var i = 1; i <= 10; i++) {
$("#chat-input-expr").html($("#chat-input-expr").html() + "");
//html和text方法不一样,前者可添加html标签和纯文本,后者只可添加纯文本。
}
//向消息中添加表情
$("#chat-input-expr img").click(function () {
$("#txtInput").val($("#txtInput").val() + "<:" + $(this).attr("ID") + ":>");
});
//6.发送消息判断
$("#btnSend").click(function () {
var sendMsg = $("#txtInput");
if (sendMsg.val() != "") {
send();
sendMsg.val("")
}
else {
alert("发送内容不能为空!");
sendMsg.focus();
return false;
}
});
//得到在线用户
getUser();
setInterval("getUser()",3000);
});
以上为实现聊天室功能的代码,还有一些css样式在下面项目源码中给出
项目源码:http://download.csdn.net/detail/aaa5438438/9589077
转载请注明出处:http://blog.csdn.net/aaa5438438