短轮询、comet、websocket与spring整合代码例子

短轮询

	function showTime(){
        $.get("showTime",function (data) {
            console.log(data);
            $("#serverTime").html(data);
        })
    }

    setInterval(showTime, 1000);

服务器推送技术-Comet

基于 AJAX 的长轮询(long-polling)方式,Spring带来的DeferedResult
/**
 * @author yun
 * 类说明:
 */
@Controller
@RequestMapping(produces="text/html;charset=UTF-8")
/*记得要在WebInitializer中增加servlet.setAsyncSupported(true);*/
public class PushNewsController {

    private ExecutorService executorService
            = Executors.newFixedThreadPool(1);

    @RequestMapping("/pushnews")
    public String news(){
        return "pushNews";
    }

    @RequestMapping(value="/realTimeNews")
    @ResponseBody
    /*在WebInitializer中要加上servlet.setAsyncSupported(true);*/
    public DeferredResult<String> realtimeNews(HttpServletRequest request){
        final DeferredResult<String> dr = new DeferredResult<String>();

        executorService.submit(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                int index = new Random().nextInt(Const.NEWS.length);
                dr.setResult(Const.NEWS[index]);
            }
        });
        return dr;
    }

}

启用异步:

/**
 * @author yun
 * 类说明:
 */
public class WebInit implements WebApplicationInitializer {

    public void onStartup(ServletContext servletContext)
            throws ServletException {

        AnnotationConfigWebApplicationContext ctx
                = new AnnotationConfigWebApplicationContext();
        ctx.register(CometMvcConfig.class);
        ctx.setServletContext(servletContext);
        
        ServletRegistration.Dynamic servlet = servletContext.addServlet("dispatcher",
                new DispatcherServlet(ctx));
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
        servlet.setAsyncSupported(true);//启用异步

    }
}

js调用:

	longLoop();

    function longLoop() {
        $.get("realTimeNews",function (data) {
            console.log(data);
            $("#realTimeNews").html(data);
            longLoop();//马上再发起请求
        })
    }
基于长轮询的服务器推模型Server-sent-events(SSE)

js代码:

<script type="text/javascript" src="assets/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript">

    function showPrice(index,data){
        $("#c"+index).html("当前价格:"+data);
        var s = $("#s"+index).html();
        $("#s"+index).html(s+data+" ");
    }

    if(!!window.EventSource){//判断浏览器支持度
        //拿到sse的对象
        var source = new EventSource('needPrice');
        //接收到服务器的消息
        source.onmessage=function (e) {
            var dataObj=e.data;
            var arr = dataObj.split(',');
            $.each(arr, function (i, item) {
                showPrice(i,item);
                });
            $("#hint").html("");
        };

        source.onopen=function (e) {
            console.log("Connecting server!");
        };

        source.onerror=function () {
            console.log("error");
        };

    }else{
        $("#hint").html("您的浏览器不支持SSE!");
    }


</script>

服务端代码:

/**
 * @author yun
 * 类说明:
 */
@Controller
public class NobleMetalController {

    private static Logger logger = LoggerFactory.getLogger(NobleMetalController.class);

    @RequestMapping("/nobleMetal")
    public String stock(){
        return "nobleMetal";
    }

    @RequestMapping(value="needPrice")
    @ResponseBody
    public void push(HttpServletResponse response){
    	response.setContentType("text/event-stream");
    	response.setCharacterEncoding("utf-8");
        Random r = new Random();
        int sendCount =0; /*服务器数据发送次数*/
        try {
			PrintWriter pw = response.getWriter();
			while(true) {
				if(pw.checkError()) {
					System.out.println("客户端断开连接");
					return;
				}
				Thread.sleep(1000);
				//字符串拼接
			    StringBuilder sb = new StringBuilder("");
			    sb.append("retry:2000\n")
			            .append("data:")
			            .append((r.nextInt(1000)+50)+",")
			            .append((r.nextInt(800)+100)+",")
			            .append((r.nextInt(2000)+150)+",")
			            .append((r.nextInt(1500)+100)+",")
			            .append("\n\n");
			    pw.write(sb.toString());
			    pw.flush();
			    sendCount++;
			    if(sendCount >= 100) {
			    	return;
			    }
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

    }

}

spring为sse提供了支持,代码示例如下:

/**
 * @author yun
 * 类说明:
 */
@Controller
public class SseController {
    private static Logger logger = LoggerFactory.getLogger(SseController.class);

    private static Map<String,SseEmitter> sseEmitters
            = new ConcurrentHashMap<>();
    private ExecutorService executorService
            = Executors.newFixedThreadPool(2);

    @RequestMapping("/weChatPay")
    public String stock(){
        return "weChatPay";
    }

    @RequestMapping(value="/payMoney")
    @ResponseBody
    public SseEmitter pay(String weCharId){
        SseEmitter emitter = new SseEmitter();
        sseEmitters.put(weCharId,emitter);
        executorService.submit(new Pay(weCharId) );
        return emitter;
    }

    private static class Pay implements Runnable{

        private String weCharId;

        public Pay(String weCharId) {
            this.weCharId = weCharId;
        }

        @Override
        public void run() {
            SseEmitter sseEmitter = sseEmitters.get(weCharId);
            try {
                logger.info("联系支付服务,准备扣款");
                Thread.sleep(500);
                sseEmitter.send("支付完成");
                logger.info("准备通知自动售货机");
                Thread.sleep(1500);//售货机的动作
                sseEmitter.send("已通知自动售货机C9出货,请勿走开!");
                sseEmitter.complete();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

js代码:

<script type="text/javascript" src="assets/js/jquery-1.9.1.min.js"></script>
<script type="text/javascript">
    function send(){
        $("#payHint").html("正在处理中........");
        $.ajax({
            type: 'get',
            url:'payMoney?weCharId=1234567',
            dataType:'text',
            success:function(e){
                console.log(e);
                var arr = e.split('data:');
                var hint = '';
                $.each(arr, function (i, item) {
                    hint = hint+item+'<br>';
                });
                $("#payHint").html(hint);
            },
            error:function(data){
                $("#payHint").html(data);
            }

        });
    }


</script>

websocket

WebSocket通信-STOMP

html引入的js脚本:

<script th:src="@{sockjs.min.js}"></script>
<script th:src="@{stomp.min.js}"></script>
<script th:src="@{jquery.js}"></script>
<script th:src="@{wechat_room.js}"></script>

wechat_room.js:

var stompClient = null;

//加载完浏览器后调用connect(),打开通道
$(function(){
    //打开双通道
    connect()
})

//强制关闭浏览器时调用websocket.close(),进行正常关闭
window.onunload = function() {
    disconnect()
}

//打开通道
function connect(){
    //连接SockJS的endpoint名称为"endpointMark"
    var socket = new SockJS('/endpointMark');
    stompClient = Stomp.over(socket);//使用STMOP子协议的WebSocket客户端
    stompClient.connect({},function(frame){//连接WebSocket服务端

        console.log('Connected:' + frame);
        //广播接收信息
        stompTopic();

    });
}

//关闭通道
function disconnect(){
    if(stompClient != null) {
        stompClient.disconnect();
    }
    console.log("Disconnected");
}

//一对多,发起订阅
function stompTopic(){
    //通过stompClient.subscribe订阅目标(destination)发送的消息(广播接收信息)
    stompClient.subscribe('/mass/getResponse',function(response){
        var message=JSON.parse(response.body);
        //展示广播的接收的内容接收
        var response = $("#mass_div");
        var userName=$("#selectName").val();
        if(userName==message.name){
            response.append("<div class='user-group'>" +
                "          <div class='user-msg'>" +
                "                <span class='user-reply'>"+message.chatValue+"</span>" +
                "                <i class='triangle-user'></i>" +
                "          </div>" +userName+
                "     </div>");
        }else{
            response.append("     <div class='admin-group'>"+
                message.name+
                "<div class='admin-msg'>"+
                "    <i class='triangle-admin'></i>"+
                "    <span class='admin-reply'>"+message.chatValue+"</span>"+
                "</div>"+
                "</div>");
        }
    });
}

//群发消息
function sendMassMessage(){
    var postValue={};
    var chatValue=$("#sendChatValue");
    var userName=$("#selectName").val();
    postValue.name=userName;
    postValue.chatValue=chatValue.val();
    //postValue.userId="0";
    if(userName==1||userName==null){
        alert("请选择你是谁!");
        return;
    }
    if(chatValue==""||userName==null){
        alert("不能发送空消息!");
        return;
    }
    stompClient.send("/massRequest",{},JSON.stringify(postValue));
    chatValue.val("");
}

//单独发消息
function sendAloneMessage(){
    var postValue={};
    var chatValue=$("#sendChatValue2");
    var userName=$("#selectName").val();
    var sendToId=$("#selectName2").val();
    var response = $("#alone_div");
    postValue.name=userName;//发送者姓名
    postValue.chatValue=chatValue.val();//聊天内容
    postValue.userId=sendToId;//发送给谁
    if(userName==1||userName==null){
        alert("请选择你是谁!");
        return;
    }
    if(sendToId==1||sendToId==null){
        alert("请选择你要发给谁!");
        return;
    }
    if(chatValue==""||userName==null){
        alert("不能发送空消息!");
        return;
    }
    stompClient.send("/aloneRequest",{},JSON.stringify(postValue));
    response.append("<div class='user-group'>" +
        "          <div class='user-msg'>" +
        "                <span class='user-reply'>"+chatValue.val()+"</span>" +
        "                <i class='triangle-user'></i>" +
        "          </div>" +userName+
        "     </div>");
    chatValue.val("");
}

//一对一,发起订阅
function stompQueue(){

    var userId=$("#selectName").val();
    alert("监听:"+userId)
    //通过stompClient.subscribe订阅目标(destination)发送的消息(队列接收信息)
    stompClient.subscribe('/queue/' + userId + '/alone',
        function(response){
        var message=JSON.parse(response.body);
        //展示一对一的接收的内容接收
        var response = $("#alone_div");
        response.append("     <div class='admin-group'>"+
            message.name+
            "<div class='admin-msg'>"+
            "    <i class='triangle-admin'></i>"+
            "    <span class='admin-reply'>"+message.chatValue+"</span>"+
            "</div>"+
            "</div>");
    });
}

后台服务器代码:
配置类

/**
 * @author yun
 * 类说明:
 */
@Configuration
/*开启使用Stomp协议来传输基于消息broker的消息
这时控制器支持使用@MessageMapping,就像使用@RequestMapping一样*/
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

	@Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        /*注册STOMP协议的节点(endpoint),并映射指定的url,
        * 添加一个访问端点“/endpointMark”,客户端打开双通道时需要的url,
        * 允许所有的域名跨域访问,指定使用SockJS协议。*/
        registry.addEndpoint("/endpointMark")
                .setAllowedOrigins("*")
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        /*配置一个消息代理
        * mass 负责群聊
        * queue 单聊*/
        registry.enableSimpleBroker(
                "/mass","/queue");

        //一对一的用户,请求发到/queue
        registry.setUserDestinationPrefix("/queue");
    }

}

controller类

/**
 * @author yun
 * 类说明:
 */
@Controller
public class StompController {

    @Autowired
    private SimpMessagingTemplate template;/*Spring实现的一个发送模板类*/

    /*消息群发,接受发送至自massRequest的请求*/
    @MessageMapping("/massRequest")
    @SendTo("/mass/getResponse")
    //SendTo 发送至 Broker 下的指定订阅路径mass ,
    // Broker再根据getResponse发送消息到订阅了/mass/getResponse的用户处
    public ChatRoomResponse mass(ChatRoomRequest chatRoomRequest){
        System.out.println("name = " + chatRoomRequest.getName()
                +" chatValue = " + chatRoomRequest.getChatValue());
        ChatRoomResponse response=new ChatRoomResponse();
        response.setName(chatRoomRequest.getName());
        response.setChatValue(chatRoomRequest.getChatValue());
        //this.template.convertAndSend();
        return response;
    }

    /*单独聊天,接受发送至自aloneRequest的请求*/
    @MessageMapping("/aloneRequest")
    //@SendToUser
    public ChatRoomResponse alone(ChatRoomRequest chatRoomRequest){
        System.out.println("SendToUser = " + chatRoomRequest.getUserId()
                +" FromName = " + chatRoomRequest.getName()
                +" ChatValue = " + chatRoomRequest.getChatValue());
        ChatRoomResponse response=new ChatRoomResponse();
        response.setName(chatRoomRequest.getName());
        response.setChatValue(chatRoomRequest.getChatValue());
        //会发送到订阅了 /user/{用户的id}/alone 的用户处
        //  {userid}/alone ==> /queue/{userid}/alone
        this.template.convertAndSendToUser(chatRoomRequest.getUserId()
                +"","/alone",response);
        return response;
    }
}

请求实体类:

/**
 * @author yun
 * 类说明:聊天室的请求实体
 */
public class ChatRoomRequest {
    private String name;
    private String chatValue;
    private String userId;

    public String getUserId() {
        return userId;
    }

    public void setUserId(String userId) {
        this.userId = userId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getChatValue() {
        return chatValue;
    }

    public void setChatValue(String chatValue) {
        this.chatValue = chatValue;
    }
}

响应实体类:

/**
 * @author yun
 * 类说明:聊天室的应答实体
 */
public class ChatRoomResponse {
    private String name;
    private String chatValue;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getChatValue() {
        return chatValue;
    }

    public void setChatValue(String chatValue) {
        this.chatValue = chatValue;
    }
}

websocket与spring整合

<script type="text/javascript">
    var socket;
    if (typeof (WebSocket) == "undefined") {
        console.log("遗憾:您的浏览器不支持WebSocket");
    } else {
        console.log("恭喜:您的浏览器支持WebSocket");

        //实现化WebSocket对象
        //指定要连接的服务器地址与端口建立连接
        //注意ws、wss使用不同的端口。我使用自签名的证书测试,
        //无法使用wss,浏览器打开WebSocket时报错
        //ws对应http、wss对应https。
        socket = new WebSocket("ws://localhost:8080/ws/asset");
        //连接打开事件
        socket.onopen = function() {
            console.log("Socket 已打开");
            socket.send("消息发送测试(From Client)");
        };
        //收到消息事件
        socket.onmessage = function(msg) {
            console.log(msg);
            //console.log(msg.data);
        };
        //连接关闭事件
        socket.onclose = function() {
            console.log("Socket已关闭");
        };
        //发生了错误事件
        socket.onerror = function() {
            alert("Socket发生了错误");
        }

        //窗口关闭时,关闭连接
        window.unload=function() {
            socket.close();
        };
    }
</script>

服务端代码:

/**
 *@author yun
 *类说明:提供WebSocket服务
 */
@ServerEndpoint(value = "/ws/asset")
@Component
public class WebSocketServer {

    private static Logger log = LoggerFactory.getLogger(WebSocketServer.class);
    private static final AtomicInteger OnlineCount = new AtomicInteger(0);
    // concurrent包的线程安全Set,用来存放每个客户端对应的Session对象。
    private static CopyOnWriteArraySet<Session> SessionSet = new CopyOnWriteArraySet<Session>();


    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        SessionSet.add(session);
        int cnt = OnlineCount.incrementAndGet(); // 在线数加1
        log.info("有连接加入,当前连接数为:{}", cnt);
        SendMessage(session, "连接成功");
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        SessionSet.remove(session);
        int cnt = OnlineCount.decrementAndGet();
        log.info("有连接关闭,当前连接数为:{}", cnt);
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("来自客户端的消息:{}",message);
        SendMessage(session, "收到消息,消息内容:"+message);

    }

    /**
     * 出现错误
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误:{},Session ID: {}",error.getMessage(),session.getId());
        error.printStackTrace();
    }

    /**
     * 发送消息,实践表明,每次浏览器刷新,session会发生变化。
     * @param session
     * @param message
     */
    public static void SendMessage(Session session, String message) {
        try {
            session.getBasicRemote().sendText(String.format("%s (From Server,Session ID=%s)",message,session.getId()));
        } catch (IOException e) {
            log.error("发送消息出错:{}", e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 群发消息
     * @param message
     * @throws IOException
     */
    public static void BroadCastInfo(String message) throws IOException {
        for (Session session : SessionSet) {
            if(session.isOpen()){
                SendMessage(session, message);
            }
        }
    }

    /**
     * 指定Session发送消息
     * @param sessionId
     * @param message
     * @throws IOException
     */
    public static void SendMessage(String sessionId,String message) throws IOException {
        Session session = null;
        for (Session s : SessionSet) {
            if(s.getId().equals(sessionId)){
                session = s;
                break;
            }
        }
        if(session!=null){
            SendMessage(session, message);
        }
        else{
            log.warn("没有找到你指定ID的会话:{}",sessionId);
        }
    }

}

开启websocket配置:

/**
 * @author yun
 * 类说明:开启WebSocket支持
 */
@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

业务controller:

/**
 *@author yun
 *类说明: WebSocket服务器端推送消息示例Controller
 */
@RestController
@RequestMapping("/api/ws")
public class WsController {

	/**
	 * 群发消息内容
	 * @param message
	 * @return
	 */
	@RequestMapping(value="/sendAll", method=RequestMethod.GET)
	String sendAllMessage(@RequestParam(required=true) String message){
		try {
			WebSocketServer.BroadCastInfo(message);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "ok";
	}

	/**
	 * 指定会话ID发消息
	 * @param message 消息内容
	 * @param id 连接会话ID
	 * @return
	 */
	@RequestMapping(value="/sendOne", method=RequestMethod.GET)
	String sendOneMessage(@RequestParam(required=true) String message,@RequestParam(required=true) String id){
		try {
			WebSocketServer.SendMessage(id,message);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "ok";
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值