Netty实现Android聊天室
一.Netty框架
Netty是一个广受欢迎的异步事件驱动的Java开源网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。为了让NIO处理更好的利用多线程特性,Netty实现了Reactor线程模型。
Reactor模型中有四个核心概念:
- Resources资源(请求/任务)
- Synchronous Event Demultiplexer同步事件复用器
- Dispatcher分配器
- Request Handler请求处理器
二.服务端
导入依赖
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
类文件
NettyService
/**
* @author 25403
*/
public class NettyService {
private final int port;
public NettyService(int port) {
this.port = port;
}
public void run() {
/**
* 1.创建两个线程组
* group1:接受客户端的连接
* group2:处理网络端的操作
*/
EventLoopGroup group1 = new NioEventLoopGroup();
EventLoopGroup group2 = new NioEventLoopGroup();
/**
* 2.创建服务器配置参数
*/
ServerBootstrap server = new ServerBootstrap();
try {
//添加线程组
server.group(group1, group2)
//使用NioSocketChannel作为服务器通道
.channel(NioServerSocketChannel.class)
//设置线程等待队列中等待连接个数
.option(ChannelOption.SO_BACKLOG, 128)
//保持激活状态
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//构建通道
ChannelPipeline pipeline = socketChannel.pipeline();
//添加一个解码器
pipeline.addLast("decoder", new StringDecoder());
//添加一个编码器
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast(new NettyServerHandler() {
});
}
});
System.out.println("服务器启动中");
ChannelFuture cf = server.bind(port).sync();
System.out.println("服务器已启动");
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group1.shutdownGracefully();
group2.shutdownGracefully();
}
}
public static void main(String[] args) {
new NettyService(9999).run();
}
}
NettyServerHandler
/**
* @author 25403
*/
public abstract class NettyServerHandler extends SimpleChannelInboundHandler<String> {
public static List<Channel> channels = new ArrayList<Channel>();
/**
* 上线
*
* @param ctx
* @throws Exception
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
Channel channel = ctx.channel();
channels.add(channel);
System.out.println("Server:" + channel.remoteAddress().toString().substring(1) + "上线");
}
/**
* 离线
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
Channel channel = ctx.channel();
channels.remove(channel);
System.out.println("Server:" + channel.remoteAddress().toString().substring(1) + "下线");
}
@Override
protected void messageReceived(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
Channel channel = channelHandlerContext.channel();
for (Channel ch : channels) {
String cliInfo = channel.remoteAddress().toString().substring(1);
System.out.println(cliInfo + "\t" + s + "\n");
ch.writeAndFlush(cliInfo + "说:" + s + "\n");
}
}
}
三.客户端
导入依赖
// https://mvnrepository.com/artifact/io.netty/netty-all
implementation group: 'io.netty', name: 'netty-all', version: '5.0.0.Alpha2'
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<ScrollView
android:layout_width="match_parent"
android:layout_height="300dp">
<TextView
android:id="@+id/tv_con"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/et_con"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="4"
android:singleLine="true"
android:hint="@string/content"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="send"
android:text="@string/send"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center">
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/bt_get"
tools:ignore="NestedWeights"
android:text="@string/getConnect"
android:textSize="20sp"
android:onClick="getConnect"/>
<Button
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/bt_post"
android:text="@string/disConnect"
android:textSize="20sp"
android:onClick="disConnect"
/>
</LinearLayout>
</LinearLayout>
类文件
MainActivity
public class MainActivity extends AppCompatActivity {
private static TextView tvCon;
private EditText etCon;
private ChatClient mChatClint;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvCon = findViewById(R.id.tv_con);
etCon = findViewById(R.id.et_con);
mChatClint = new ChatClient("192.168.1.106", 9999);
}
public void getConnect(View view) {
mChatClint.connect();
}
public void disConnect(View view) {
mChatClint.disconnect();
}
public void send(View view) {
new Thread() {
@Override
public void run() {
super.run();
String msg = etCon.getText().toString();
if (msg.equals("")) {
Toast.makeText(MainActivity.this, "发送消息不能为空!", Toast.LENGTH_LONG).show();
return;
}
mChatClint.send(msg);
etCon.setText("");
}
}.start();
}
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
if (msg.what == 0x001) {
Bundle bundle = msg.getData();
String data = bundle.getString("msg");
tvCon.append(data);
}
}
};
public static Handler getMsgHandler() {
return mHandler;
}
}
ChatClient
public class ChatClient {
private String host;
private int port;
private Channel mChannel;
private EventLoopGroup group1;
/**
* 防止一个客户端建立多个Bootstrap
*/
private Bootstrap bootstrap = null;
public ChatClient(String host, int port) {
this.host = host;
this.port = port;
}
public void connect() {
group1 = new NioEventLoopGroup();
if(bootstrap==null){
bootstrap = new Bootstrap();
}else {
return;
}
try {
bootstrap.group(group1);
bootstrap.channel(NioSocketChannel.class);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("decoder", new StringDecoder());
pipeline.addLast("encoder", new StringEncoder());
pipeline.addLast(new ChatClientHandler());
}
});
ChannelFuture cf = bootstrap.connect(host, port).sync();
mChannel = cf.channel();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void send(String msg) {
if (mChannel.isOpen()) {
mChannel.writeAndFlush(msg + "\n");
}
}
public void disconnect() {
if (mChannel.isOpen()) {
mChannel.close();
}
if (!group1.isShutdown()) {
group1.shutdownGracefully();
}
}
}
ChatClientHandler
public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void messageReceived(ChannelHandlerContext ctx, String msg) throws Exception {
Message message = new Message();
message.what = 0x001;
Bundle bundle = new Bundle();
bundle.putString("msg", msg);
message.setData(bundle);
MainActivity.getMsgHandler().sendMessage(message);
}
}