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

來源:互聯網
上載者:User

標籤:style   blog   http   color   io   os   使用   java   ar   

開發過Web應用的同學應該都會使用session。由於HTTP協議本身是無狀態的,所以一個用戶端多次訪問這個web應用的多個頁面,伺服器無法判斷多次訪問的用戶端是否是同一個用戶端。有了session就可以設定一些和用戶端相關的屬性,用於保持這種串連狀態。例如使用者登入系統後,設定session標記這個用戶端已登入,那麼訪問別的頁面時就不用再次登入了。

不過本文的內容不是Web應用的session,而是TCP串連的session,實際上二者還是有很大區別的。Web應用的session實現方式並不是基於同一個TCP串連,而是通過cookie實現,這裡不再詳細展開。上面講到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 LineOnlyReceiverfrom twisted.internet.protocol import Factoryfrom twisted.internet import reactorclass TcpServerHandle(LineOnlyReceiver):    def lineReceived(self, data):        if(hasattr(self, ‘counter‘)):            self.counter += 1        else:            self.counter = 1;                    print "第" + str(self.counter) + "次請求:" + datafactory = Factory()factory.protocol = TcpServerHandlereactor.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

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

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.