Mina网络通信框架(续)

在看到本文之前,如果读者没看过笔者的前文Mina网络通信框架 ,请先翻阅。

在本节中,我将用 Mina 框架,同时实现服务器和客户端,实现客户端与客户端之间的 IM 通信,使用 Java JDBC 接口连入 MySQL 数据库,实现一个具有登录功能的简易 IM 聊天工具。

首先,我们来看看效果图:

使用 Navicat 可视化操作工具创建并连接数据库


Paste_Image.png

启动 Mina 服务器


Paste_Image.png

启动登录界面


Paste_Image.png

注册失败演示


Paste_Image.png

注册成功演示


Paste_Image.png
Paste_Image.png

观察数据库,发现添加了新用户


Paste_Image.png

登录后的聊天窗口


Paste_Image.png

再登录一个用户


Paste_Image.png

发送消息演示

Paste_Image.png

下面,笔者贴出几个核心代码部分:

数据库登录处理逻辑

package database;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

import org.apache.mina.core.session.IoSession;

import util.MessageUtil;
import util.SessionUtil;
import common.Common;

/**
 * 管理客户端的数据库
 */
public class ClientJDBC {

    public ClientJDBC() {
        try {
            // 加载JDBC Driver
            Class.forName("com.mysql.jdbc.Driver");

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * <pre>
     * 1、查询是否已存在此用户
     * 2、注册,添加新数据
     */
    public void addNewData(String name, String password, IoSession loginSession) {
        try {
            // 建立连接
            Connection connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/clientdb", "root", "");

            // 创建Statement
            Statement statement = connection.createStatement();
            // 查询是否已存在此用户
            String selectSQL = "select password from client where name=" + "'"
                    + name + "'";
            ResultSet set = statement.executeQuery(selectSQL);
            // 如果不存在此用户
            if (!set.next()) {
                // 添加数据
                PreparedStatement preparedStatement = connection
                        .prepareStatement("insert into client values(null,?,?)");
                preparedStatement.setString(1, name);
                preparedStatement.setString(2, password);
                preparedStatement.executeUpdate();
                // 提示用户注册成功
                loginSession.write(Common.CANREG);
            } else {
                // 提示用户已注册
                loginSession.write(Common.CANTRE);
            }
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    /**
     * <pre>
     * 1、查询帐号密码是否匹配
     * 2、查询是否用户已登录
     * 3、登录
     */
    public boolean login(String name, String password, IoSession userSession) {
        try {
            // 建立连接
            Connection connection = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/clientdb", "root", "");

            // 创建Statement
            Statement statement = connection.createStatement();

            // 查询是否已存在此用户
            String selectSQL = "select password from client where name=" + "'"
                    + name + "'";
            ResultSet set = statement.executeQuery(selectSQL);
            // 如果存在此用户
            if (set.next()) {
                // 密码匹配成功
                if (set.getString("password").equals(password)) {
                    // 如果用户没登录
                    if (SessionUtil.getSessionFromName(name) == null) {
                        // 存储用户名
                        userSession.setAttribute("name", name);
                        // 登录成功
                        String s = name;
                        String message = MessageUtil.createSendMessage(Common.LOGIN_SUCCESS, s);
                        userSession.write(message);
                        // 发送在线列表给所有客户端
                        SessionUtil.sendClientListToAll();
                    } else {
                        // 提示用户已登录
                        userSession.write(Common.IS_LOGIN);
                    }
                } else {
                    // 提示帐号和密码不匹配
                    userSession.write(Common.ISNT_MATCH);
                }
            } else {
                // 提示帐号和密码不匹配
                userSession.write(Common.ISNT_MATCH);
            }
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return true;
    }

}

服务器端Handler处理逻辑
注意,此处继承的是StreamIoHandler,为了后续对传输文件进行操作

package server;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.handler.stream.StreamIoHandler;

import thread.IoStreamThreadWork;
import util.SessionUtil;
import common.Common;

/**
 * Mina Handler
 */
public class ServerHandler extends StreamIoHandler {

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) {
        System.out.println(session.getRemoteAddress() + "   "
                + "exceptionCaught");
        System.out.println(cause);
    }

    /**
     * mina只能接收以换行符结束的信息
     */
    @Override
    public void messageReceived(IoSession session, Object message) {
        System.out.println(session.getRemoteAddress() + "   "
                + "messageReceived");

        // 解析信息
        String result = (String) message;

        // 注册信息
        if (result.startsWith(Common.REGISTER) && result.length() > 10) {
            String suffix = result.substring(10);
            String[] array = suffix.split(",");
            MinaServer.clientJDBC.addNewData(array[0], array[1], session);
        }
        // 登录信息
        else if (result.startsWith(Common.LOGIN) && result.length() > 10) {
            String suffix = result.substring(10);
            String[] array = suffix.split(",");
            MinaServer.clientJDBC.login(array[0], array[1], session);
        }
        // 登出信息
        else if (result.equals(Common.LOGOUT) && result.length() == 6) {
            // 关闭当前session
            session.close();
            // 发送在线列表给所有客户端
            SessionUtil.sendClientListToAll();
        }
        // 用户之间发送消息
        else if (result.startsWith(Common.CLIENT) && result.length() > 10) {
            String suffix = result.substring(10);
            // 分隔选中用户名和要发送的消息
            String[] array = suffix.split(Common.SEPARATOR);
            // 向指定用户发送消息
            if (session.getAttribute("name") != null)
                SessionUtil.sendToSelectedClient(
                        (String) session.getAttribute("name"), array[0],
                        array[1]);
        }
    }

    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        System.out.println(session.getRemoteAddress() + "   " + "messageSent");
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        System.out
                .println(session.getRemoteAddress() + "   " + "sessionClosed");
    }

    /**
     * 用户创建的时候保留会话的ip和端口
     */
    @Override
    public void sessionCreated(IoSession session) throws Exception {
        System.out.println(session.getRemoteAddress() + "   "
                + "sessionCreated");
        // 提取客户端ip和端口
        String s = session.getRemoteAddress() + "";
        String port = s.substring(s.indexOf(":") + 1);
        String ip = s.substring((s.indexOf("/") + 1),
                s.length() - 1 - port.length());
        // 存储ip和端口
        if (session.getAttribute("ip") == null)
            session.setAttribute("ip", ip);
        if (session.getAttribute("port") == null)
            session.setAttribute("port", port);
    }

    /**
     * 会话空闲状态
     */
    @Override
    public void sessionIdle(IoSession session, IdleStatus status) {
        System.out.println(session.getRemoteAddress() + "   " + "sessionIdle");
    }

    @Override
    public void sessionOpened(IoSession session) {
        System.out
                .println(session.getRemoteAddress() + "   " + "sessionOpened");
    }

    @Override
    protected void processStreamIo(IoSession session, InputStream input,
            OutputStream output) {
        System.out.println(session.getRemoteAddress() + "   "
                + "processStreamIo");
        // 设定一个线程池
        // 参数说明:最少数量3,最大数量6 空闲时间 3秒
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(3, 6, 3,
                TimeUnit.SECONDS,
                // 缓冲队列为3
                new ArrayBlockingQueue<Runnable>(3),
                // 抛弃旧的任务
                new ThreadPoolExecutor.DiscardOldestPolicy());
        FileOutputStream fos = null;
        // 此处路径如何动态设定。
        File receiveFile = new File("F:\\111.jks");

        try {
            fos = new FileOutputStream(receiveFile);
        } catch (FileNotFoundException e1) {
            e1.printStackTrace();
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 将线程放入线程池 当连接很多时候可以通过线程池处理
        threadPool.execute(new IoStreamThreadWork(input, fos));
    }
}

客户端Handler处理逻辑

package client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.swing.DefaultListModel;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;

import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.handler.stream.StreamIoHandler;

import thread.IoStreamThreadWork;

import common.Common;

import frame.ChatFrame;
import frame.LoginFrame;

/**
 * Mina Handler
 */
// IoHandlerAdapter
public class ClientHandler extends StreamIoHandler {

    @Override
    public void exceptionCaught(IoSession session, Throwable cause) {
        System.out.println(session.getLocalAddress() + "   "
                + "exceptionCaught");
        System.out.println(cause);
    }

    /**
     * mina只能接收以换行符结束的信息
     */
    @Override
    public void messageReceived(IoSession session, Object message) {
        System.out.println(session.getLocalAddress() + "   "
                + "messageReceived");

        // 解析信息
        String result = (String) message;

        // 注册成功
        if (result.equals(Common.CANREG) && result.length() == 6) {
            JOptionPane.showMessageDialog(null, "注册成功!");
        }
        // 用户已存在注册失败
        else if (result.equals(Common.CANTRE) && result.length() == 6) {
            JOptionPane.showMessageDialog(null, "用户已存在!");
        }
        // 用户已登录,登录失败
        else if (result.equals(Common.IS_LOGIN) && result.length() == 6) {
            JOptionPane.showMessageDialog(null, "用户已登录!");
            session.close();// 关闭这个Session
        }
        // 帐号和密码不匹配,登录失败
        else if (result.equals(Common.ISNT_MATCH) && result.length() == 6) {
            JOptionPane.showMessageDialog(null, "帐号和密码不匹配!");
            session.close();// 关闭这个Session
        }
        // 登录成功
        else if (result.startsWith(Common.LOGIN_SUCCESS)
                && result.length() > 10) {
            String suffix = result.substring(10);
            // 从Attribute中取出登录窗口
            LoginFrame frame = (LoginFrame) session.getAttribute("loginFrame");
            // 关闭登录窗口
            frame.dispose();
            // 打开聊天窗口
            new ChatFrame(session).setTitle("用户" + suffix + "的聊天窗口");
        }
        // 服务器发送在线列表消息
        else if (result.startsWith(Common.SERVER) && result.length() > 10) {
            String suffix = result.substring(10);
            String[] array = suffix.split(",");
            // 判断空,因为有loginSession
            if (session.getAttribute("clientListModel") != null) {
                // 取出clientListModel
                DefaultListModel<String> clientListModel = (DefaultListModel<String>) (session
                        .getAttribute("clientListModel"));
                // 清空原视图
                clientListModel.removeAllElements();
                // 重新加入在线用户列表
                for (String clientName : array)
                    clientListModel.addElement(clientName);
            }
        }
        // 客户发送过来的消息
        else if (result.startsWith(Common.CLIENT) && result.length() > 10) {
            String suffix = result.substring(10);
            // if (session.getAttribute("receivedMessage") != null)
            JTextArea jta = (JTextArea) session.getAttribute("receivedMessage");
            jta.append(suffix + "\n");
            // 滚动到底端
            jta.setCaretPosition(jta.getText().length());
        }
    }

    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        System.out.println(session.getLocalAddress() + "   " + "messageSent");
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        System.out.println(session.getLocalAddress() + "   " + "sessionClosed");
    }

    @Override
    public void sessionCreated(IoSession session) throws Exception {
        System.out
                .println(session.getLocalAddress() + "   " + "sessionCreated");
    }

    /**
     * 会话空闲状态
     */
    @Override
    public void sessionIdle(IoSession session, IdleStatus status) {
        System.out.println(session.getLocalAddress() + "   " + "sessionIdle");
    }

    @Override
    public void sessionOpened(IoSession session) {
        System.out.println(session.getLocalAddress() + "   " + "sessionOpened");
    }

    @Override
    protected void processStreamIo(IoSession session, InputStream input,
            OutputStream output) {
        System.out.println(session.getLocalAddress() + "   "
                + "processStreamIo");
        // 客户端发送文件
        File sendFile = new File("e:\\MyKey.jks");
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(sendFile);

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        // 放入线程让其执行
        // 客户端一般都用一个线程实现即可 不用线程池
        new IoStreamThreadWork(fis, output).start();
        return;

    }
}

在 Mina 的使用过程中,应注意以下问题:

【1】客户端IoSession.setAttribute后,服务器IoSession.getAttribute为null,这两个session虽然getId一样,但不是同一个session

【2】使用TextLineCodecFactory,messageReceived方法默认只能接收以换行符结束的信息

【3】使用getAttribute时应注意判断空指针:if (session.getAttribute("name") != null && session.getAttribute("name").equals(name))
否则不会报错,但下面的程序段会终止执行。可在exceptionCaught方法中catch到异常

【4】每个客户端对应一个ConnectFuture,每个ConnectFuture对应一个IoSession

【5】可以巧用session.setAttribute("loginFrame", LoginFrame.this);来进行窗口的传递,注意:如果在监听中直接传this,要注意是不是LoginFrame.this

【6】mina不能动态修改acceptor的ProtocolCodecFilter和Handler

【7】传输文件可通过StreamIoHandler和MessageEncoder的方式

整个案例的源码及相关 jar 包在百度云中给出链接 Mina,读者如要运行项目,还需自行安装 MySQL 创建数据库。

谢谢您的关注和支持!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容

  • 会话(Session)跟踪是Web程序中常用的技术,用来跟踪用户的整个会话。常用的会话跟踪技术是Cookie与Se...
    chinariver阅读 5,593评论 1 49
  • 目录Cookie机制什么是CookieCookie的不可跨域名性Unicode编码:保存中文BASE64编码:保存...
    Tomatoro阅读 16,909评论 7 186
  • 有些事情的努力并不一定会很有用,但我不能否定它们
    杰star爱番石榴阅读 111评论 0 1
  • 今天早上起来,我感到十分的轻松。心中想了一下,今天有什么任务呢?好像没有什么任务。于是我就从床上爬了起来,...
    0328周宇同洲雨阅读 230评论 2 1
  • 人生旅途的行囊中,或许我们早已装满了许多东西,但别忘记自己生命旅程中最重要的东西,生命的旅途不可能一帆风顺...
    懵懂情愫化诗雨阅读 235评论 0 1