一、概述
今天在工作一朋友Q我,问我长连接一般用什么做,我说我用的Mina,朋友表示没听过,于是打算写一篇相关的博文供大家讨论。
首先什么是mina?
它的官方定义:一个能够帮助用户开发高性能和高伸缩性网络应用程序的框架。它通过Java nio技术基于TCP/IP和UDP/IP协议提供了抽象的、事件驱动的、异步的API。
简单来说尼,就是一个优化过的长连接框架。
好了,先上图:
服务端的控制台显示
客户端发送“你好”,服务端控制台显示“服务端接收的消息:你好”,然后再返回字段“服务端给返回的消息:你好”客户端接收并显示。
好了,演示完毕,下面开始开启我们的代码之旅吧。
二、服务端搭建
/**
* Mina长连接服务端搭建
*
* @author 刘洋巴金
* @date 2017-4-6
* */
public class MinaService {
public static void main(String[] args){
// 继承IoService,服务器端接收器
IoAcceptor acceptor = new NioSocketAcceptor();
// 添加过滤器
acceptor.getFilterChain().addLast("logf", new LoggingFilter()); // 日志过滤器
acceptor.getFilterChain().addLast("objType", new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); // 只能传输序列化后的对象
// 回调
acceptor.setHandler(new MyHandler());
// 进行一些初始化配置
acceptor.getSessionConfig().setReadBufferSize(2048); // 设置读缓存区大小
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10); // 如果10秒钟没有任何读写,就设置成空闲状态。 BOTH_IDLE(读和写)
// 设置监听端口号,开始监听了
try {
acceptor.bind(new InetSocketAddress(8888));
System.out.println("启动成功");
} catch (Exception e) {
e.printStackTrace();
}
}
IoAcceptor:它主要负责在一个客户端和该服务之间创建连接。NioSocketAcceptor创建一个基于TCP/IP的非阻塞的server的socket。
acceptor.getFilterChain().addLast(...):添加一些过滤链。
handler:是个回调
然后进行一些初始化配置,比如超过几秒没有通讯就设置成空闲。
然后acceptor.bind(new InetSocketAddress(8888)); 设置监听的端口号,开始监听了。
/**
* 负责session的创建及消息发送和接收的监听
* */
private static class MyHandler extends IoHandlerAdapter{
// session创建时回调
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
System.out.println("sessionCreated " );
}
// session打开时回调
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
System.out.println("sessionOpened " );
}
// 消息接收时回调
public void messageReceived(IoSession session, Object message)throws Exception {
super.messageReceived(session, message);
String str = message.toString();
session.write("服务端给返回的消息:"+str+"\n"); // 给客户端返回
System.out.println("服务端接收消息: " + str);
}
// 消息发送时回调
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
System.out.println("messageSent " );
}
// session关闭时回调
public void sessionClosed(IoSession session) throws Exception {
super.sessionClosed(session);
}
}
handler用于回调各种状态。messageReceived 当客户端发送的消息到达时回调session.write(""); 给客户端返回
三、客户端搭建
/**
* 参数配置
*
* @author 刘洋巴金
* @date 2017-4-6
* */
public class ConnectionConfig {
private Context context;
private String ip;
private int port;
private int readBufferSize;
private long connectionTimeout;
public Context getContext() {
return context;
}
public String getIp() {
return ip;
}
public int getPort() {
return port;
}
public int getReadBufferSize() {
return readBufferSize;
}
public long getConnectionTimeout() {
return connectionTimeout;
}
public static class Builder{
private Context context;
private String ip;
private int port;
private int readBufferSize;
private long connectionTimeout;
public Builder(Context context){
this.context = context;
}
public Builder setIp(String ip) {
this.ip = ip;
return this;
}
public Builder setPort(int port) {
this.port = port;
return this;
}
public Builder setReadBufferSize(int readBufferSize) {
this.readBufferSize = readBufferSize;
return this;
}
public Builder setConnectionTimeout(long connectionTimeout) {
this.connectionTimeout = connectionTimeout;
return this;
}
public ConnectionConfig builder(){
ConnectionConfig config = new ConnectionConfig();
config.context = this.context;
config.ip = this.ip;
config.port = this.port;
config.readBufferSize = this.readBufferSize;
config.connectionTimeout = this.connectionTimeout;
return config;
}
}
}
首先先创建个ConnectionConfig,该类用了构建者模式,该类主要作用是设置一些常用的属性。
/**
* 连接管理器
*
* @author 刘洋巴金
* @date 2017-4-6
* */
public class ConnectionManager {
private ConnectionConfig config;
private WeakReference<Context> mContext;
private IoConnector mConnector;
private SocketAddress mAddress;
public ConnectionManager(ConnectionConfig config) {
this.config = config;
mContext = new WeakReference<Context>(config.getContext());
// 初始化
init();
}
/**
* 初始化
* */
private void init() {
// 继承IoService,客户端连接器
mConnector = new NioSocketConnector();
// 添加过滤器
mConnector.getFilterChain().addLast("logf", new LoggingFilter()); // 日志过滤器
mConnector.getFilterChain().addLast("objType", new ProtocolCodecFilter(new ObjectSerializationCodecFactory())); // 只能传输序列化后的对象
// 回调
mConnector.setHandler(new MyHandler(mContext.get()));
// 进行一些初始化配置
mConnector.getSessionConfig().setReadBufferSize(2048); // 设置读缓存区大小
// 设置IP地址
mAddress = new InetSocketAddress(config.getIp(), config.getPort());
mConnector.setDefaultRemoteAddress(mAddress);
}
创建连接管理者,基本配置与服务端搭建类似
NioSocketConnector创建客户端,然后设置过滤链,然后设置ip和端口号,config就是上面我们自己建的配置文件。然后设置handler进行回调。
/**
* 负责session的创建及消息发送和接收的监听
* */
private static class MyHandler extends IoHandlerAdapter{
private Context mContext;
private MyHandler(Context context){
this.mContext = context;
}
// session打开时回调
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
}
// 消息接收时回调
public void messageReceived(IoSession session, Object message)throws Exception {
super.messageReceived(session, message);
if(mContext != null){
Intent intent = new Intent("com.bs.myMsg");
intent.putExtra("message", message.toString());
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}
}
// 消息发送时回调
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
}
}
messageReceived接收服务端返回的消息,然后启动本地广播,本地广播的使用和正常广播使用一样,只是本地广播LocalBroadcastManager.getInstance(mContext)只能被本应用接收广播。其他应用接收不到该广播。
/**
* 服务
*
* @author 刘洋巴金
* @date 2017-4-6
* */
public class MyService extends Service{
@Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
}
private ConnectionManager mManager; // 连接管理
private ConnectThread mConnectThread; // 线程
@Override
public void onCreate() {
super.onCreate();
mConnectThread = new ConnectThread("mina", getApplicationContext());
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
mConnectThread.start();
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
mConnectThread.disConnect();
mConnectThread = null;
}
class ConnectThread extends HandlerThread{
public ConnectThread(String name, Context context) {
super(name);
// 配置
ConnectionConfig config = new ConnectionConfig.Builder(context)
.setIp("192.168.0.103")
.setPort(8888)
.setReadBufferSize(2048)
.setConnectionTimeout(10)
.builder();
// 连接管理器
mManager = new ConnectionManager(config);
}
@Override
protected void onLooperPrepared() {
// TODO Auto-generated method stub
super.onLooperPrepared();
// 异步进行连接
for (;;) {
// 如果连接成功,则跳出循环
if(mManager.connect()){
Toast.makeText(getApplicationContext(), "连接成功", Toast.LENGTH_SHORT).show();
break;
}
}
// 3秒连接一次
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
// 用于断开连接
public void disConnect(){
mManager.disConnect();
}
}
}
然后启动我们的服务,因为连接服务端是耗时操作,所以要放到子线程,我用的是HandlerThread,当然也可以自己写线程。
onLooperPrepared相当于run方法,我们做的操作是,在这里面一直进行连接,如果连接失败就继续连接,直至连接成功为止。
/**
* 进行连接,并获取Session
* */
public boolean connect() {
try {
ConnectFuture connectFuture = mConnector.connect(); // 可以查询连接操作的状态
connectFuture.awaitUninterruptibly(); // 它会将程序阻塞住,直到连接创建好,所以,当这行代码结束后,
// 就可以直接获取session。
IoSession mSession = connectFuture.getSession();
SessionManager.getInstance().setSession(mSession); // 存储session
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
ConnectionManager 中的connect方法,进行连接服务端,如果连接成功了,就返回true
这样就获取到了session,然我们存储起来,以便使用。
/**
* 会话管理器
*
* @author 刘洋巴金
* @date 2017-4-6
* */
public class SessionManager {
private static SessionManager instance = new SessionManager();
private SessionManager(){
}
public static SessionManager getInstance() {
return instance;
}
private IoSession session;
public IoSession getSession() {
return session;
}
public void setSession(IoSession session) {
this.session = session;
}
/**
* 写消息
* */
public void writeMag(String msg){
if(session != null){
session.write(msg);
}
}
/**
* 关闭
* */
public void closeSession(){
if(session != null){
session.closeOnFlush();
session = null;
}
}
}
然后是MainActivity的2个按钮的点击
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_conn: // 开启服务
Intent in = new Intent(MainActivity.this, MyService.class);
startService(in);
break;
case R.id.btn_sendMsg: // 发送消息
// 文本消息
String msg = et_msg.getText().toString();
SessionManager.getInstance().writeMag(msg); // 发送消息
break;
default:
break;
}
}
先开启服务,连接服务端,然后是取出我们存储的session,给服务端发送消息。
然后再注册广播,接收ConnectionManager发送过来的广播。
/**
* 注册消息广播
* */
private void registerMsgReceiver() {
mMsgReceiver = new MsgReceiver();
IntentFilter filter = new IntentFilter("com.bs.myMsg");
LocalBroadcastManager.getInstance(MainActivity.this).registerReceiver(mMsgReceiver, filter);
}
/**
* 广播接收
* */
class MsgReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
String msgStr = intent.getStringExtra("message");
rmsg += msgStr+"\n";
tv_msg_show.setText(rmsg);
}
}
OK了。
四、mina流程
1.远程服务器通过IoService与客户端建立连接,然后得到session。
2.远程服务器发数据到我们的session
3.然后我们的session会把我们的数据发送给IoFiterChain(过滤链)中进行过滤
4.过滤链将符合条件的数据发送到Application layer(数据处理器)中,客户端做的就是根据IoHandler书写自己的业务逻辑。
五、demo
※1.首先记得改端口号和IP
2.记得电脑和手机要在一个IP段上,也就是同一个wife