首页 > 代码库 > Mina、Netty、Twisted一起学(六):session

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

开发过Web应用的同学应该都会使用session。由于HTTP协议本身是无状态的,所以一个客户端多次访问这个web应用的多个页面,服务器无法判断多次访问的客户端是否是同一个客户端。有了session就可以设置一些和客户端相关的属性,用于保持这种连接状态。例如用户登录系统后,设置session标记这个客户端已登录,那么访问别的页面时就不用再次登录了。

在同步阻塞的网络编程中,代码都是按照TCP操作顺序编写的,即创建连接、多次读写、关闭连接,这样很容易判断这一系列操作是否是同一个连接。而在事件驱动的异步网络编程框架中,IO操作都会触发一个事件调用相应的事件函数,例如接收到客户端的新数据,会调用messageReceived(MINA)、channelRead(Netty)、dataReceived(Twisted),同一个TCP连接的多次请求和多个客户端请求都是一样的。

那么如何判断多次请求到底是不是同一个TCP连接,如何保存连接相关的信息?针对这个问题,MINA、Netty、Twisted都提供了相应的解决方案。

下面分别用MINA、Netty、Twisted实现一个请求次数计数器,用于记录同一个连接多次请求的请求次数。

MINA:

在MINA中,每当一个客户端连接到服务器,就会创建一个新的IoSession,直到客户端断开连接才会销毁。IoSession可以用setAttribute和getAttribute来存储和获取一个TCP连接的相关信息。

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 {

		int counter = 1;
		
		// 第一次请求,创建session中的counter
		if(session.getAttribute("counter") == null) {
			session.setAttribute("counter", 1);
		} else {
			// 获取session中的counter,加1后再存入session
			counter = (Integer) session.getAttribute("counter");
			counter++;
			session.setAttribute("counter", counter);
		}
		
		String line = (String) message;
		System.out.println("第" + counter + "次请求:" + line);
	}
}

Netty:

Netty中分为两种情况,一种是针对每个TCP连接创建一个新的ChannelHandler实例,另一种是所有TCP连接共用一个ChannelHandler实例。这两种方式的区别在于ChannelPipeline的addLast方法中添加的是否是新的ChannelHandler实例。

针对每个TCP连接创建一个新的ChannelHandler实例:

针对每个TCP连接创建一个新的ChannelHandler实例是最常用的一种方式。这种情况非常简单,直接在ChannelHandler的实现类中加入一个成员变量即可保存连接相关的信息。

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 TcpServerHandler()); // 针对每个TCP连接创建一个新的ChannelHandler实例
						}
					});
			ChannelFuture f = b.bind(8080).sync();
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}

}

class TcpServerHandler extends ChannelInboundHandlerAdapter {

	// 连接相关的信息直接保存在TcpServerHandler的成员变量中
	private int counter = 0;

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		
		counter++;
		
		String line = (String) msg;
		System.out.println("第" + counter + "次请求:" + line);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

所有TCP连接共用一个ChannelHandler实例:

在这种情况下,就不能把连接相关的信息放在ChannelHandler实现类的成员变量中了,否则这些信息会被其他连接共用。这里就要使用到ChannelHandlerContext的Attribute了。

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>() {
						
						private TcpServerHandler tcpServerHandler = new TcpServerHandler();
						
						@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(tcpServerHandler); // 多个连接使用同一个ChannelHandler实例
						}
					});
			ChannelFuture f = b.bind(8080).sync();
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}

}

@Sharable // 多个连接使用同一个ChannelHandler,要加上@Sharable注解
class TcpServerHandler extends ChannelInboundHandlerAdapter {
	
	private AttributeKey<Integer> attributeKey = AttributeKey.valueOf("counter");

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) {
		
		Attribute<Integer> attribute = ctx.attr(attributeKey);
		
		int counter = 1;
		
		if(attribute.get() == null) {
			attribute.set(1);
		} else {
			counter = attribute.get();
			counter++;
			attribute.set(counter);
		}
		
		String line = (String) msg;
		System.out.println("第" + counter + "次请求:" + line);
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}

Twisted:

在Twisted中,每个TCP连接都会创建一个新的Protocol实例,这样也就很简单了,直接将连接相关的信息保存为Protocol继承类的属性。

# -*- coding:utf-8 –*-

from twisted.protocols.basic import LineOnlyReceiver
from twisted.internet.protocol import Factory
from twisted.internet import reactor

class TcpServerHandle(LineOnlyReceiver):

    def lineReceived(self, data):
        if(hasattr(self, ‘counter‘)):
            self.counter += 1
        else:
            self.counter = 1;
            
        print "第" + str(self.counter) + "次请求:" + data

factory = Factory()
factory.protocol = TcpServerHandle
reactor.listenTCP(8080, factory)
reactor.run()

下面是一个Java实现的客户端,代码中发起了3次TCP连接,在每个连接中发送两次请求数据到服务器:

public class TcpClient {

	public static void main(String[] args) throws IOException, InterruptedException {

		// 3次TCP连接,每个连接发送2个请求数据
		for(int i = 0; i < 3; i++) {
			
			
			Socket socket = null;
			OutputStream out = null;
	
			try {
	
				socket = new Socket("localhost", 8080);
				out = socket.getOutputStream();
	
				// 第一次请求服务器
				String lines1 = "Hello\r\n";
				byte[] outputBytes1 = lines1.getBytes("UTF-8");
				out.write(outputBytes1);
				out.flush();
	
				// 第二次请求服务器
				String lines2 = "World\r\n";
				byte[] outputBytes2 = lines2.getBytes("UTF-8");
				out.write(outputBytes2);
				out.flush();
	
			} finally {
				// 关闭连接
				out.close();
				socket.close();
			}
			
			Thread.sleep(1000);
		}
	}
}

分别测试上面的4个服务器,输出结果都是:

第1次请求:Hello
第2次请求:World
第1次请求:Hello
第2次请求:World
第1次请求:Hello
第2次请求:World


作者:叉叉哥   转载请注明出处:http://blog.csdn.net/xiao__gui/article/details/39180209

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