標籤:cas neu output str sage close 發送訊息 方式 pipeline
訊息傳遞有非常多種方式。請求/響應(Request/Reply)是最經常使用的。在前面的博文的範例中。非常多都是採用請求/響應的方式。當server接收到訊息後,會馬上write回寫一條訊息到client。
HTTP協議也是基於請求/響應的方式。
可是請求/響應並不能滿足全部的訊息傳遞的需求,有些需求可能須要服務端主動推送訊息到client,而不是被動的等待請求後再給出響應。
公布/訂閱(Publish/Subscribe)是一種server主動發送訊息到client的訊息傳遞方式。訂閱者Subscriber串連到serverclient後,相當於開始訂閱公布者Publisher公布的訊息,當公布者公布了一條訊息後,全部訂閱者都會接收到這條訊息。
網路聊天室一般就是基於公布/訂閱模式來實現。
比如增加一個QQ群。就相當於訂閱了這個群的全部訊息。當有新的訊息,server會主動將訊息發送給全部的client。僅僅只是聊天室裡的全部人既是公布者又是訂閱者。
以下分別用MINA、Netty、Twisted分別實現簡單的公布/訂閱模式的server程式,串連到server的全部client都是訂閱者,當公布者公布一條訊息後,server會將訊息轉寄給全部client。
MINA:
在MINA中。通過IoService的getManagedSessions()方法能夠擷取這個IoService當前管理的全部IoSession,即全部串連到server的client集合。當server接收到公布者公布的訊息後,能夠通過IoService的getManagedSessions()方法擷取到全部client相應的IoSession並將訊息發送到這些client。
public class TcpServer {public static void main(String[] args) throws IOException {IoAcceptor acceptor = new NioSocketAcceptor();acceptor.getFilterChain().addLast("codec",new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"), "\r\n", "\r\n")));acceptor.setHandler(new TcpServerHandle());acceptor.bind(new InetSocketAddress(8080));}}class TcpServerHandle extends IoHandlerAdapter {@Overridepublic void exceptionCaught(IoSession session, Throwable cause)throws Exception {cause.printStackTrace();}@Overridepublic void messageReceived(IoSession session, Object message)throws Exception {// 擷取全部正在串連的IoSessionCollection<IoSession> sessions = session.getService().getManagedSessions().values();// 將訊息寫到全部IoSessionIoUtil.broadcast(message, sessions);}}
Netty:
Netty提供了ChannelGroup來用於儲存Channel組,ChannelGroup是一個安全執行緒的Channel集合,它提供了一些列Channel大量操作。
當一個TCP串連關閉後,相應的Channel會自己主動從ChannelGroup移除。所以不用手動去移除關閉的Channel。
Netty文檔關於ChannelGroup的解釋:
A thread-safe Set that contains open Channels and provides various bulk operations on them. Using ChannelGroup, you can categorize Channels into a meaningful group (e.g. on a per-service or per-state basis.) A closed Channel is automatically removed from the collection, so that you don‘t need to worry about the life cycle of the added Channel. A Channel can belong to more than one ChannelGroup.
當有新的client串連到server,將相應的Channel增加到一個ChannelGroup中。當公布者公布訊息時,server能夠將訊息通過ChannelGroup寫入到全部client。
public class TcpServer {public static void main(String[] args) throws InterruptedException {EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workerGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overridepublic void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LineBasedFrameDecoder(80));pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));pipeline.addLast(new TcpServerHandler());}});ChannelFuture f = b.bind(8080).sync();f.channel().closeFuture().sync();} finally {workerGroup.shutdownGracefully();bossGroup.shutdownGracefully();}}}class TcpServerHandler extends ChannelInboundHandlerAdapter {// ChannelGroup用於儲存全部串連的client。注意要用static來保證僅僅有一個ChannelGroup執行個體。否則每new一個TcpServerHandler都會建立一個ChannelGroupprivate static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);@Overridepublic void channelActive(ChannelHandlerContext ctx) {channels.add(ctx.channel()); // 將新的串連增加到ChannelGroup。當串連斷開ChannelGroup會自己主動移除相應的Channel}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {channels.writeAndFlush(msg + "\r\n"); // 接收到訊息後。將訊息發送到ChannelGroup中的全部client}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {// cause.printStackTrace(); 臨時把異常列印凝視掉,由於PublishClient公布一條訊息後會馬上中斷連線。而server也會向PublishClient發送訊息,所以會拋出異常ctx.close();}}
Twisted:
在Twisted中,全域的資料通常會放在Factory,而每一個串連相關的資料會放在Protocol中。所以這裡能夠在Factory中增加一個屬性,來存放Protocol集合,表示全部串連server的client。當有新的client串連到server時。將相應的Protocol執行個體放入集合。當串連斷開,將相應的Protocol從集合中移除。當server接收到公布者公布的訊息後,遍曆全部client並發送訊息。
# -*- coding:utf-8 –*-from twisted.protocols.basic import LineOnlyReceiverfrom twisted.internet.protocol import Factoryfrom twisted.internet import reactorclass TcpServerHandle(LineOnlyReceiver): def __init__(self, factory): self.factory = factory def connectionMade(self): self.factory.clients.add(self) # 新串連增加串連相應的Protocol執行個體到clients def connectionLost(self, reason): self.factory.clients.remove(self) # 串連斷開移除串連相應的Protocol執行個體 def lineReceived(self, line): # 遍曆全部的串連。發送資料 for c in self.factory.clients: c.sendLine(line)class TcpServerFactory(Factory): def __init__(self): self.clients = set() # set集合用於儲存全部串連到server的client def buildProtocol(self, addr): return TcpServerHandle(self)reactor.listenTCP(8080, TcpServerFactory())reactor.run()
以下各自是兩個client程式,一個是用於公布訊息的client。一個是訂閱訊息的client。
公布訊息的client非常easy,就是向serverwrite一條訊息就可以:
public class PublishClient {public static void main(String[] args) throws IOException {Socket socket = null;OutputStream out = null;try {socket = new Socket("localhost", 8080);out = socket.getOutputStream();out.write("Hello\r\n".getBytes()); // 公布資訊到serverout.flush();} finally {// 關閉串連out.close();socket.close();}}}
訂閱訊息的client串連到server後,會堵塞等待接收server發送的公布訊息:
public class SubscribeClient {public static void main(String[] args) throws IOException {Socket socket = null;BufferedReader in = null;try {socket = new Socket("localhost", 8080);in = new BufferedReader(new InputStreamReader(socket.getInputStream()));while (true) {String line = in.readLine(); // 堵塞等待server公布的訊息System.out.println(line);}} finally {// 關閉串連in.close();socket.close();}}}
分別針對MINA、Netty、Twistedserver進行測試:
1、測試時首先開啟server;
2、然後再執行訂閱訊息的clientSubscribeClient,SubscribeClient能夠開啟多個;
3、最後執行公布訊息的clientPublishClient。能夠多次執行查看全部SubscribeClient的輸出結果。
執行結果能夠發現,當執行公布訊息的clientPublishClient公布一條訊息到server時。server會主動將這條訊息轉寄給全部的TCP串連,全部的訂閱訊息的clientSubscribeClient都會接收到這條訊息並列印出來。
叉叉哥 轉載請註明出處:http://blog.csdn.net/xiao__gui/article/details/39396789
MINA、Netty、Twisted一起學系列
MINA、Netty、Twisted一起學(一):實現簡單的TCPserver
MINA、Netty、Twisted一起學(二):TCP訊息邊界問題及按行切割訊息
MINA、Netty、Twisted一起學(三):TCP訊息固定大小的首碼(Header)
MINA、Netty、Twisted一起學(四):定製自己的協議
MINA、Netty、Twisted一起學(五):整合protobuf
MINA、Netty、Twisted一起學(六):session
MINA、Netty、Twisted一起學(七):公布/訂閱(Publish/Subscribe)
MINA、Netty、Twisted一起學(八):HTTPserver
MINA、Netty、Twisted一起學(九):非同步IO和回呼函數
MINA、Netty、Twisted一起學(十):執行緒模式
MINA、Netty、Twisted一起學(十一):SSL/TLS
MINA、Netty、Twisted一起學(十二):HTTPS
原始碼
https://github.com/wucao/mina-netty-twisted
Mina、Netty、Twisted一起學(七):公布/訂閱(Publish/Subscribe)