1. 程式人生 > >Netty構建遊戲服務器(三)--netty spring簡單整合

Netty構建遊戲服務器(三)--netty spring簡單整合

channel server 配置 generated truct 成功 pipeline close cef

一,基本方法

上節實現了netty的基本連接,這節加入spring來管理netty,由spring來開啟netty服務。

在netty服務器中,我們建立了三個類:HelloServer(程序主入口) , HelloServerInitializer(傳輸通道初始化),HelloServerHandler(業務控制器)

這三個類中HelloServer中new了一個HelloServerInitializer,在HelloServerInitializer最後又new了一個HelloServerHandler。其中需要new的地方,就是spring要管理的地方。

二,實現源碼

1,相關準備

導入需要的JAR包和相關依賴,主要是spring,log4j,netty等,後面的項目源碼中包含了所有jar包;

另外你可以使用maven來構建項目,添加各種jar包。

2,主程序TestSrping.java

主程序很簡單,就是加載spring配置文件就可以了。

package com.wayhb;

import org.apache.log4j.Logger;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestSpring { private static Logger log = Logger.getLogger(TestSpring.class); public static void main(String[] args) { // TODO Auto-generated method stub //加載spirng配置文件 ApplicationContext context= new ClassPathXmlApplicationContext("server.xml"); } }

3,spring的XML配置單server.xml和LOG4J配置文件

server.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:lang="http://www.springframework.org/schema/lang" xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-3.0.xsd"
    default-lazy-init="false" default-autowire="byName">

<!--  <bean id="user" class="com.wayhb.User">
<property name="userid" value="20"></property>
<property name="username" value="wayhb"></property>
</bean>-->
<!--開啟註解方式,掃描com.wayhb,com.netty兩個包路徑-->
<context:annotation-config/> 
<context:component-scan base-package="com.wayhb,com.netty" /> 

<!-- 傳統方法配置BEAN
<bean id="helloServer" class="com.netty.HelloServer" init-method="serverStart">
<property name="helloServerInitializer" ref="helloServerInitializer"></property>
</bean>

<bean id="helloServerInitializer" class="com.netty.HelloServerInitializer">
<property name="helloServerHandler" ref="helloServerHandler"></property>
</bean>

<bean id="helloServerHandler" class="com.netty.HelloServerHandler" scope="prototype"></bean>
 -->

</beans>

log4j.properties

### 設置###
#log4j.rootLogger = debug,stdout,D,E
log4j.rootLogger = debug,stdout
### 輸出信息到控制擡 ###
log4j.appender.stdout = org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target = System.out
log4j.appender.stdout.layout = org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n

### 輸出DEBUG 級別以上的日誌到=E://logs/error.log ###
#log4j.appender.D = org.apache.log4j.DailyRollingFileAppender
#log4j.appender.D.File = C://logs/log.log
#log4j.appender.D.Append = true
#log4j.appender.D.Threshold = DEBUG 
#log4j.appender.D.layout = org.apache.log4j.PatternLayout
#log4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

### 輸出ERROR 級別以上的日誌到=E://logs/error.log ###
#log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
#log4j.appender.E.File =C://logs/error.log 
#log4j.appender.E.Append = true
#log4j.appender.E.Threshold = ERROR 
#log4j.appender.E.layout = org.apache.log4j.PatternLayout
#log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n

其中輸出功能是關閉的,需要的話去掉#就可以了

4,HelloServer

package com.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

import javax.annotation.PostConstruct;

import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
//註解方式註入bean,名字是helloServer
@Service("helloServer")
public class HelloServer {
    private static Logger log = Logger.getLogger(HelloServer.class); 
    /**
     * 服務端監聽的端口地址
     */
    private static final int portNumber = 7878;
    
    //自動裝備變量,spring會根據名字或者類型來裝備這個變量,註解方式不需要set get方法了
    @Autowired
    private HelloServerInitializer helloServerInitializer;

    //程序初始方法入口註解,提示spring這個程序先執行這裏
    @PostConstruct
    public void serverStart() throws InterruptedException{
         EventLoopGroup bossGroup = new NioEventLoopGroup();
         EventLoopGroup workerGroup = new NioEventLoopGroup();
         try {
             ServerBootstrap b = new ServerBootstrap();
             b.group(bossGroup, workerGroup);
             b.channel(NioServerSocketChannel.class);
             b.childHandler(helloServerInitializer);

             // 服務器綁定端口監聽
             ChannelFuture f = b.bind(portNumber).sync();
             // 監聽服務器關閉監聽
             f.channel().closeFuture().sync();

             log.info("###########################################");
             // 可以簡寫為
             /* b.bind(portNumber).sync().channel().closeFuture().sync(); */
         } finally {
             bossGroup.shutdownGracefully();
             workerGroup.shutdownGracefully();
         }
    }
    
}

註解方式更加簡便,配置內容少了很多

5,HelloServerInitializer

package com.netty;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

@Service("helloServerInitializer")
public class HelloServerInitializer extends ChannelInitializer<SocketChannel> {

    @Autowired
    private HelloServerHandler helloServerHandler;
    
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();

        // 以("\n")為結尾分割的 解碼器
        pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));

        // 字符串解碼 和 編碼
        pipeline.addLast("decoder", new StringDecoder()); 
        pipeline.addLast("encoder", new StringEncoder());

        // 自己的邏輯Handler
        pipeline.addLast("handler", helloServerHandler);
    }
}

6,HelloServerHandler

package com.netty;

import java.net.InetAddress;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import io.netty.channel.ChannelHandler.Sharable;

@Service("helloServerHandler")
@Scope("prototype")
//特別註意這個註解@Sharable,默認的4版本不能自動導入匹配的包,需要手動加入
//地址是import io.netty.channel.ChannelHandler.Sharable;
@Sharable
public class HelloServerHandler extends SimpleChannelInboundHandler<String> {
    
    
    public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {  // (2)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n");
        }
        channels.add(ctx.channel());
    } 
    
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {  // (3)
        Channel incoming = ctx.channel();
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n");
        }
        channels.remove(ctx.channel());
    }
    
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        // 收到消息直接打印輸出
        System.out.println(ctx.channel().remoteAddress() + " Say : " + msg);
        
        // 返回客戶端消息 - 我已經接收到了你的消息
        ctx.writeAndFlush("Received your message !\n");
    }
    
    /*
     * 
     * 覆蓋 channelActive 方法 在channel被啟用的時候觸發 (在建立連接的時候)
     * 
     * channelActive 和 channelInActive 在後面的內容中講述,這裏先不做詳細的描述
     * */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        
        System.out.println("RamoteAddress : " + ctx.channel().remoteAddress() + " active !");
        
        ctx.writeAndFlush( "Welcome to " + InetAddress.getLocalHost().getHostName() + " service!\n");
        
        super.channelActive(ctx);
    }
}

特別註意這個註解@Sharable,默認的4版本不能自動導入匹配的包,需要手動加入
地址是import io.netty.channel.ChannelHandler.Sharable;

三,運行測試

1,打開TestSpring.java,右鍵運行,正常情況下服務器會開啟;

2,打開上節寫的項目,打開HelloClient.java,右鍵運行;

3,HelloClient.java上再運行一次,出現兩個客戶端,如果都連接成功,說明項目沒有問題。

註意事項:netty4需要@Sharable才可以開啟多個handler,所以需要多個連接的handler類上要加入註解@Sharable,該註解自動導入有問題,手動加入包import io.netty.channel.ChannelHandler.Sharable;

Netty構建遊戲服務器(三)--netty spring簡單整合