首先记得引入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() {
@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();
}
}
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() {
@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();
}
}