WebSocket本身指的就是在网页上进行Scoket连接,这样可以直接进行客户端与服务器端的通讯操作,WebSocket的出现是为了提升Ajax的处理性能,同时也可以得到良好的安全保证,最为常见的应用就是各个电商网站的在线咨询。
WebSocket简介
WebSocket是在HTML5一起推出的一个通讯技术,在HTNL里面本身是有一个Ajax2.0开发技术的,但是它又提供了一个更加高效的技术“WebkSocket”,如果想要充分的理解WebSocket设计的目的,那么首先就必须要充分的理解Ajax的设计问题
Ajax的产生是在HTTP协议基础之上完成的,其最大的特征是直接基于HTTP协议进行异步的请求处理,而所有HTTP请求里面最大的问题就在于,不会保留用户的状态,每一次都需要创建新的连接,这样对于服务器的性能是有严重损耗的,那么既然现在用户有可能性需要长期的使用通讯通道,最好的做法就是不关闭连接。
WebSocket协议和Http协议一样,都是在TCP协议的基础上进行系统构建,所有TCP的处理依然是现在操作的主流。但与HTTP相比,两者在通讯处理的时候有着不一样的操作机制。
- HTTP通讯:在每一次进行请求发送之后都会有响应的回应进行该请求处理
- WebSocket通讯:在进行通讯之前首先进行握手处理,确定当前已经可以获取一个有效的连接,随后才可以实现通讯。
对于WebSocket程序的开发,可以直接在当前的WEB服务器上使用,而且最为重要的是WebSocket客户端不一定非要在WEB容器中使用,只要有相应的JavaScript支持就可以
WebSocket基础实现
如果要想快速的进行WebSocket程序的开发,那么只要在拥有支持WebSocket容器上直接进行代码的编写即可,例如:Tomcat容器。
在项目中引入如下依赖
Gradle
providedCompile group: 'javax.websocket', name: 'javax.websocket-api', version: '1.1'
providedCompile group: 'javax.servlet', name: 'javax.servlet-api', version: '4.0.1'
providedCompile group: 'javax.servlet.jsp', name: 'javax.servlet.jsp-api', version: '2.3.3'
Maven
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.3</version>
<scope>provided</scope>
</dependency>
要想实现WebSocket的程序开发,只需要设置一个WebSocket程序处理类即可
package cn.srthe.websocket;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
/** @ServerEndpoint 该注解表示:运行Tomcat之后会自动使用此路径进行定义 */
@ServerEndpoint("/message")
public class EchoMessageServer {
/**
* 方法名称可随意
*
* @OnOpen 表示当前的方法作为连接创建时的操作
*/
@OnOpen
public void openChannel() {
System.err.println("********************* 【EchoMessageServer】建立WebSocket连接 *********************");
}
/**
* 进行消息通讯操作
*
* @OnMessage 进行消息处理
*/
@OnMessage
public void messageHandle(String message, Session session) {
System.err.println("********************* 【EchoMessageServer】接收到消息内容" + message);
try {
session.getBasicRemote().sendText("【ECHO】" + message + "{" + session.getId() + "}");
} catch (Exception e) {
e.printStackTrace();
}
}
/** @OnClose 关闭WebSocket连接 */
@OnClose
public void closeHandle() {
System.err.println("********************* 【EchoMessageServer】关闭WebSocket连接");
}
}
在使用WebSocket开发的时候,所有的操作方法都采用了注解的形式进行了动态的配置,这样的设计拥有极大的灵活性
将代码部署到Tomcat上,启动Tomcat即可。进行WebSocket通讯操作的时候实际上不要求WebSocket客户端代码必须与服务器端代码在一台服务器上,它可以随意的放在任何的位置,只要有相应的IP地址即可。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket通讯</title>
<script type="text/javaScript">
url = "ws://localhost/message"; //WebSocket服务器的连接地址
window.onload = function () {
webSocket = new WebSocket(url); //创建WebSocket的连接
webSocket.onopen = function (ev) {
document.getElementById("contentDiv").innerHTML += "<p>服务器连接成功,开始进行消息交互处理!</p>";
}
webSocket.onclose = function()
{
document.getElementById("contentDiv").innerHTML += "<p>消息交互完毕,关闭连接通道!</p>";
}
document.getElementById("send").addEventListener("click", function () {
inputMessage = document.getElementById("msg").value;//获取输入的消息内容
webSocket.send(inputMessage); //将输入的消息发送到WebSocket服务器端
webSocket.onmessage = function (obj) {
document.getElementById("contentDiv").innerHTML += "<p>" + obj.data + "</p>";
document.getElementById("msg").value = "";
}
}, false);
}
</script>
</head>
<body>
<div id="contentDiv" style="height:300px;overflow:scroll;background:lightblue"></div>
<div id="inputDiv">
请输入消息内容:<input type="text" name="msg" id="msg">
<button type="button" id="send">发送</button>
</div>
</body>
</html>
此时的程序只要不关闭客户端的连接通道,那么就可以进行持续的信息交流,当前的这种操作就适合于频繁的WEB输入与输出操作。
使用Netty开发WebSocket
服务器端
如果要进行WebSocket通讯实现,服务器端只需进行消息的回应处理即可,创建一个WebSocketServerHandler程序类
public class WebSocketServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("【WebSocketServerHandler】消息类型:"+msg.getClass());
//当前是否为文本数据
if (msg instanceof TextWebSocketFrame){
TextWebSocketFrame textWebSocketFrame = (TextWebSocketFrame) msg;
String echoMessage = "【ECHO】"+textWebSocketFrame.content().toString(CharsetUtil.UTF_8);
ctx.writeAndFlush(new TextWebSocketFrame(echoMessage));
}
}
}
创建一个WebSocketServer程序类,进行服务器运行环境的配置
public class WebSocketServer {
//服务器端主运行程序
public void run() throws Exception{
EventLoopGroup masterGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
System.out.println("************** 服务器已经运行,在"+ HttpServerInfo.PORT+"端口进行监听 **************");
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(masterGroup,workerGroup).channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpRequestDecoder());
ch.pipeline().addLast(new HttpObjectAggregator(10485760));
ch.pipeline().addLast(new ChunkedWriteHandler());
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/message"));
ch.pipeline().addLast(new WebSocketServerHandler());
}
});
serverBootstrap.option(ChannelOption.SO_BACKLOG,128);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture future = serverBootstrap.bind(HttpServerInfo.PORT).sync();
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
masterGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
编写服务器端的启动类StartWebSocketServerMain
public class StartWebSocketServerMain {
public static void main(String[] args) throws Exception {
new WebSocketServer().run();
}
}
客户端
当用户通过浏览器进行WebSocket的连接时,浏览器会帮助用户省略掉许多的处理细节,所有的WebSocket进行连接控制的时候都需要考虑到对于握手的处理,如果说现在不使用浏览器,而直接使用Netty来实现客户端,那么在这个实现之中就必须手工进行握手的处理
定义一个接收键盘输入的类:KeyboardInputData
public class KeyboardInputData {
private static final BufferedReader KEYBOARD_INPUT = new BufferedReader(new InputStreamReader(System.in));
private KeyboardInputData(){}
public static String getString(String prompt){
System.out.println(prompt);
String result = null;
boolean flag = true;
while (flag){
try {
result = KEYBOARD_INPUT.readLine();
if (result == null || "".equals(result)){
System.out.println("错误,输入放入内容不允许为空,请重新输入:");
}else {
flag = false;
}
}catch (Exception e){
e.printStackTrace();
}
}
return result;
}
}
创建一个WebSocketClientHandler类,负责进行所有的回调操作,通过键盘输入实现数据的交互处理。
public class WebSocketClientHandler extends ChannelInboundHandlerAdapter {
/**
* WebSocket握手处理类
*/
private WebSocketClientHandshaker handlershaker;
private ChannelPromise handshakerFuture;
/**
* 描述当前的关闭状态
*/
private boolean closeFlag = false;
/**
* 所有的握手处理操作都是在连接的位置上进行的,WebSocketClient连接
* @param handlershaker 握手对象
*/
public WebSocketClientHandler(WebSocketClientHandshaker handlershaker) {
this.handlershaker = handlershaker;
}
public ChannelPromise getHandshakerFuture(){
return this.handshakerFuture;
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当进行通道创建的时候需要进行握手的处理
//进行握手处理
this.handlershaker.handshake(ctx.channel());
}
/**
* 在进行通道注册的时候触发操作
*/
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
this.handshakerFuture = ctx.newPromise();
}
/**
* 消息读取完毕之后触发
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//当内容读取完毕之后应该考虑进行消息的发送处理
//当前的通道还未关闭
if (this.closeFlag == false){
String inputMesssage = KeyboardInputData.getString("请输入要发送的数据").trim();
if ("exit".equalsIgnoreCase(inputMesssage)){
//当前的操作结束
ctx.writeAndFlush(new CloseWebSocketFrame());
}else if ("ping".equalsIgnoreCase(inputMesssage)){
ctx.writeAndFlush(new PingWebSocketFrame());
}else {
TextWebSocketFrame text = new TextWebSocketFrame(inputMesssage);
ctx.writeAndFlush(text);
}
}
}
/**
* 进行通道信息读取
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//获取当前的操作通道
Channel channel = ctx.channel();
if (!this.handlershaker.isHandshakeComplete()){
//判断是否已经成功握手了
try {
//握手处理
this.handlershaker.finishHandshake(channel, (FullHttpResponse) msg);
System.err.println("【WebSocketClientHandler - channelRead()】成功进行了服务器的连接");
//握手完成
this.handshakerFuture.setSuccess();
}catch (Exception e){
System.out.println("【WebSocketClientHandler - channelRead()】服务连接失败。");
//握手失败
this.handshakerFuture.setFailure(e);
}
//后续的处理暂时不执行
return;
}
//如果执行后续的处理代码,则一定表示已经握手成功了,服务器开始进行响应处理了
if (!(msg instanceof HttpResponse)){
//回应的不是Response就表示有数据
//获取响应的数据内容
WebSocketFrame frame = (WebSocketFrame) msg;
if (frame instanceof TextWebSocketFrame){
//当前文本信息
TextWebSocketFrame text = (TextWebSocketFrame) frame;
System.out.println("【WebSocketClientHandler - channelRead()】接收到的文本数据:"+text.text());
}else if (frame instanceof PongWebSocketFrame){
PongWebSocketFrame pong = (PongWebSocketFrame) frame;
System.out.println("【WebSocketClientHandler - channelRead()】连接测试数据Pong....");
}else if (frame instanceof CloseWebSocketFrame){
//当前通道已经关闭
this.closeFlag = true;
//关闭当前的操作通道
channel.close();
}
}
}
}
定义WebSocketServer类
public class WebSocketServer {
//服务器端主运行程序
public void run() throws Exception{
EventLoopGroup masterGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
System.out.println("************** 服务器已经运行,在"+ HttpServerInfo.PORT+"端口进行监听 **************");
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(masterGroup,workerGroup).channel(NioServerSocketChannel.class);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpResponseEncoder());
ch.pipeline().addLast(new HttpRequestDecoder());
ch.pipeline().addLast(new HttpObjectAggregator(10485760));
ch.pipeline().addLast(new ChunkedWriteHandler());
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/message"));
ch.pipeline().addLast(new WebSocketServerHandler());
}
});
serverBootstrap.option(ChannelOption.SO_BACKLOG,128);
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE,true);
ChannelFuture future = serverBootstrap.bind(HttpServerInfo.PORT).sync();
future.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
masterGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
定义客户端启动程序类:StartWebSocketServerMain
public class StartWebSocketServerMain {
public static void main(String[] args) throws Exception {
new WebSocketServer().run();
}
}