使用Netty与Android构建简单C/S消息服务

使用Netty 构建简单C/S消息服务

Server

首先记得引入nettycompile 'io.netty:netty-all:4.1.13.Final'

public class Server {
    private static final int PORT = 8999;

    public static void main(String[] args) {
        // NioEventLoopGroup is a multithreaded event loop that handles I/O operation.
        EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用于处理客户端的连接请求
        EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用于处理与各个客户端连接的IO操作

        // ServerBootstrap is a helper class that sets up a server.
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class) // 使用Java Nio的Selector来处理新连接接入

                // ChannelInitializer是一个特殊的Handler用来帮助用户配置新Channel,一般用于在channelPipeline中新建channelHandler
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new EchoServerHandler());
                    }
                })
                .option(ChannelOption.SO_BACKLOG, 128)  // 对应bossGroup
                .childOption(ChannelOption.SO_KEEPALIVE, true); // 对应workerGroup
        try {
            ChannelFuture channelFuture = serverBootstrap.bind(PORT).sync();    // 等待接入

            // Wait until the server socket is closed.
            // In this example, this does not happen, but you can do that to gracefully
            // shut down your server.
            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }
}

ChannelInboundHandlerAdapter负责处理连接建立,收到消息。

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        System.out.println("connected from:" + ctx.channel().remoteAddress());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byt = (ByteBuf) msg;
        byte[] bytesSrc = new byte[byt.readableBytes()];
        byt.readBytes(bytesSrc);
        String data = new String(bytesSrc);
        System.out.println("received:" + data + " from:" + ctx.channel().remoteAddress());
        String response = "response: " + data;

        ByteBuf responseBuf = ctx.alloc().buffer(response.length());
        responseBuf.writeBytes(response.getBytes());
        ctx.channel().writeAndFlush(responseBuf);
        byt.release();  // 释放资源
    }

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

Client

public class MainActivity extends AppCompatActivity {
    private EditText mIpEt;
    private EditText mPortEt;
    private Button mConnBtn;
    private TextView mScreenTv;
    private EditText mInputEt;
    private Button mSendBtn;

    private SocketThread mSocketThread;
    private static Handler mMainHandler;

    public static final int MSG_CONNECT = 0x001;
    public static final int MSG_RECEIVE = 0x002;
    public static final int MSG_SEND = 0x003;

    public static final String DATA_RECEIVE = "data_receive";
    public static final String DATA_SEND = "data_send";

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mIpEt = findViewById(R.id.main_ip_et);
        mPortEt = findViewById(R.id.main_port_et);
        mConnBtn = findViewById(R.id.main_connect_btn);
        mScreenTv = findViewById(R.id.main_screen_tv);
        mInputEt = findViewById(R.id.main_input_et);
        mSendBtn = findViewById(R.id.main_send_btn);

        // defalut value. Change it to your own server ip
        mIpEt.setText("172.16.62.65");
        mPortEt.setText("8999");

        mConnBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String ip = mIpEt.getText().toString();
                String port = mPortEt.getText().toString();
                if (TextUtils.isEmpty(ip) || TextUtils.isEmpty(port)) {
                    Toast.makeText(MainActivity.this, "ip or port is null", Toast.LENGTH_SHORT).show();
                } else {
                    connectToServer(ip, Integer.valueOf(port));
                }
            }
        });

        mSendBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String data = mInputEt.getText().toString();
                if (!TextUtils.isEmpty(data)) {
                    mSocketThread.sendMessage(data);
                }
            }
        });

        // TODO handler may cause memory leaks
        mMainHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case MSG_CONNECT:
                        Toast.makeText(MainActivity.this, "Connect to Server Success", Toast.LENGTH_SHORT).show();
                        mConnBtn.setText("Connected");
                        mConnBtn.setEnabled(false);
                        break;
                    case MSG_RECEIVE:
                        Bundle data = msg.getData();
                        String dataStr = data.getString(DATA_RECEIVE);
                        CharSequence originData = mScreenTv.getText();
                        String result = originData + "\n" + dataStr;
                        mScreenTv.setText(result);
                        break;
                }
            }
        };
    }


    private void connectToServer(String ip, int port) {
        mSocketThread = new SocketThread(ip, port);
        mSocketThread.start();
    }

    private static class SocketThread extends Thread {
        private Channel mChannel;
        private String mIp;
        private int mPort;
        private SendThread mSendThread;

        public SocketThread(String ip, int port) {
            this.mIp = ip;
            this.mPort = port;
        }

        @Override
        public void run() {
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            Bootstrap clientBootStrap = new Bootstrap();
            clientBootStrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler(mMainHandler));
                        }
                    });

            try {
                // Start the client.
                ChannelFuture future = clientBootStrap.connect(mIp, mPort).sync();
                mChannel = future.channel();
                mSendThread = new SendThread(mChannel);
                mSendThread.start();

                // Wait until the connection is closed.
                mChannel.closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                eventLoopGroup.shutdownGracefully();
            }
        }

        public void sendMessage(String data) {
            Handler socketHandler = mSendThread.getSocketHandler();
            Message message = socketHandler.obtainMessage();
            message.what = MSG_SEND;
            Bundle bundle = new Bundle();
            bundle.putString(DATA_SEND, data);
            message.setData(bundle);
            socketHandler.sendMessage(message);
        }

    }

    private static class SendThread extends Thread {
        private Handler mSocketHandler;
        private Channel mChannel;

        public SendThread(Channel channel) {
            mChannel = channel;
        }

        @Override
        public void run() {
            // init child thread handler
            if (mSocketHandler == null) {
                Looper.prepare();
                mSocketHandler = new Handler(Looper.myLooper()) {
                    @Override
                    public void handleMessage(Message msg) {
                        switch (msg.what) {
                            case MSG_SEND:
                                String data = msg.getData().getString(DATA_SEND);
                                ByteBuf byteBuf = Unpooled.buffer(data.length());   //使用非池化Buffer去申请内存,这里待优化
                                byteBuf.writeBytes(data.getBytes());
                                mChannel.writeAndFlush(byteBuf);
                                break;
                        }
                    }
                };
            }
            Looper.loop();
        }

        public Handler getSocketHandler() {
            return mSocketHandler;
        }
    }
}

ChannelInboundHandlerAdapter实现代码,用于处理连接建立、收到消息。

public class EchoClientHandler extends ChannelInboundHandlerAdapter {
    private Handler mMainHandler;

    public EchoClientHandler(Handler handler) {
        mMainHandler = handler;
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        super.channelActive(ctx);
        mMainHandler.sendEmptyMessage(MainActivity.MSG_CONNECT);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        super.channelInactive(ctx);
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byt = (ByteBuf) msg;
        byte[] bytesSrc = new byte[byt.readableBytes()];
        byt.readBytes(bytesSrc);
        String data = new String(bytesSrc);
        Message message = mMainHandler.obtainMessage();
        message.what = MainActivity.MSG_RECEIVE;
        Bundle extra = new Bundle();
        extra.putString(MainActivity.DATA_RECEIVE, data);
        message.setData(extra);
        mMainHandler.sendMessage(message);

        byt.release();  // 释放资源
    }

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

Layout

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:layout_margin="15dp">

    <EditText
        android:id="@+id/main_ip_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ip address"/>

    <EditText
        android:id="@+id/main_port_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="port"
        android:inputType="number"/>

    <Button
        android:id="@+id/main_connect_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Connect"/>

    <EditText
        android:id="@+id/main_input_et"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="message to server"/>

    <Button
        android:id="@+id/main_send_btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Send"/>

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="10dp">
        <TextView
            android:id="@+id/main_screen_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="received message will be shown here"/>
    </ScrollView>

</LinearLayout>

Run


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值