Java、WebSocket、HTML简易聊天室

来源:互联网 发布:c语言字符回车结束 编辑:程序博客网 时间:2024/05/20 21:23
WebSockt协议是一种双向通信的解决方案,减少了频繁建立连接所带来的开销,使得客户端和服务端的通信更加及时。与轮询(polling)和(Comet)技术相比,WebSockt在双端通信上有明显的优势。本文以Java、Tomcat、JavaScript实现双端通信实例。
        本文所使用开发环境:Java 1.8.0_45、Tomcat 7.0.53、IDE IntelliJ IDEA 2016.3.1。开发环境搭建与项目配置方法不在本文讨论范围之内。
        在进行项目开发之前,首先需要弄清楚一些基本概念,首先是WebSocket中的数据传输存在的数据有两种形式:控制帧(control frame)和数据帧(data frame)。

控制帧又分为:关闭帧(close frame) 和ping-pong帧(ping and pong frames)
  • close frame:用于关闭连接
  • ping and pong frames:用于检测连接情况(是协议自带的心跳检测)

数据帧又主要有文本帧(textual frame)、字节帧(binary frame)
  • textual frame:接收客户端以文本形式传递的数据
  • binary frame:接收客户端以二进制形式传递的数据 

        控制帧一般是协议自己控制和实现的操作,我们在开发中并不需要自己实现。数据帧中的文本帧是最常用的,一般可以传递格式化的字符串数据,如json、xml等。字节帧接收的数据一般经过压缩处理,如gzip、prorobuf。其实数据帧除了这两种常见类型以外,还有一种partial frame,一般用于传输大量数据,例如图片和视频。

      在开发过程中,我们需要了解在WebSockt的生命周期中存在四种主要的事件:
  • The open event:建立连接事件
  • The message event:接收消息事件
  • The error event:连接或终端出错事件
  • The close event:关闭连接事件

了解了WebSocket的基本的概念以后,接着来看一下Java WebSocket的API,首先是EndPoint类的源码:
public abstract class Endpoint {
public Endpoint() {
}

public abstract void onOpen(Session var1, EndpointConfig var2);

public void onClose(Session session, CloseReason closeReason) {
}

public void onError(Session session, Throwable throwable) {
}
}
紧接着在看一下继承EndPoint类实现终端接口的ProgrammaticEchoServer类代码:
public class ProgrammaticEchoServer extends Endpoint {

@Override
public void onOpen(Session session, EndpointConfig endpointConfig) {
Session mySession = session;
mySession.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String text) {
try {
mySession.getBasicRemote().sendText("I got (" + text + ")" + "so I am send the message back.anna");
} catch (IOException e) {
System.out.println("oh dear, something was wrong send the message back:" + e.getMessage());
}
}
});
}

@Override
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
}

@Override
public void onError(Session session, Throwable throwable) {
super.onError(session, throwable);
}
}
        注意:EndPoint类是没有定义message event事件的处理的,此处的message event事件响应是在onOpen方法中给Session对象添加addMessageHandler方法添加的。在实现的onMessge中,只是将接收到的字符串加工一下就直接返回给另一个终端了。

        接下来我们看一下另一个终端,HTML端JavaScript部分的相关代码:
function initConnect() {
//var wsUri = "ws://localhost:8080/programmaticEcho";
var wsUri = "ws://localhost:8080/echo";
writeToScreen("Connecting to " + wsUri);
echo_websocket = new WebSocket(wsUri);
echo_websocket.onopen = function (evt) {
writeToScreen("Connected !");
};

echo_websocket.onmessage = function (evt) {
writeToScreen("Received message: " + evt.data);
};

echo_websocket.onerror = function (evt) {
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
};
}

function send_echo() {
doSend(textID.value);
}

function doSend(message) {
echo_websocket.send(message);
writeToScreen("Sent message: " + message);
}
        首先建立WebSocket连接,建立连接以后客户端可以向服务终端发送字符串,服务端接收到以后,加工一下,直接返回,在将加工后的字符串显示在浏览器窗口。本页面的运行效果如下:
        本实例只是单纯地收发消息并显示咋在浏览器窗口上,实现了双端的收发,和及时通信的骨架代码。接下来我们看一下本实例中的聊天室实例。
        
        聊天室代码的Java终端是使用注解实现的,具体代码如下:
@ServerEndpoint("/chatroom")
public class ChatRoom {

private Session session;
private List<String> names = new ArrayList<>();
private String username;

@OnOpen
public void open(Session session) {
this.username = "用户-->" + session.getId();
this.session = session;
SocketConfig.getInstance().add(this);
names.add(username);
broadCast(username + " 进入聊天室!!");
}

@OnMessage
public void receive(Session session, String msg) {
broadCast(username + "" + new Date().toString() + msg);
}

@OnClose
public void close(Session session) {
SocketConfig.getInstance().remove(this);
names.remove(username);
broadCast(username + " 退出聊天室!!");
}

public void broadCast(String msg) {
Set<ChatRoom> sockets = SocketConfig.getInstance().getSockets();
System.out.println("sockets-->" + sockets.size());
for (Iterator iterator = sockets.iterator(); iterator.hasNext(); ) {
try {
ChatRoom chatRoom = (ChatRoom) iterator.next();
chatRoom.session.getBasicRemote().sendText(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}

}
        注意:@OnServerEndPoint注解表示该类为实现了EndPoint的终端,后面的参数表示在发布的项目该终端的相对路径。@OnOpen注解在此处仅接受一个Session参数,但是添加了@OnOpen注解的方法最多可以有三个参数:Session session, EndPointConfig cfg, String parameters,而且这三个参数是可选无序的。后面的@OnMessage,@OnClose,@OnError注解也有这样的特性。

   OK~~~代码中接受到消息后,会将消息发给每一个消息给广播给每一个连接中的每一个终端。
    注意:每一个终端连接创建一个EndPoint对象,一个Session代表一次会话,因此需要在创建连接以后,将Session对象存储到一个全局单例的对象中,这就是最简易的连接池。
    聊天室的运行效果如下:

最后附上实例代码:https://github.com/BruceT2010/WS-Samples
0 0
原创粉丝点击