简单的说一下什么是websocket,它是基于TCP的服务器与客户端之间全双工通信的一种协议,允许服务端主动推送消息给客户端,只需要一次握手,就可以在服务端和客户端之间建立持久相连接,在这里我们只用到它的即时性实现一对一或多对多聊天。更多关于websocket的内容可以自行查阅。
总体流程是先做一个登录,用户名就是你的ID,别人可以通过你的用户名给你发消息,你知道了别人的用户名也可以给他发消息,前提是两人都在线,若对方不在线则把你发送的消息存到数据库,这里用的MySQL,当对方上线之后则从数据库里提取你发送的消息发给对方。开发环境是eclipse IDE,MySQL数据库,Tomcat服务器(8.0以上版本)。这些软件的安装就不多说了,网上有很多教程。
Tomcat和eclipse
Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,是开发和调试JSP 程序的首选。
eclipse相信都不陌生,很厉害的一个IDE,进入官网下载安装。
Tomcat最好选择8.0以上版本,下载和安装网上也有很多教程,不再赘述。
在eclipse里配置Tomcat,点击Window–>preference–>server–>Runtime Environments–>add
MySQL
MySQL是一种关系型数据库管理系统,关系数据库将数据保存在不同的表中,而不是将所有数据放在一个大仓库内,这样就增加了速度并提高了灵活性。
这里附上一些主要MySQL操作指令:
创建数据库:CREATE DATABASE 数据库名;
使用数据库:USE 数据库名;
创建table:create table <表名> ( <字段名1> <类型1> [,…<字段名n> <类型n>]);
例如,建立一个名为MyClass的表,
字段名 | 数据类型 | 数据宽度 | 是否为空 | 是否主键 | 自动增加 | 默认值 |
---|---|---|---|---|---|---|
id | int | 4 | 否 | primary key | auto_increment | |
name | char | 20 | 否 |
create table MyClass(
id int(4) not null primary key auto_increment,
name char(20) not null);
查看table所有信息:select * from table名;
删除table:drop table table名;
根据字段名删除表中某个数据:delete from MyClass where id=1;
客户端代码
注意:以下代码只是对websocket的实现描述,并没有对前端的页面设计。我自己之前写了一个简单的网站挂在了服务器上,有兴趣的可以可以初步体验一下。https://yuewuge.club ,进入后点击“聊聊”注册登录。
var websocket = null;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://服务端地址");
}else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("连接服务器失败");
};
//连接成功建立的回调方法
websocket.onopen = function () {
send("time");
//setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
//在这里可以对接收到的消息进行处理。
}
//连接关闭的回调方法
websocket.onclose = function () {
//setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send(msg) {
var message = msg;
websocket.send(message);
}
Android手机客户端也可以使用websocket,这里不再多说,有需要可以私信。
服务端代码
注意:以下服务端代码主要都是我对接收到的数据用分页符隔开处理然后下发到客户端,客户端再根据分页符分割数据获得所需要的数据,你只需知道四个方法:
客户端与服务端建立连接
public void onOpen(Session session, @PathParam(value = "sname") String sname, EndpointConfig config)
throws IOException {
}
连接关闭
public void onClose() {
}
接收到客户端的消息
public void onMessage(String message, Session session) throws IOException {
}
群发消息
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message + "\t");
@ServerEndpoint("/websocket/{sname}")
public class Test {
// 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
// 若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static CopyOnWriteArraySet<Test> webSocketSet = new CopyOnWriteArraySet<Test>();
// concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static ConcurrentHashMap<String, Test> webSocketSet1 = new ConcurrentHashMap<String, Test>();
// 与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
public Session getSession() {
return this.session;
}
/**
* 接收sname
*/
private String sname = "";
public static HashSet<String> al = new HashSet<String>();
/**
* 连接建立成功调用的方法
*
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
* @throws IOException
*/
@OnOpen
public void onOpen(Session session, @PathParam(value = "sname") String sname, EndpointConfig config)
throws IOException {
this.session = session;
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在线数加1
this.sname = sname;//当前用户名
al.add(sname);//加入在线集合
webSocketSet1.put(sname, this); // 加入set中
for (Test item : webSocketSet) {//发给所有人当前在线人数
try {
item.sendMessage("\t" + getOnlineCount());
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
//在数据库中搜索是否有未接收消息,若有则添加到list
String sql = "SELECT * FROM message WHERE toname ='" + sname + "'";
List<Msg> list = new ArrayList<Msg>();
ResultSet rs = DbcpPool.executeQuery(sql);
try {
while (rs.next()) {
Msg msg = new Msg();
msg.setName(rs.getString("name"));
msg.setToname(rs.getString("toname"));
msg.setMsg(rs.getString("msg"));
msg.setTime(rs.getString("time"));
msg.setDes(rs.getString("des"));
list.add(msg);
}
DbcpPool.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (!list.isEmpty()) {
Iterator<Msg> it = list.iterator();
while (it.hasNext()) {
Msg s = (Msg) it.next();
if (s.getDes().equals("text")) {
this.session.getBasicRemote()
.sendText("\t\t" + s.getTime() + "-" + s.getName() + ": " + s.getMsg());
} else if (s.getDes().equals("pic")) {
this.session.getBasicRemote()
.sendText(s.getTime() + "-" + s.getName() + ": " + "\t\t" + s.getMsg());
}
}
String sql1 = "DELETE FROM message WHERE toname = '" + sname + "'";
DbcpPool.executeUpdate(sql1);
DbcpPool.close();
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this);// 从set中删除
al.remove(sname);
subOnlineCount(); // 在线数减1
if (!sname.equals("")) {
webSocketSet1.remove(sname); // 从set中删除
}
for (Test item : webSocketSet) {
try {
item.sendMessage("\t" + getOnlineCount());
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
* @param session 可选的参数
* @throws IOException
*/
@OnMessage
public void onMessage(String message, Session session) throws IOException {
// System.out.println("来自客户端的消息:" + message);
// 群发消息
String m = message;
if (m.equals("time")) {//用户一登录就发送当前时间
Date t = new Date();
SimpleDateFormat df = new SimpleDateFormat("HH:mm");
String date = df.format(t).toString();
sendMessage("\t\t\t" + date);
} else {
try {
if (m.split("[|]")[1] == "" || message.split("[|]")[1] == null) {//发给个人
sendall(message.split("[|]")[0]);
} else {//群发
sendInfo(message);
}
} catch (ArrayIndexOutOfBoundsException e) {
sendall(message.split("[|]")[0]);
}
}
}
/**
* 群发自定义消息
*/
public void sendInfo(String message) {
// 这里可以设定只推送给这个sid的,为null则全部推送
String sendUsername = message.split("[|]")[1];
String sendMessage = message.split("[|]")[0];
Date t = new Date();
SimpleDateFormat df = new SimpleDateFormat("MM-dd HH:mm");
SimpleDateFormat df1 = new SimpleDateFormat("HH:mm");
String date = df.format(t).toString();
String date1 = df1.format(t).toString();
try {//用户在线
if (webSocketSet1.get(sendUsername) != null) {
webSocketSet1.get(sendUsername).sendMessage("\t\t" + date1 + "-" + sname + ": " + sendMessage);
} else {//用户不在线,把数据保存到数据库
String sql = "INSERT INTO message (name,toname,msg,time,des) " + "VALUES (?,?,?,?,?)";
PreparedStatement ps = DbcpPool.executePreparedStatement(sql);
try {
ps.setString(1, sname);
ps.setString(2, sendUsername);
ps.setString(3, sendMessage);
ps.setString(4, date);
ps.setString(5, "text");
ps.executeUpdate();
DbcpPool.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendall(String message) throws IOException {
Map<String, Test> map = webSocketSet1;
for (String key : map.keySet()) {
if (!key.equals(sname)) {
map.get(key).sendMessage(sname + ": " + message);
}
}
}
public void sendpic(String message, String name, String sendname) throws IOException {
Date t = new Date();
SimpleDateFormat df = new SimpleDateFormat("MM-dd HH:mm");
SimpleDateFormat df1 = new SimpleDateFormat("HH:mm");
String date = df.format(t).toString();
String date1 = df1.format(t).toString();
try {
if (webSocketSet1.get(sendname) != null) {
webSocketSet1.get(sendname).sendMessage(date1 + "-" + name + "\t\t" + message);
System.out.println(date1 + "-" + name + "\t\t" + message);
} else {
String sql = "INSERT INTO message (name,toname,msg,time,des) " + "VALUES (?,?,?,?,?)";
PreparedStatement ps = DbcpPool.executePreparedStatement(sql);
try {
ps.setString(1, name);
ps.setString(2, sendname);
ps.setString(3, message);
ps.setString(4, date);
ps.setString(5, "pic");
ps.executeUpdate();
DbcpPool.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void sendallpic(String message, String name) throws IOException {
Map<String, Test> map = webSocketSet1;
for (String key : map.keySet()) {
if (!key.equals(name)) {
map.get(key).sendMessage(name + ":\t\t\t\t" + message);
}
}
}
/**
* 发生错误时调用
*
* @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 + "\t");
// this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
Test.onlineCount++;
}
public static synchronized void subOnlineCount() {
Test.onlineCount--;
}
}
最后
其实websocket非常简单易用,做的工作更多的还是对网页的设计,这就涉及到html了,不是本次讨论的内容,我做这个聊天室也是偶然,之前目的是做一个网站挂到服务器上,然后突发奇想弄了一个即时聊天的功能。更多的还是对前端的开发,我上面提到的这个网站(https://yuewuge.club)主要用了layui的组件,很好用。代码都是零零碎碎的,实现基本功能的代码很简单,主要是其他的界面设计,这里就不再往上贴了,有需要的可以私聊我,我也是小白一个自己做的网站也是很简易没有花里胡哨的界面。本次分享到此结束。