rest推送实现--jesey SSE

rest推送实现–jesey SSE

  服务器推送技术,是一种当服务器端的业务数据,资源状态发生改变的时候,服务器可以主动将这个信息通知给浏览器的通信技术。这里我们不讨论TCP/IP协议建立的连接,基于这种的Socket,一但连接建立,在这样的双向通信链路中随时都可以发送通知。如果我们自己在项目中实现一般还是使用第三方的,比如极光推送这个也是采用建立TCP长连接实现的。我们这里就主要介绍HTTP协议下面的推送。

有哪些推送技术?

1.Polling技术

  这里就只简单的说一下polling(轮询),是客户端周期性地访问服务器来获得数据。

  优点:客户端轮询技术易于实现。不需要为此在服务器或者浏览器端额外使用第三方库。

  缺点:客户端轮询每次请求都需要建立新的Http连接并在结束时关闭,就算是没有更新数据也会建立大量的Http连接来查询是否更新。

2.Comet

  反向AJAX的技术集,包括长轮询和流两种技术实现。反向AJAX利用HTTP1.1中的keepAlive持久性连接技术,在客户端发出请求后,通过KeepAlive保存服务器向浏览器做出响应的通信。

  优点:解决了轮询的消耗服务器网络带宽的问题,变成了推。

  缺点:Comet需要服务器额外的技术支持,需要服务器和客户端同时引入第三方工具包,实现相对复杂。

3.HTML5技术集中的SSE和WebSocket(TCP双通道)

  http://blog.csdn.net/li563868273/article/details/50251267

在我的这篇帖子中有详细的SSE介绍。我这里主要讲如何实现。

如何实现推送?

  jersey的SSE MAVEN依赖包如下:

<dependency>
  <groupId>org.glassfish.jersey.media</groupId>
  <artifactId>jersey-media-sse</artifactId>
  <version>${jersey.version}</version>
</dependency>

Jersey的SSE有两种通信模式,即发布订阅模式(端对端,一对一的模式),广播模式(多客户端对服务器端的多播模式)

1.发布-订阅模式

  第一步:EventSource实现了EventListener接口,EventListener有个onEvent方法,参数为InboundEvent,也就是进站事件(也是个javabean,根据SSE的规范我们主要实现ID,data,name)。

public interface EventListener {
void onEvent(InboundEvent var1);
}

我们在客户端实例化一个EventSource.

@Test
public void testEventSource() throws InterruptedException, URISyntaxException {
//测试10个post
final int testCount = 10;
final String messagePrefix = "pubsub-";
//用CountDownLatch来唤醒eventSource.close
final CountDownLatch latch = new CountDownLatch(testCount);
//在eventSource实例化过程中,会从这个端点向服务器发出请求
final EventSource eventSource = new EventSource(target().path(ROOT_PATH)) {
private int i;
@Override
//当服务器推送过来数据,会在这里响应
public void onEvent(InboundEvent inboundEvent) {
try {
System.out.println("Received: " + inboundEvent.getId() + ":" + inboundEvent.getName() + ":" + new String(inboundEvent.getRawData()));
Assert.assertEquals(messagePrefix + i++, inboundEvent.readData(String.class));
//递减,当减为0时唤醒await
latch.countDown();
} catch (ProcessingException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < testCount; i++) {
target().path(ROOT_PATH).request().post(Entity.text(messagePrefix + i));
}
try {
latch.await();
} finally {
eventSource.close();//关闭eventSource,对于关闭策略可以服务器关闭也可以客户端关闭。
}
}

第二部服务器端如何设置

  客户端会先用GET访问服务器,得到一个信道,这个信道属于端对端,如果当有post时,eventOutput写入OutboundEvent(出站事件)。如果想知道详细步骤可以看看jerseysse包的源码。

@Path("pubsub")
public class AirSsePubSubResource {
//建立eventOutput信道用于写入OutboundEvent(出站事件)
private static EventOutput eventOutput=new EventOutput();
@GET//提供SSE事件输出通道的资源方法
@Produces(SseFeature.SERVER_SENT_EVENTS)
public EventOutput publishMessage() throws IOException{

return  eventOutput;
}
@POST
public Boolean saveMessage(String message) throws IOException {
//向信道中写入出站事件,客户端的信道会接受这个事件并把它转换为进站事件
eventOutput.write(new OutboundEvent.Builder().
id(System.nanoTime() + "").
name("post message").
data(String.class, message).
build());
return true;
}
}

2.广播模式–生产者-消费者模式的体现

  广播模式和发布-订阅不同主要在与服务器端的实现,在服务器端其实是生产者-消费者模式的体现,
在客户端利用get开启信道,服务器端会把这个信道加入队列,在其他方法中也就时消费者会对这个队列上的信道进行消费推送出信息,下面给出的代码在客户端满足一定的数量的时候就可以实现。
package com.lz.sse;

import org.glassfish.jersey.media.sse.EventOutput;
import org.glassfish.jersey.media.sse.OutboundEvent;
import org.glassfish.jersey.media.sse.SseBroadcaster;
import org.glassfish.jersey.media.sse.SseFeature;
import org.glassfish.jersey.server.ChunkedOutput;

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by lizhaoz on 2015/12/10.
 */
@Path("broadcast")
public class AirSseBroadcastResource {
private static final BlockingQueue<BroadcastProcess> processQueue = new LinkedBlockingQueue<>(1);

@Path("book")
@POST
public Boolean postBook(@DefaultValue("0") @QueryParam("total") int total, String bookName) {
final BroadcastProcess broadcastProcess = new BroadcastProcess(total, bookName);
processQueue.add(broadcastProcess);
Executors.newSingleThreadExecutor().execute(broadcastProcess);

return true;
}
@Path("book/clear")
@DELETE
public Boolean clear() {
processQueue.clear();
return true;
}

@Path("book")
@Produces(SseFeature.SERVER_SENT_EVENTS)
@GET
public EventOutput getBook(@DefaultValue("0") @QueryParam("clientId") int clientId) throws InterruptedException {
System.out.println("clientId=" + clientId);
BroadcastProcess broadcastProcess = processQueue.peek();
if (broadcastProcess != null) {
final long countDown = broadcastProcess.countDown();
System.out.println("countDown count= " + countDown);
final EventOutput eventOutput = new EventOutput();
//加入服务队列
broadcastProcess.getBroadcaster().add(eventOutput);
return eventOutput;
} else {
throw new NotFoundException("No new broadcast.");
}
}
static class BroadcastProcess implements Runnable {
private final long processId;
private final String bookName;
private final CountDownLatch latch;
private final SseBroadcaster broadcaster = new SseBroadcaster() {
@Override
public void onException(ChunkedOutput<OutboundEvent> out, Exception e) {

}
};

public BroadcastProcess(int total, String bookName) {
this.processId = System.nanoTime();
this.bookName = bookName;
latch = total > 0 ? new CountDownLatch(total) : null;
}

public long getProcessId() {
return processId;
}

public SseBroadcaster getBroadcaster() {
return broadcaster;
}

public long countDown() {
if (latch == null) {
return -1L;
}
latch.countDown();
return latch.getCount();
}

public void run() {
try {
if (latch != null) {
//在没有足够的客户端的情况下会一直阻塞
latch.await();
}
OutboundEvent.Builder eventBuilder = new OutboundEvent.Builder().mediaType(MediaType.TEXT_PLAIN_TYPE);
OutboundEvent event = eventBuilder.id(processId + "").name("New Book Name").data(String.class, bookName).build();
//向队列上的信道写入这个事件
broadcaster.broadcast(event);
//关闭所有和他连接的信道
broadcaster.closeAll();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

自此SSE基本介绍完毕,具体参考https://github.com/lzggsimida123/jsse可以下载项目来测试。

你可能感兴趣的:(socket,REST,jersey,sse)