消息传递有非常多种方式。请求/响应(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 { @Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
cause.printStackTrace();
} @Override
public void messageReceived(IoSession session, Object message)
throws Exception { // 获取全部正在连接的IoSession
Collection<IoSession> sessions = session.getService().getManagedSessions().values(); // 将消息写到全部IoSession
IoUtil.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>() {
@Override
public 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都会创建一个ChannelGroup
private static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override
public void channelActive(ChannelHandlerContext ctx) {
channels.add(ctx.channel()); // 将新的连接增加到ChannelGroup。当连接断开ChannelGroup会自己主动移除相应的Channel
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
channels.writeAndFlush(msg + "\r\n"); // 接收到消息后。将消息发送到ChannelGroup中的全部client
} @Override
public 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 LineOnlyReceiver
from twisted.internet.protocol import Factory
from twisted.internet import reactor class 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()); // 公布信息到server
out.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)的更多相关文章

  1. RabbitMQ学习总结 第四篇:发布/订阅 Publish/Subscribe

    目录 RabbitMQ学习总结 第一篇:理论篇 RabbitMQ学习总结 第二篇:快速入门HelloWorld RabbitMQ学习总结 第三篇:工作队列Work Queue RabbitMQ学习总结 ...

  2. RabbitMQ入门(3)——发布/订阅(Publish/Subscribe)

    在上一篇RabbitMQ入门(2)--工作队列中,有一个默认的前提:每个任务都只发送到一个工作人员.这一篇将介绍发送一个消息到多个消费者.这种模式称为发布/订阅(Publish/Subscribe). ...

  3. RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe)

    原文:RabbitMQ入门教程(五):扇形交换机发布/订阅(Publish/Subscribe) 版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明. ...

  4. Mina、Netty、Twisted一起学(七):发布/订阅(Publish/Subscribe)

    消息传递有很多种方式,请求/响应(Request/Reply)是最常用的.在前面的博文的例子中,很多都是采用请求/响应的方式,当服务器接收到消息后,会立即write回写一条消息到客户端.HTTP协议也 ...

  5. Mina、Netty、Twisted一起学(八):HTTP服务器

    HTTP协议应该是目前使用最多的应用层协议了,用浏览器打开一个网站就是使用HTTP协议进行数据传输. HTTP协议也是基于TCP协议,所以也有服务器和客户端.HTTP客户端一般是浏览器,当然还有可能是 ...

  6. Mina、Netty、Twisted一起学(十):线程模型

    要想开发一个高性能的TCP服务器,熟悉所使用框架的线程模型非常重要.MINA.Netty.Twisted本身都是高性能的网络框架,如果再搭配上高效率的代码,才能实现一个高大上的服务器.但是如果不了解它 ...

  7. Mina、Netty、Twisted一起学(九):异步IO和回调函数

    用过JavaScript或者jQuery的同学都知道,JavaScript特别是jQuery中存在大量的回调函数,例如Ajax.jQuery的动画等. $.get(url, function() { ...

  8. Mina、Netty、Twisted一起学(六):session

    开发过Web应用的同学应该都会使用session.由于HTTP协议本身是无状态的,所以一个客户端多次访问这个web应用的多个页面,服务器无法判断多次访问的客户端是否是同一个客户端.有了session就 ...

  9. Mina、Netty、Twisted一起学(五):整合protobuf

    protobuf是谷歌的Protocol Buffers的简称,用于结构化数据和字节码之间互相转换(序列化.反序列化),一般应用于网络传输,可支持多种编程语言. protobuf如何使用这里不再介绍, ...

随机推荐

  1. Python学习-day7 类 部分socket

    这周还是继续关于类的学习,在面向对象的学习过程中又学习了网络编程,并且提交了编写FTP的作业. 复习一下类的相关概念和定义 类      属性           实例变量:内存中           ...

  2. SDOJ 3742 黑白图

    [描述] 一个 n 个点 m 条边构成的无向带权图.由一些黑点与白点构成 树现在每个白点都要与他距离最近的黑点通过最短路连接(如果有很多个,可以选 取其中任意一个),我们想要使得花费的代价最小.请问这 ...

  3. failed to lazily initialize a collection of role 异常

    最近在通过配置实体类的方式,正向自动扫描注解方式配置的hibernate类文件来生成数据库的方法搭建环境,遇到了许多问题. 通过数据库配置hibernate的时候,大家都知道是在实体类对应生成的.hb ...

  4. CS231n笔记 Lecture 1 Introduction

    主题有关 这一讲主要是介绍性质的,虽然大多数概念以前听说过,但还是在他们的介绍中让我有如下一些认识,所谓温故而知新,不无道理: IMAGENET Feifei Li的团队首先爬取.标注了IMAGENE ...

  5. BZOJ4816 [Sdoi2017]数字表格 【莫比乌斯反演】

    题目 Doris刚刚学习了fibonacci数列.用f[i]表示数列的第i项,那么 f[0]=0 f[1]=1 f[n]=f[n-1]+f[n-2],n>=2 Doris用老师的超级计算机生成了 ...

  6. 【CCF】最优灌溉 最小生成树

    [AC] #include<iostream> #include<cstdio> #include<string> #include<cstring> ...

  7. 【前端学习笔记】2015-09-10~~~~ css层叠样式表~~格式

    关于颜色的定义: 有几种方式:1.直接接英文单词   2.#16进制  3.rgb(100%:0%:0%)或者里面写像素,此处百分号不能省略. 加多个属性时,用“;”分开 前面如果是定义标签的样式时, ...

  8. net4:MultiView(view)行为中的commmandname与commmandargument,ListBox的使用及移动操作

    原文发布时间为:2008-07-29 -- 来源于本人的百度文章 [由搬家工具导入] using System;using System.Data;using System.Configuration ...

  9. 学习javascript设计模式之代理模式

    1.代理模式为一个对象提供一个代用品或占位符,以便控制对它的访问. 2.不用代理模式: 客户 -> 本体  使用代理模式:  客户 -> 代理 -> 本体 3.例子场景1 点击操作与 ...

  10. Select函数实现

    int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict errorfds, ...