基于JAVA的聊天室,包含服务器、PC客户端、android客户端

发布时间:2020-05-01

概述

基于JAVA服务器、客户端以及android客户端的聊天室,这三端都是基于Netty框架处理的。为什么要是用Netty框架?简单说三点:1、并发高2、传输快3、封装好 。相对于java本身所提供的的API来说,Netty从各方面来说都完胜。当中也包含了QT的客户端,QT这个工具非常适合桌面开发!想学习的也可以尝试下

详细

概述

基于Netty框架多端通信写的,包含服务器、PC客户端、android客户端 (另有QT版本客户端)

一、需求

架设一个简单的服务器,实现多端数据互传,实现聊天功能

二、具体要求

聊天室功能

三、实现

(服务器、PC客户端)

1、建立IDEA  maven 项目

2、添加Netty依赖(pom.xml中添加)

(android客户端)

1、建立android项目

2、build.gradle中添配置Netty依赖

3、第三库使用butterknife 等等

四、主要代码结构

(服务器)

入口:ServerMain.java

UI:MainFrame.java

Netty:EchoServer.java /ServerHandler.java /ServerInializer.java

工具类:Util.java 等

(PC客户端)

入口:ClientMain.java

UI:MainFrame.java

Netty:EchoClient.java /ClientHandler.java /ClientInializer.java

工具类:Util.java OberverMange.java等

(android客户端)

入口:MainActivity.java

UI:acticity_main.xml

Netty:NettyClient.java /NettyClientHandler.java /NettyListener.java

javabean:RecevieDataBean.java/SendDataBean.java

工具类:Utils.java Const.java等

(QT版  PC客户端)

头文件:mainwindow.h

主文件:mainwindow.cpp

UI:mainwindow.UI

五、运行效果(多端展示)


六、部分代码演示


服务器部分代码:


package com.test;


import com.test.netty.EchoServer;
import com.test.ui.MainFrame;

/**
 *  开启服务器
 */
public class ServerMain {
    public static void main(String[] args) {
        ServerMain serverMain = new ServerMain();
        MainFrame mainFrame = new MainFrame(serverMain);
        mainFrame.setVisible(true);
        try {
            EchoServer.init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.test.netty;

import com.test.javabean.OfflineListMode;
import com.test.javabean.OnlineListMode;
import com.test.javabean.RecDataBean;
import com.test.util.LogM;
import com.test.util.ObserverManage;
import com.test.util.Util;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;


public class ServerHandler extends ChannelInboundHandlerAdapter {
    //所有与服务器建立连接的channel对象
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel channel = ctx.channel();
        ByteBuf buf = (ByteBuf)msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        LogM.i(body);
        // 数据处理一般在这里接收到,并去处理、
        // 我在这里仅仅是收到消息,并发给所有在线客户端
        //  如果要一对一私发消息,你也是在这里做处理,根据你的协议,来处理发给谁
        channelGroup.forEach(ch -> {  //把信息全部发出去,不发给发消息的客户端
            if(channel != ch){
                String msg2=body;
                ch.writeAndFlush(Unpooled.copiedBuffer(msg2.getBytes()));
            }
        });
        RecDataBean recDataBean=new RecDataBean();
        recDataBean.setMsg(Util.getData()+"\r\n" +body);
        ObserverManage.getObserver().setMessage(recDataBean); // 把信息传到窗口
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);

    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        super.handlerAdded(ctx);
        Channel channel = ctx.channel();
        channelGroup.add(channel);
        //此DEMO 直接通信,就在这里做在线设备
        OnlineListMode onlineListMode=new OnlineListMode();
        onlineListMode.setOnlineDeviceName(channel.remoteAddress().toString());
        ObserverManage.getObserver().setMessage(onlineListMode);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        super.handlerRemoved(ctx);
        Channel channel = ctx.channel();
        channelGroup.remove(channel);
        OfflineListMode offlineListMode=new OfflineListMode();
        offlineListMode.setOfflineDeviceName(channel.remoteAddress().toString());
        ObserverManage.getObserver().setMessage(offlineListMode);

    }
}


PC客户端部分代码:

package com.test;


import com.test.netty.EchoClient;
import com.test.ui.MainFrame;

/**
 *  开启客户端
 */
public class ClientMain {
    public static void main(String[] args) {
        ClientMain clientMain = new ClientMain();
        MainFrame mainFrame = new MainFrame(clientMain);
        mainFrame.setVisible(true);
        try {
            EchoClient.init();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
package com.test.netty;

import com.test.util.LogM;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.io.UnsupportedEncodingException;

/**
 * Handler implementation for the echo client.  It initiates the ping-pong
 * traffic between the echo client and server by sending the first message to
 * the server.
 */

import com.test.bean.MsgBean;
import com.test.util.Const;
import com.test.util.LogM;
import com.test.util.ObserverManage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.io.UnsupportedEncodingException;

public class ClientHandler extends ChannelInboundHandlerAdapter {

    private final ByteBuf firstMessage;
    private int lossConnectCount = 0;//心跳


    /**
     * Creates a client-side handler.
     */
    public ClientHandler() {
        firstMessage = Unpooled.buffer(EchoClient.SIZE);
        for (int i = 0; i < firstMessage.capacity(); i ++) {
            firstMessage.writeByte((byte) i);
        }
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        LogM.i("=============channelActive");
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws UnsupportedEncodingException {
//        if (!Const.getLoginState())
//            return;
        ByteBuf buf = (ByteBuf)msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        String body = new String(req, "UTF-8");
        MsgBean msgBean=new MsgBean();
        msgBean.setMsg(body);
        ObserverManage.getObserver().setMessage(msgBean);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
       ctx.flush();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        // Close the connection when an exception is raised.
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
        LogM.i("连接成功");
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer((Const.USES_NAME+":上线了").getBytes()));
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        super.channelUnregistered(ctx);
        LogM.i("连接断开");
    }
    /**
     * 心跳检测
     * @param ctx
     * @param evt
     * @throws Exception
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

    }

}

android端部分代码:

package com.example.nettyclientandroid;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.SystemClock;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.example.nettyclientandroid.R;
import com.example.nettyclientandroid.adapter.ReceiveDataAdapter;
import com.example.nettyclientandroid.javabean.RecevieDataBean;
import com.example.nettyclientandroid.javabean.SendDataBean;
import com.example.nettyclientandroid.tcp.NettyClient;
import com.example.nettyclientandroid.tcp.NettyListener;
import com.example.nettyclientandroid.util.Const;
import com.example.nettyclientandroid.util.LogM;
import com.example.nettyclientandroid.util.Utils;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import butterknife.BindView;
import butterknife.ButterKnife;
import butterknife.OnClick;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;


public class MainActivity extends Activity implements NettyListener {
    private NettyClient nettyClient;
    ReceiveDataAdapter adapter;
    RecyclerView recyclerView;
    private List<RecevieDataBean> dataBeanList = new ArrayList<>();
    public boolean pause = false;
    @BindView(R.id.tv_connect)
    TextView tv_connect;
    @BindView(R.id.edit_input)
    EditText editText;
    @BindView(R.id.btnSend)
    Button btnSend;

    @OnClick(R.id.btnSend)
    public void onBtnSendClicked() {
        String msg=editText.getText().toString();
        if (msg.isEmpty())
            return;
        SendDataBean dataBean=new SendDataBean();
        dataBean.setUser(Const.userName);
        dataBean.setMsg(msg);
        SendMsg(dataBean);
        editText.setText(null);
        RecevieDataBean recevieDataBean = new RecevieDataBean();
        recevieDataBean.setMsg(" " + Utils.getCurrentTime() + "  " +"我:"+msg);
        adapter.addData(recevieDataBean);
        if (!pause) {
            recyclerView.scrollToPosition(adapter.getItemCount() - 1);
        }
    }


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        initUi();
        initSocketTcp();//默认自动连接socket
    }

    public void initUi() {
        //这里应该是自定义的 View ,我这里没有花太多精力去做自定义的,就直接用简单的TextView 来显示吧
        recyclerView = (RecyclerView) findViewById(R.id.recycleView);
        LinearLayoutManager layoutManager = new LinearLayoutManager(this);
        recyclerView.setLayoutManager(layoutManager);
        adapter = new ReceiveDataAdapter(this, dataBeanList);
        recyclerView.setAdapter(adapter);
    }

    /*
   socket 端口号以及开始连接,配置接口监听
   */
    private void initSocketTcp() {
        nettyClient = new NettyClient(Const.Ip, Const.Port);
        if (!nettyClient.getConnectStatus()) {
            nettyClient.setListener(MainActivity.this);
            nettyClient.connect();
        } else {
            nettyClient.disconnect();
        }
    }

    String body = "";

    @Override
    public void onMessageResponse(Object msg) {
        ByteBuf buf = (ByteBuf) msg;
        byte[] req = new byte[buf.readableBytes()];
        buf.readBytes(req);
        try {
            body = new String(req, "UTF-8");
            LogM.e("===处理数据" + body);
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    RecevieDataBean recevieDataBean = new RecevieDataBean();
                    recevieDataBean.setMsg(" " + Utils.getCurrentTime() + "  " + body);
                    adapter.addData(recevieDataBean);
                    if (!pause) {
                        recyclerView.scrollToPosition(adapter.getItemCount() - 1);
                    }
                }
            });
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onServiceStatusConnectChanged(final int statusCode) {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                SystemClock.sleep(2000);
                if (statusCode == NettyListener.STATUS_CONNECT_SUCCESS) {
                    if (nettyClient.getConnectStatus()) {
                        Toast.makeText(MainActivity.this, "已连接到服务器!", Toast.LENGTH_SHORT).show();
                        tv_connect.setText("已连接到服务器!");
                    }
                } else {
                    if (!nettyClient.getConnectStatus()) {
                        tv_connect.setText("与服务器连接已断开! 尝试重连中...");
                        Toast.makeText(MainActivity.this, "与服务器连接已断开! 尝试重连中...", Toast.LENGTH_SHORT).show();
                    }
                }
            }
        });
    }

    /**
     * 发送数据
     *
     * @param dataBean
     */
    public void SendMsg(SendDataBean dataBean) {
        if (!nettyClient.getConnectStatus()) {//获取连接状态,必须连接才能点。
            Toast.makeText(MainActivity.this, "未连接到服务器", Toast.LENGTH_SHORT).show();
        } else {
            String user = dataBean.getUser();
            String msg = dataBean.getMsg();
            nettyClient.SENDCmd(user, msg, new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (future.isSuccess()) {
                        LogM.e("发送成功");
                    } else {
                        LogM.e("发送失败");
                    }
                }
            });
        }
    }

    private long firstTime = 0;

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
            long secondTime = System.currentTimeMillis();
            if (secondTime - firstTime > 2000) {
                Toast.makeText(MainActivity.this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
                firstTime = secondTime;
                return true;
            } else {
                System.exit(0);
            }
        }
        return super.onKeyUp(keyCode, event);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
    }


}


QT版本  PC客户端部分代码:

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QDate"
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    setWindowTitle(QString::fromUtf8("聊天室-PC客户端(QT)"));
    //初始化TCP
    tcpClient = new QTcpSocket(this);   //实例化
    tcpClient->abort();                 //取消原有连接
    tcpClient->connectToHost("127.0.0.1",8888); //IP以及端口号

    connect(tcpClient, SIGNAL(readyRead()), this, SLOT(ReadData()));
    connect(tcpClient, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(ReadError(QAbstractSocket::SocketError)));

    if (tcpClient->waitForConnected(30000))  // 连接成功
    {
        qDebug() << "已连接到服务器!" ;
    }else{
        qDebug() << "未连接服务器!" ;
    }

}

MainWindow::~MainWindow()
{
    delete this->tcpClient;
    delete ui;
}

/**
*   接收数据
*/
void MainWindow::ReadData()
{
    QByteArray buffer = tcpClient->readAll();
    if(!buffer.isEmpty())
    {
         qDebug() << "接收数据->"+buffer ;
         QDateTime curDataTime = QDateTime::currentDateTime();
         QString curTime = curDataTime.toString("yyyy-MM-dd hh:mm:ss");
         ui->textBrowser->append(curTime);
         ui->textBrowser->append(buffer);
    }
}

/**
*断开连接
*/
void MainWindow::ReadError(QAbstractSocket::SocketError)
{
    qDebug() << "与服务器连接已断开!" ;
    tcpClient->disconnectFromHost();
}


/**
 * @brief MainWindow::SendTcpData
 * @param msg
 * 发送数据
 */
void MainWindow::SendTcpData(QString msg){
    if(tcpClient->isOpen()){
        QByteArray sendMessage = msg.toUtf8();
        tcpClient->write(sendMessage);
        tcpClient->flush();
    }
}

void MainWindow::on_pushButton_clicked()
{
    QString msg=ui->lineEdit->text();
    SendTcpData(USERNAME+":"+msg);
    ui->lineEdit->setText(NULL);
    QDateTime curDataTime = QDateTime::currentDateTime();
    QString curTime = curDataTime.toString("yyyy-MM-dd hh:mm:ss");
    ui->textBrowser->append(curTime);
    ui->textBrowser->append("我:"+msg);
}


七、项目结构

1.png

ClientQT部分

2.png

NettyClient部分

3.png

NettyClientAndroid部分

4.png

NettyServer部分

5.png



本实例支付的费用只是购买源码的费用,如有疑问欢迎在文末留言交流,如需作者在线代码指导、定制等,在作者开启付费服务后,可以点击“购买服务”进行实时联系,请知悉,谢谢
手机上随时阅读、收藏该文章 ?请扫下方二维码