基于JAVA的聊天室,包含服务器、PC客户端、android客户端
概述
详细
概述
基于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); }
七、项目结构
ClientQT部分
NettyClient部分
NettyClientAndroid部分
NettyServer部分