关于长连接
与Http连接相反,通过某种方式与服务器一直保持连接称之为长连接。底层都是基于TCP/IP协议,通过Socket、ServerSocket与服务期保持连接,服务端一般是通过ServerSocket建立监听,监听客户端与之连接,客户端通过Socket指定端口和地址与服务端进行连接。
长连接的意义
- 长连接实现服务端主动向客户端推送消息
- 减少对服务器的轮询,减小服务器的压力
- 通信效率高于Http
mina 的优势
- 非常适合C/S架构的通信框架
- apache 的一个开源项目,比较信赖
- 官网上有详细的学习资料供开发者学习
- 使用起来比较简单,一定程度上降低了学习的成本
mina 的核心类
- IoService 及其相关类
- IoAcceptor 及其相关类
- IoConnector 及其相关类
- Filter 及其相关类,LoggingFilter 记录 mina 所有日志,ProtocolCodecFilter 数据转化过滤器,主要定义与服务器进行通信的数据类型,有利于数据传递;CompressionFilter 数据压缩过滤器,支持数据压缩,有利于提高数据传输效率;SSLFilter 数据加密过滤器。也可以通过 IoFilterAdapter 来实现自己的过滤器
- IoSession 类主要与通信有关,当与服务器建立连接之后,mina 返回一个 IoSession 对象,通过该对象就可以与服务器端进行读写操作,如果不想进行读写操作,可选择关闭。IoSession 的其他设置:设置接收数据缓存区的大小、设置数据发送缓存区的大小、设置状态恢复时间、设置写超时时间,其中设置缓存区的大小主要是防止内存溢出,设置状态恢复时间就是不进行读写操作多长时间之后自动进入空闲状态,可节省系统资源
- Handler 类主要对 SessionCreated、SessionOpen、SessionClosed 等事件进行监听,messageReceived、messageSend事件的监听,exceptionCaught异常的捕获等。
mina 服务器的搭建
主要步骤
- 创建 IoAcceptor 监听对象
- 设置 I0Acceptor 监听对象,通过 IoAcceptor 监听对象获得过滤链,然后设置日志过滤器、数据转化过滤器,然后设置一个事件处理handler
- 创建一个类继承IoHandlerAdapter,主要负责 Session 对象的创建监听、消息发送和接收的监听
- 通过 IoAcceptor 监听对象获得 SessionConfig 设置读取缓存区的大小、设置状态恢复时间
- 通过 IoAcceptor 监听对象的 bind() 方法指定端口号进行监听
集成 mina 框架
首先 mina 官网下载 mina 的压缩包,然后解压导入对应的 jar 包,这里导入的 jar 包为 mina-core-2.0.16.jar 这是 mina 的核心包,而 mina 又依赖与 slf4j ,故需导入 slf4j-api-1.7.21.jar ,此时运行项目会出错,根据提示需导入
slf4j-nop-1.8.0-alpha2.jar,此时运行项目正常,说明导入 mina 框架已经集成到我们的项目中了,如下图如下:
代码参考
/**
* 使用Mina创建一个简单的通信服务器
* @author jzman
*/
public class MinaServer {
public static void main(String[] args) {
//创建监听对象
IoAcceptor acceptor = new NioSocketAcceptor();
//为监听对象添加日志过滤器、数据转换过滤器
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
acceptor.getFilterChain().addLast("protocal",
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
//设置事件处理的Handler
acceptor.setHandler(new DataHandler());
//设置读取数据缓存区的大小
acceptor.getSessionConfig().setReadBufferSize(1024);
//设置状态恢复时间
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
try {
//指定端口号进行监听
acceptor.bind(new InetSocketAddress(12345));
} catch (IOException e) {
e.printStackTrace();
System.out.println(e.toString());
}
System.out.println("启动服务...");
}
/**
* session对象的创建监听和消息的接收、发送的监听
* @author lcdn
*
*/
private static class DataHandler extends IoHandlerAdapter{
/**
* session的创建
*/
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
}
/**
* session的打开
*/
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
}
/**
* 消息的接收
*/
public void messageReceived(IoSession session, Object message) throws Exception {
super.messageReceived(session, message);
String info = message.toString();
Date date = new Date(System.currentTimeMillis());
SimpleDateFormat sdf = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
String time = sdf.format(date);
session.write(time);
System.out.println("接收到的消息:"+info);
}
/**
* 消息的发送
*/
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
}
/**
* session的关闭
*/
public void sessionClosed(IoSession session) throws Exception {
super.sessionClosed(session);
}
}
}
mina 客户端的搭建
主要步骤
- 创建一个 Service 用来与远程服务器连接
- 封装一个 ConnectManager 类用来提供与服务器连接的方法
- 在 Service 中启动线程,调用 ConnectManager 里面的方法完成连接的创建
集成 mina 框架
打开 Android studio 创建工程,导入slf4j-api-1.7.21.jar 、
mina-core-2.0.16.jar 或 slf4j-android-1.6.1-RC1.jar , 编译不出错说明将 mina 框架导入到项目中了,如下图所示:
代码参考
ConnectConfig
/**
* Created by lcdn on 2017/6/30 0030.
* 使用构建者模式进行网络连接的一些配置信息
*/
public class ConnectConfig {
private Context mContext;
private String ip;
private int port;
private int readBufferSize;
private long connectionTimeOut;
public Context getmContext() {
return mContext;
}
public void setmContext(Context mContext) {
this.mContext = mContext;
}
public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public int getReadBufferSize() {
return readBufferSize;
}
public void setReadBufferSize(int readBufferSize) {
this.readBufferSize = readBufferSize;
}
public long getConnectionTimeOut() {
return connectionTimeOut;
}
public void setConnectionTimeOut(long connectionTimeOut) {
this.connectionTimeOut = connectionTimeOut;
}
public static class Builder{
private Context mContext;
private String ip = "192.168.1.104";
private int port = 1234;
private int readBufferSize = 1024;
private long connectionTimeOut = 10000;
public Builder(Context context){
this.mContext = 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 timeOut){
this.connectionTimeOut = timeOut;
return this;
}
private void applyConnectConfig(ConnectConfig config){
config.mContext = this.mContext;
config.ip = this.ip;
config.port = this.port;
config.readBufferSize = this.readBufferSize;
config.connectionTimeOut = this.connectionTimeOut;
}
public ConnectConfig builder(){
ConnectConfig connectConfig = new ConnectConfig();
applyConnectConfig(connectConfig);
return connectConfig;
}
}
}
ConnectManager
/**
* Created by jzman on 2017/6/30 0030.
* 封装与服务器连接相关的方法
*/
public class ConnectManager {
public static final String BROADCAST_MINA_ACTION = "com.manu.mina.BROADCAST_MINA_ACTION";
private ConnectConfig mConnectConfig;//网络连接的配置信息
private WeakReference<Context> mContextWeakReference;
private NioSocketConnector mNioSocketConnector;
private InetSocketAddress mInetSocketAddress;
private IoSession mIoSession;
public ConnectManager(ConnectConfig config){
this.mConnectConfig = config;
this.mContextWeakReference = new WeakReference<>(config.getmContext());
init();
}
private void init() {
//根据主机名和端口号创建地址对象
mInetSocketAddress = new InetSocketAddress(mConnectConfig.getIp(),mConnectConfig.getPort());
//创建连接对象
mNioSocketConnector = new NioSocketConnector();
//设置读缓存大小
mNioSocketConnector.getSessionConfig().setReadBufferSize(mConnectConfig.getReadBufferSize());
//设日志过滤器、数据过滤器
mNioSocketConnector.getFilterChain().addLast("logger",new LoggingFilter());
mNioSocketConnector.getFilterChain().addLast("protocol",
new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
mNioSocketConnector.setHandler(new DataHandler(mContextWeakReference.get()));
//为连接对象设置默认远程地址(必须)
mNioSocketConnector.setDefaultRemoteAddress(mInetSocketAddress);
}
/**
* 是否连接成功
* @return
*/
public boolean connect(){
try{
ConnectFuture future = mNioSocketConnector.connect();
future.awaitUninterruptibly();
mIoSession = future.getSession();
//将获取到的 Session 保存在 SessionManager 中
SessionManager.getInstance().setSession(mIoSession);
}catch(Exception e){
Log.i("mina:","连接失败");
Log.i("mina:",e.toString());
e.printStackTrace();
return false;
}
return mIoSession != null;
}
/**
* 关闭连接
*/
public void disConnect(){
mNioSocketConnector.dispose();
mNioSocketConnector = null;
mInetSocketAddress = null;
mIoSession = null;
mContextWeakReference = null;
}
private static class DataHandler extends IoHandlerAdapter {
private Context mContext;
public DataHandler(Context context) {
this.mContext = context;
}
@Override
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
//将session保存在session manager中,从而可以向服务器发送消息
Log.i("sessionOpened:","");
Log.i("sessionOpened:",session.toString());
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
//接收消息
if (mContext!=null){
//将接受到的消息广播出去
Intent intent = new Intent(BROADCAST_MINA_ACTION);
intent.putExtra("message",message.toString());
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}
}
}
}
ConnectService
/**
* Created by jzman on 2017/7/5 0005.
* 用来连接的服务
*/
public class ConnectService extends Service {
private ConnectRunnable runnable;
@Override
public void onCreate() {
super.onCreate();
Log.i("onCreate:","");
runnable = new ConnectRunnable(getApplicationContext());
new Thread(runnable).start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
runnable.disConnect();
runnable = null;
}
/**
* 创建连接服务器的线程
*/
class ConnectRunnable implements Runnable{
private Context mContext;
private boolean isConnect;
private ConnectManager mManager;
ConnectRunnable(Context context){
this.mContext = context;
ConnectConfig config = new ConnectConfig.Builder(mContext)
.setIp("192.168.1.104")
.setPort(12345)
.setreadBufferSize(1024)
.setConnectionTimeOut(10000)
.builder();
mManager = new ConnectManager(config);
}
@Override
public void run() {
for( ; ; ){
isConnect = mManager.connect();
System.out.println("isConnect:"+isConnect);
if (isConnect){
Log.i("mina:","连接成功");
break;
}
try{
Log.i("mina:","重新连接");
Thread.sleep(3000);
}catch(Exception e){
Log.i("mina:","连接失败");
Log.i("mina:",e.toString());
}
}
}
//关闭连接
public void disConnect(){
mManager.disConnect();
}
}
}
SessionManager
/**
* Created by jzman on 2017/7/5 0005.
* Session 管理器
*/
public class SessionManager {
private static SessionManager mInstance = null;
private IoSession mSession;//客户端与服务器端之间的通信对象
public static SessionManager getInstance(){
if (mInstance == null){
synchronized (SessionManager.class){
if (mInstance == null){
mInstance = new SessionManager();
}
}
}
return mInstance;
}
private SessionManager(){
}
public void setSession(IoSession session){
this.mSession = session;
}
/**
* 将对象写到服务器
* @param msg
*/
public void writeToServer(Object msg){
Log.i("writeToServer:",msg.toString());
if (mSession!=null){
Log.i("writeToServer:","msg");
mSession.write(msg);
}
}
/**
* 关闭session
*/
public void closeSession(){
if (mSession!=null){
mSession.closeOnFlush();
}
}
/**
* 移除session
*/
public void removeSession(){
mSession = null;
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.manu.minademo.MainActivity">
<TextView
android:id="@+id/tv_startConnect"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:text="启动mina服务连接服务器端"
android:padding="10dp"
android:background="#60ed24"
android:textSize="18sp"/>
<TextView
android:id="@+id/tv_sendMessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:text="向mina服务端发送消息"
android:padding="10dp"
android:background="#60ed24"
android:textSize="18sp"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginLeft="12dp"
android:layout_marginRight="12dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="from nina server:"
android:padding="10dp"
android:background="#60ed24"
android:textSize="18sp"/>
<TextView
android:id="@+id/tv_receiveMessage"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:padding="10dp"
android:background="#60ed24"
android:textSize="18sp"
android:textColor="#ffffff"/>
</LinearLayout>
</LinearLayout>
MainActivity
public class MainActivity extends AppCompatActivity implements
View.OnClickListener{
private TextView tv_startConnect;
private TextView tv_sendMessage;
private TextView tv_receiveMessage;
private MinaBroadcastReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
tv_startConnect.setOnClickListener(this);
tv_sendMessage.setOnClickListener(this);
receiver = new MinaBroadcastReceiver();
registerReceiver();
}
private void initView() {
tv_startConnect = (TextView) findViewById(R.id.tv_startConnect);
tv_sendMessage = (TextView) findViewById(R.id.tv_sendMessage);
tv_receiveMessage = (TextView) findViewById(R.id.tv_receiveMessage);
}
@Override
protected void onDestroy() {
super.onDestroy();
stopService(new Intent(this,ConnectService.class));
unRegisterReceiver();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.tv_startConnect:
Intent intent = new Intent(this,ConnectService.class);
startService(intent);
break;
case R.id.tv_sendMessage:
SessionManager.getInstance().writeToServer("这是客户端发送的消息...");
break;
}
}
/**
* 注册广播接收器
*/
private void registerReceiver(){
IntentFilter filter = new IntentFilter();
filter.addAction("com.manu.mina.BROADCAST_MINA_ACTION");
LocalBroadcastManager.getInstance(this).registerReceiver(receiver,filter);
}
/**
* 解除广播
*/
private void unRegisterReceiver(){
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
}
/**
* 定义广播接收器
*/
private class MinaBroadcastReceiver extends BroadcastReceiver{
public MinaBroadcastReceiver(){
}
@Override
public void onReceive(Context context, Intent intent) {
tv_receiveMessage.setText(intent.getStringExtra("message"));
}
}
}
测试效果
<完>
热门评论
服务端启动 报这个错:错误: 找不到或无法加载主类 org.apache.log4j.net.JMSSink
服务端启动 报这个错:错误: 找不到或无法加载主类 org.apache.log4j.net.JMSSink
mina在与客户端链接的时候,过几秒客户端自动断开了,是什么原因呀