WebSocket簡單使用
一、WebSocket單獨使用
1.創建WebSocket
import java.util.Date;
import javax.websocket.server.ServerEndpoint;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;
@ServerEndpoint("/ws")
public class MyWebSocketHandler extends SimpleChannelInboundHandler<Object> {
private WebSocketServerHandshaker handshaker;
private static final String WEB_SOCKET_URL="ws://localhost:8080/start/websocket";
//客户端與服務器創建連接的時候調用
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
NettyConfig.group.add(ctx.channel());
System.out.println("客戶端與服務端里連接開啟。。");
}
//客户端與服務器斷開連接的時候調用
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
NettyConfig.group.remove(ctx.channel());
System.out.println("客戶端與服務端連接關閉");
}
//服務器接收客戶端信息發送過的數據結束之後調用
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
super.channelReadComplete(ctx);
}
//工程出現異常時調用
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
//服务端处理客户端websocket请求的核心方法
@Override
protected void messageReceived(ChannelHandlerContext context, Object msg) throws Exception {
//處理客戶端向服務器發起http握手請求業務
if(msg instanceof FullHttpRequest) {
handHttpRequest(context, (FullHttpRequest)msg);
}else if(msg instanceof WebSocketFrame) {//處理websocket連接業務
handWebsocketFrame(context, (WebSocketFrame)msg);
}
}
/**
* 處理客戶端與服務端之間的websocket業務
* @param context
* @param frame
*/
private void handWebsocketFrame(ChannelHandlerContext ctx,WebSocketFrame frame) {
//判斷是否是關閉websocket的指令
if(frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
}
//判斷是否是ping消息
if(frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
//判斷是否是二進制消息,如果是二進制消息,拋出異常
if(!(frame instanceof TextWebSocketFrame)) {
System.out.println("目前我們不支持二進制消息");
throw new RuntimeException("【"+this.getClass().getName()+"】不支持消息");
}
//返回應答消息
//獲取客戶端向服務器發送的消息
String request=((TextWebSocketFrame)frame).text();
System.out.println("服務器收到客戶端的消息=====>>>"+request);
TextWebSocketFrame tws=new TextWebSocketFrame(new Date().toString()+ctx.channel().id()+"=====>>>"+request);
//群發,服務器向每個鏈接上來的客戶端群發消息
NettyConfig.group.writeAndFlush(tws);
}
/**
* 處理客戶端向服務器發起http握手請求業務
* @param ctx
* @param req
*/
private void handHttpRequest(ChannelHandlerContext ctx,FullHttpRequest req) {
if(!req.decoderResult().isSuccess()||!("websocket".equals(req.headers().get("upgrad")))) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return;
}
WebSocketServerHandshakerFactory wsFactory=new WebSocketServerHandshakerFactory(WEB_SOCKET_URL, null, false);
handshaker=wsFactory.newHandshaker(req);
if(handshaker==null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
}else {
handshaker.handshake(ctx.channel(), req);
}
}
/**
* 服務端向客戶端相應消息
* @param ctx
* @param req
*/
private void sendHttpResponse(ChannelHandlerContext ctx,FullHttpRequest req,DefaultFullHttpResponse res) {
if(res.status().code()!=200) {
ByteBuf buf=Unpooled.copiedBuffer(res.status().toString(),CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
}
//服務端向客戶端發送數據
ChannelFuture f=ctx.channel().writeAndFlush(res);
if(res.status().code()!=200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
}
2.初始化鏈接時候的各個組件
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* 初始化鏈接時候的各個組件
*
*/
public class MyWebSocketChannelhandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel e) throws Exception {
e.pipeline().addLast("http-code", new HttpServerCodec());
e.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));
e.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
e.pipeline().addLast("handler", new MyWebSocketHandler());
}
}
3.頁面
<html>
<head>
<meta http-equiv="Content-Type" content="text/html";charset="UTF-8"/>
<title>WebSocket客戶端</title>
</head>
<boby>
<form onSubmit="return false;">
<input type="text" name="message" value=""/>
<br/><br/>
<input type="button" value="發送WebSocket請求消息" onclick="send(this.form.message.value)"/>
<hr color="red"/>
<h2>客戶端接收到服務端返回的應答消息</h2>
<textarea id="responseContent" style="width:1024px height:300px"></textarea>
</form>
</boby>
<script type="text/javascript">
var socket;
if(!window.WebSocket){
window.WebSocket=window.MozWebSocket;
}
if(window.WebSocket){
Socket=new WebSocket("ws://localhost:8080/api/ws");//此處ip為服務端
socket.onmessage=function(event){
var ta=document.getElementById('responseContent');
ta.value+=event.data+"\'r\n";
};
socket.onopen=function(event){
var ta=document.getElementById('responseContent');
ta.value="你當前的瀏覽器支出websocket,請進行後續操作\r\n";
};
socket.onclose=function(event){
var ta=document.getElementById('responseContent');
ta.value="WebSocket連接已經關閉\r\n";
};
}else{
alert("你的瀏覽器不支持WebSocket");
}
function send(message){
socket.send(message);
}
</script>
</html>
4.啟動類
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
@Controller
@RequestMapping("/start")
public class StartWebSocket {
@RequestMapping("/webSocket.do")
@ResponseBody
public void startWebSocket() {
EventLoopGroup bossGroup=new NioEventLoopGroup();
EventLoopGroup workGroup=new NioEventLoopGroup();
try {
ServerBootstrap b=new ServerBootstrap();
b.group(bossGroup,workGroup);
b.channel(NioServerSocketChannel.class);
b.childHandler(new MyWebSocketChannelhandler());
System.out.println("服務端開啟等待客戶端連接。。");
/*
* Channel ch=b.bind(8088).sync().channel(); ch.closeFuture().sync();
*/
}catch(Exception e) {
e.printStackTrace();
}finally {
//退出程序
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
如此就能前後端的簡單通訊。
二、WebSocket+tomcat+spring實現客戶端向服務端通信以及服務端主動向客戶端發消息
1.創建WebSocket服務端
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@ServerEndpoint("/ws/{uuid}")
@Component
public class WSServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。若要实现服务端与单一客户端通信的话,可以使用Map来存放,其中Key可以为用户标识
private static ConcurrentHashMap<Session, WSServer> ssMap= new ConcurrentHashMap<Session, WSServer>();
//與某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//客戶端用戶uuid
private String uuid="";
/**
* 连接建立成功调用的方法
* @param session 可选的参数。session为与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
@OnOpen
public void onOpen(Session session,@PathParam("uuid") String uuid){
this.session = session;
this.uuid=uuid;
ssMap.put(session, this);
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose(){
ssMap.remove(this.session);
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
* @param message 客户端发送过来的消息
* @param session 可选的参数
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
WSServer tmp = ssMap.get(session);
try {
tmp.sendMessage(message);
} catch (IOException e1) {
e1.printStackTrace();
}
}
/**
* 发生错误时调用
* @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);
}
/**
* 发送信息给指定ID用户,如果用户不在线则返回不在线信息给自己
* @param message
* @param id
* @throws IOException
*/
public void sendtoUser(String message,String uuid) throws IOException {
if (ssMap.get(uuid) != null) {
if(!uuid.equals(uuid)) {
ssMap.get(uuid).sendMessage("用户" + uuid + "发来消息:" + " <br/> " + message);
}else {
ssMap.get(uuid).sendMessage(message);
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WSServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WSServer.onlineCount--;
}
}
2.創建客戶端
<html>
<head>
<meta charset="UTF-8">
<title>Java后端WebSocket的Tomcat??</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">發送消息</button>
<hr/>
<button onclick="closeWebSocket()">關閉WebSocket連接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//判??前??器是否支持WebSocket
if ('WebSocket' in window) {
var uuid='2';
//websocket = new WebSocket("ws://localhost:8080/web1/websocket");
websocket = new WebSocket("ws://localhost:8080/api/ws/{uuid}");
}
else {
alert('當前瀏覽器 Not support websocket')
}
//連接發生錯誤的回調方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發生錯誤");
};
//連接成功建立的回調方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
}
//接收到消息的回調方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連接關閉的回調方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關閉");
}
//監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket聯接,防止連接還沒斷開就關閉窗口,server端??异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//將消息顯示在网頁上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
3.服務端向客戶端傳送消息的調用
import java.io.IOException;
import javax.annotation.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
/**
*
*通過長連接服務端主動發消息給客戶端
*/
@Controller
@RequestMapping("/test")
public class test {
@Resource
WSServer wsServer;
@RequestMapping("/send.do")
public void send(String uuid) {
//如果访问的地址中msg参数不为空值,发送msg的值给前端
try {
String msg="來了老弟";
wsServer.sendMessage(msg);
wsServer.onClose();
System.out.println(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
以上僅供參考