如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天

手把手教你用Java语言在IdeaAndroid中分别建立服务端客户端实现局域网聊天

目录

文章目录

  • 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天
    • **目录**
    • @[toc]
    • **基本实现**
    • **问题分析**
    • **服务端**
      • Idea:
        • 结构预览
        • Server类
          • 代码解读
        • ServerReader类
          • 代码解读
    • **客户端**
      • Android:
        • 结构预览
        • 布局文件 activity_main.xml
          • 代码解读
        • MainActivity
          • 代码解读
        • 配置网络
        • 配置网络

基本实现

  • 实现客户端和服务端之间的通信

  • 实现服务端转接客户端消息,并发送给其他局域网在线成员

  • 实现服务端接收客户端消息,并判断相应类型,做出对应应答

  • 实现客户端消息发送者 发送时间 当前在线用户基本可视化


问题分析

  1. 服务端开发

    • 在IntelliJ IDEA中创建一个Java项目。
    • 实现一个简单的TCP服务器,能够接收客户端消息并回显(或广播)消息给所有已连接的客户端。
  2. 客户端开发

    • 在Android Studio中创建一个Android项目。
    • 实现一个简单的TCP客户端,能够发送消息到服务端并显示从服务端接收到的消息。
  3. 网络通信

    • 确保服务端和客户端在同一局域网内,并且客户端可以正确连接到服务端。
    • 处理多线程问题,确保服务端可以同时处理多个客户端连接。

服务端

Idea:

结构预览

在这里插入图片描述

在Idea中创建一个名为Server的类

Server类
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;public class Server {// 定义一个集合容器存储所有登陆进来的客户端,以便群发消息给他们// 定义一个Map集合,键是存储客户端的管道,值是这个管道的名称public static final Map<Socket, String> onLineSockets = new HashMap<>();public static void main(String[] args) throws Exception {System.out.println("服务端启动");// 1. 创建服务端ServerSocket对象,绑定端口号,监听客户端连接ServerSocket serverSocket = new ServerSocket(9999);while (true) {System.out.println("等待客户端连接....");Socket socket = serverSocket.accept();new ServerReader(socket).start();System.out.println("一个客户端上线了....  IP:" + socket.getInetAddress().getHostAddress());}}
}
代码解读
  • 定义一个Map集合(所有局域网用户共享集合)存储所有登陆进来的客户端,以便群发消息给他们,是存储客户端的管道,是这个管道的名称(说白了就是前一个是主键,后一个)
public static final Map<Socket, String> onLineSockets = new HashMap<>();
  • 创建服务端ServerSocket对象,绑定端口号,监听客户端连接
ServerSocket serverSocket = new ServerSocket(9999);
  • 端口号选择建议范围**(1024~65535)**,其中绝大多数没有被使用
while (true) {System.out.println("等待客户端连接....");Socket socket = serverSocket.accept();new ServerReader(socket).start();System.out.println("一个客户端上线了....  IP:" + socket.getInetAddress().getHostAddress());}
  • 使用无限循环,持续监听新的连接

  • **serverSocket.accept()**会堵塞线程,等待连接请求,直到收到一个新的请求,并与客户端建立新的通信管道用来传输数据

  • **new ServerReader(socket).start()**在建立新的管道后,会建立一个新线程用来与管道对应的客户端通信,这样就能实现多客户端之间通信

  • **socket.getInetAddress().getHostAddress()**用于获取新建连接的客户端Ip,并在Server类终端打印,便于服务端查看连接信息


在Idea中创建一个名为ServerReader的类

注:本文所有读取发送都使用特殊流DataInputStreamDataOutputStream

ServerReader类
import java.io.*;
import java.net.Socket;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Collection;public class ServerReader extends Thread {private Socket socket;public ServerReader(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {DataInputStream dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1 2switch (type) {case 1:String nickname = dis.readUTF();// 登陆成功,将客户端socket存入在线集合Server.onLineSockets.put(socket, nickname);// 更新全部客户端的在线人数列表updateClientOnLineUserList();break;case 2:String msg = dis.readUTF();sendMsgToAll(msg);break;default:System.out.println("未知的消息类型: " + type);break;}}} catch (Exception e) {System.out.println("客户端断开连接  IP:" + socket.getInetAddress().getHostAddress() + " 时间: " + LocalDateTime.now());Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除updateClientOnLineUserList();}}// 给全部在线socket推送当前客户端发来的消息private void sendMsgToAll(String msg) {StringBuilder sb = new StringBuilder();String name = Server.onLineSockets.get(socket);// 获取当前时间LocalDateTime now = LocalDateTime.now();DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dft.format(now);String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();// 推送给全部客户端for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeUTF(msgResult);dos.flush();} catch (IOException e) {e.printStackTrace();}}}// 更新全部客户端的在线人数列表private void updateClientOnLineUserList() {// 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道Collection<String> onLineUsers = Server.onLineSockets.values();for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeInt(onLineUsers.size());for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}dos.flush();} catch (IOException e) {e.printStackTrace();}}}
}
代码解读
  1. sendMsgToAll方法
// 给全部在线socket推送当前客户端发来的消息private void sendMsgToAll(String msg) {StringBuilder sb = new StringBuilder();String name = Server.onLineSockets.get(socket);// 获取当前时间LocalDateTime now = LocalDateTime.now();DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dft.format(now);String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();// 推送给全部客户端for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(2); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeUTF(msgResult);dos.flush();} catch (IOException e) {e.printStackTrace();}}}
  • 从Map集合(onLineSockets)中,拿到当前客户端的用户名
String name = Server.onLineSockets.get(socket);
  • 获取当前时间,自定义时间格式dft "yyyy-MM-dd HH:mm:ss EEE a"年 月 日 时 分 秒 星期 上下午
LocalDateTime now = LocalDateTime.now();DateTimeFormatter dft = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss EEE a");String nowStr = dft.format(now);
  • 拼装消息 用户名+空格+时间+换行回车+消息+换行回车
String msgResult = sb.append(name).append(" ").append(nowStr).append("\r\n").append(msg).append("\r\n").toString();
  • 将拼装完成的消息,推送给所有当前在线客户端

    for循环遍历当前在线客户端

    标注消息类型为群聊消息(2)

    接着发送拼装完成的消息

    刷新管道

for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(2);dos.writeUTF(msgResult);dos.flush();} catch (IOException e) {e.printStackTrace();}}

  1. updateClientOnLineUserList方法

    // 更新全部客户端的在线人数列表private void updateClientOnLineUserList() {// 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道Collection<String> onLineUsers = Server.onLineSockets.values();for (Socket clientSocket : Server.onLineSockets.keySet()) {try {DataOutputStream dos = new DataOutputStream(clientSocket.getOutputStream());dos.writeInt(1); // 1代表告诉客户端接下来是在线人数列表信息 2代表发的是群聊消息dos.writeInt(onLineUsers.size());for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}dos.flush();} catch (IOException e) {e.printStackTrace();}}}
    
  • 拿到全部在线客户端的用户名称,把这些名称转发给全部在线socket管道
Collection<String> onLineUsers = Server.onLineSockets.values();
  • 1代表消息类型 告诉客户端接下来是在线人数列表信息
dos.writeInt(1);
  • 告诉客户端在线用户数量,客户端循环接收多少次
dos.writeInt(onLineUsers.size());
  • 服务端循环发送
for (String onLineUser : onLineUsers) {dos.writeUTF(onLineUser);}
  • 刷新管道
dos.flush();

  1. run方法
public void run() {try {DataInputStream dis = new DataInputStream(socket.getInputStream());while (true) {int type = dis.readInt(); // 1 2switch (type) {case 1:String nickname = dis.readUTF();// 登陆成功,将客户端socket存入在线集合Server.onLineSockets.put(socket, nickname);// 更新全部客户端的在线人数列表updateClientOnLineUserList();break;case 2:String msg = dis.readUTF();sendMsgToAll(msg);break;default:System.out.println("未知的消息类型: " + type);break;}}} catch (Exception e) {System.out.println("客户端断开连接  IP:" + socket.getInetAddress().getHostAddress() + " 时间: " + LocalDateTime.now());Server.onLineSockets.remove(socket); // 把下线的客户端socket从在线集合中移除updateClientOnLineUserList();}}
  • 创建一个读取socket管道的对象dis
DataInputStream dis = new DataInputStream(socket.getInputStream());
  • while循环保证该方法一直处于接收消息的状态

  • 先获取管道中发送的数据类型

int type = dis.readInt();
  • switch (type)判断:

    如果是1,则为用户名,String nickname = dis.readUTF()读取管道中发送的内容(用户名),这个时候用户已经登录成功,用Server.onLineSockets.put(socket, nickname)将客户端socket存入在线集合onLineSockets(该集合在前面已经创建在Server类里了)

    如果是2,则为群聊消息,String msg = dis.readUTF()读取管道中的发送的群聊消息,接着调用sendMsgToAll()方法将消息广播给在线用户

    如果使用的传输数据的方式不是特殊流,则打印出该消息在特殊流下的形式(可能是一堆乱码)

  • 异常:当客户端断开连接后,系统会抛出一个异常,用Server.onLineSockets.remove(socket) 把下线的客户端socket从在线集合中移除,重新调用updateClientOnLineUserList()方法,刷新在线用户列表

catch (Exception e) {System.out.println("客户端断开连接  IP:" + socket.getInetAddress().getHostAddress() + " 时间: " + LocalDateTime.now());Server.onLineSockets.remove(socket);updateClientOnLineUserList();}

客户端

Android:

创建一个空项目Client

因为安卓客户端只有一个Activity和一个布局文件,所以项目构建完成后就不需要再创建其他类和活动了

结构预览

在这里插入图片描述

布局文件 activity_main.xml

预览
在这里插入图片描述

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:padding="16dp"><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="聊天室"android:textSize="24sp"android:textStyle="bold"android:gravity="center"android:layout_gravity="center_horizontal"android:layout_marginBottom="16dp" /><EditTextandroid:id="@+id/input_field"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="Type a message..."android:inputType="textMultiLine"android:minLines="3"android:maxLines="5" /><Buttonandroid:id="@+id/send_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="发送"android:layout_gravity="end"android:layout_marginTop="8dp" /><ListViewandroid:id="@+id/message_list_view"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:dividerHeight="1dp"android:layout_marginTop="16dp" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="在线用户列表"android:textSize="18sp"android:textStyle="bold"android:layout_marginTop="16dp" /><ListViewandroid:id="@+id/user_list_view"android:layout_width="match_parent"android:layout_height="wrap_content"android:dividerHeight="1dp"android:layout_marginTop="8dp" />
</LinearLayout>
代码解读

涉及到的布局属性

  • xml声明,编码方式为utf-8
<?xml version="1.0" encoding="utf-8"?>
  • 开始一个线性布局容器
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  • 宽高和父本容器同尺寸(全屏)
android:layout_width="match_parent"
android:layout_height="match_parent"
  • 线性布局方向为垂直方向
android:orientation="vertical"  //horizontal水平从左向右排列,vertical垂直从上向下排列
  • 给整个布局设置一个16dp的内边距
android:padding="16dp"
  • 添加文本内容
android:text="XXX"
  • 文本大小 文本样式 对齐方式
android:textSize="24sp"  //大小
android:textStyle="bold"  //样式 加粗
android:gravity="center"  //对齐方式 居中
  • 让添加该属性的控件水平居中
android:layout_gravity="center_horizontal"
  • 文本输入框,输入提示:没输入内容时显示提示语句,输入文本后就不可见,起提示作用
android:hint="//提示语句"
  • 允许该控件输入框输入多行文本,
android:inputType="textMultiLine"  //允许输入多行文本
android:minLines="3"   //最少3行
android:maxLines="5"   //最多5行
  • 控件靠右
android:layout_gravity="end"

这里说下android:gravityandroid:layout_gravity区别

android:gravity作用对象为当前控件内部,比如有一个TextView的文本内容,如果使用android:gravity="center",则会让文本内容在该TextView内部居中,和TextView在整个屏幕的位置没关系

android:layout_gravity作用对象为当前控件,这里还用TextView举例,如果使用android:layout_gravity"center_horizontal",则会让该TextView在屏幕中的位置处于居中状态,和控件内部的内容没关系

  • ListView列表项之间的分割线高度
android:dividerHeight="1dp"
  • 控件间的距离
 android:layout_marginTop="16dp"//当前控件与上方相邻控件的距离

MainActivity
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;public class MainActivity extends AppCompatActivity {private Socket socket;private DataInputStream in;private DataOutputStream out;private Handler handler = new Handler(Looper.getMainLooper());private List<String> messages = new ArrayList<>();private ArrayAdapter<String> messageAdapter;private List<String> onlineUsers = new ArrayList<>();private ArrayAdapter<String> userAdapter;private EditText inputField;private Button sendButton;private ListView messageListView, userListView;private String nickname = "XXX"; // 使用实际的昵称@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);messageListView = findViewById(R.id.message_list_view);userListView = findViewById(R.id.user_list_view);inputField = findViewById(R.id.input_field);sendButton = findViewById(R.id.send_button);messageAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, messages);messageListView.setAdapter(messageAdapter);userAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, onlineUsers);userListView.setAdapter(userAdapter);sendButton.setOnClickListener(v -> sendMessage());// 连接到服务器connectToServer();}private void connectToServer() {new Thread(() -> {try {// 创建Socket对象并连接到服务器socket = new Socket("192.168.68.206", 9999); // 替换为实际服务器IP和端口in = new DataInputStream(socket.getInputStream());out = new DataOutputStream(socket.getOutputStream());// 发送登录请求out.writeInt(1);out.writeUTF(nickname);// 开始接收消息receiveMessages();} catch (IOException e) {e.printStackTrace();handler.post(this::disconnect);}}).start();}private void sendMessage() {String message = inputField.getText().toString().trim();if (!message.isEmpty()) {new Thread(() -> {try {out.writeInt(2);out.writeUTF(message);inputField.setText("");} catch (IOException e) {e.printStackTrace();handler.post(this::disconnect);}}).start();}}private void receiveMessages() {new Thread(() -> {try {while (true) {int type = in.readInt();switch (type) {case 1:int count = in.readInt();for (int i = 0; i < count; i++) {String user = in.readUTF();updateOnlineUsers(user);}break;case 2:String msg = in.readUTF();updateMessage(msg);break;}}} catch (IOException e) {e.printStackTrace();handler.post(this::disconnect);}}).start();}private void updateMessage(String message) {handler.post(() -> {messages.add(message);messageAdapter.notifyDataSetChanged();});}private void updateOnlineUsers(String user) {handler.post(() -> {if (!onlineUsers.contains(user)) {onlineUsers.add(user);}userAdapter.notifyDataSetChanged();});}private void disconnect() {if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}@Overrideprotected void onDestroy() {super.onDestroy();disconnect();}
}
代码解读

在我制作的时候,在写方法这里就遇到了问题,总的来说,就是匿名类内部调用方法,默认调用的是匿名类内部的方法,而不调用外部方法.但是我们一般定义类都在外部定义而不会在匿名类内部定义,所以就有找不到调用类的错误(下面有例子)

这里用MainActivity中的sendMessage方法来举例

上图片!
在这里插入图片描述

先不看报错部分,如果大致对比一下,就能发现代码好多行都不一样,是的,因为最上面展示的是更改和优化过的"好代码"

点开小红灯泡

就能看到

在这里插入图片描述

创建方法? 我disconnect已经创建过了,为什么还要我创建

因为他没有找到啊

打个比方说,匿名类就像是封建派的老顽固,只用自己家有的,外来的?“我匿名类可是天朝上国,还需要你的方法?”(其实自家也没有)

而这个方法呢就像世界的先进技术,别人已经研究好的,拿来就能用,可悲的是他非要用自己的,那怎么办?

不开国门做生意,那就打到你开为止,不用?那就逼着你用

所以强制他一下就好啦

架炮!

在报错这行代码的this前面加上外部类的的类名+,咱这里就是MainActivity.,加上后效果如下
在这里插入图片描述

这里加上**MainActivity.**就是限制了this必须调用外部类MainActivity里的方法disconnect

找不到我就硬塞给你,你还不能不要

但是改好了,还和最上面的"好代码"不一样啊

是的,上面的是用了Lambda表达式的,拆开代码单独看就是…

这个(老)
在这里插入图片描述

和这个(新)
在这里插入图片描述

的区别

一开始我以为他俩是等效的,只是后者是用了Lambda简化过的,代码更简洁了而已,但是他的进步远不止于此

在这里插入图片描述

可以看到他并没有被"强制"增加MainActivity.,这是为什么呢?

这是因为Lambda表达式中的 this 自动指向外部类实例,因此可以直接使用 this::disconnect

说白了就是人家本身就开放,追求"外界",没必要轰他

同理,简洁代码如下

将这个

在这里插入图片描述

换成这个

在这里插入图片描述

Lambda好处多多,在这里就不一一赘述了

所以咱家也是好起来了,与时俱进,都改用"先进技术"了


话说回来,先说控件的定义和初始化吧

成员变量声明

private Socket socket; //用于建立连接
private DataInputStream in;  //特殊流接收
private DataOutputStream out;  //特殊流发送
private Handler handler = new Handler(Looper.getMainLooper());//用于更新主线程
private List<String> messages = new ArrayList<>();//定义存储消息的ArrayList
private ArrayAdapter<String> messageAdapter;//消息显示适配器
private List<String> onlineUsers = new ArrayList<>();//定义存储在线用户的ArrayList
private ArrayAdapter<String> userAdapter;//用户显示适配器
private EditText inputField; //文本输入框
private Button sendButton;  //发送按钮
private ListView messageListView, userListView;//群聊消息列表和在线用户列表
private String nickname = "XXX"; //用户名 使用实际的昵称

在线用户和消息显示其实是一样的,这里就只拿消息来举例:

1.当我们客户端收到消息就把消息存到存储消息的ArrayList–messages中

2.消息存储好后我们要调用把他显示出来,这时候需要用到适配器,来解决"用什么方式来显示"的问题(不用的话太难看)

3.将存储消息的ArrayList–messages放到适配器里,选择显示方式,创建该适配器对象,并将该适配器对象调用在群聊消息列表ListView–messageListView中

打个比方: 现在要吃一顿饭,先拿到饭,找到合适的餐具,才能慢慢享用

详细看下面主线程注释

主线程

protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);
//初始化控件,在布局文件中找到控件messageListView = findViewById(R.id.message_list_view);userListView = findViewById(R.id.user_list_view);inputField = findViewById(R.id.input_field);sendButton = findViewById(R.id.send_button);
//消息适配器,适配器显示方式android.R.layout.simple_list_item_1,调用显示数据集合messagesmessageAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, messages);//将适配器添加到显示窗口  messageListView.setAdapter(messageAdapter);
//同上userAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, onlineUsers);userListView.setAdapter(userAdapter);
//监听按钮,点击发送消息,调用方法sendButton.setOnClickListener(v -> sendMessage());// 连接到服务器,调用方法connectToServer();}

与网络请求有关的方法和代码,是不能堆在主线程(一般是onCreate)的,因为Android怕这些耗时操作堵塞主线程,影响用户体验,这里的收发消息和获取用户名都是需要网络的,也就是耗时操作,都需要新开线程来进行

连接+上线方法:

connectToServer方法

private void connectToServer() {//开线程,进行耗时操作new Thread(() -> {try {// 创建Socket对象并连接到服务器socket = new Socket("192.168.68.206", 9999); // 替换为实际服务器IP和端口//初始化,给数据流连接位置in = new DataInputStream(socket.getInputStream());out = new DataOutputStream(socket.getOutputStream());// 发送登录请求out.writeInt(1);out.writeUTF(nickname);// 开始接收消息,调用方法receiveMessages();} catch (IOException e) {e.printStackTrace();//断开连接后,将用户从在线列表中移除handler.post(this::disconnect);}}).start();}
  • 登录请求发送消息类型1,发送用户名
  • 端口与服务端一致

发送消息方法:

sendMessage

private void sendMessage() {//从输入框获取消息String message = inputField.getText().toString().trim();//消息不为空,则执行if (!message.isEmpty()) {//开线程,进行耗时操作new Thread(() -> {try {//发送消息out.writeInt(2);out.writeUTF(message);//发送后清空输入框inputField.setText("");} catch (IOException e) {e.printStackTrace();//断开连接后,将用户从在线列表中移除,调用方法handler.post(this::disconnect);}}).start();}
}
  • 发送消息类型2,发送输入框获取的消息
String message = inputField.getText().toString().trim();
  1. inputField:这是一个引用,指向一个实现了getText()方法的对象,通常是EditTextTextView等视图组件。它代表了用户可以输入文本的地方
  2. getText():这是EditText类中的一个方法,用来获取当前输入框内的文本内容。这个方法返回的是一个Editable对象,而不是直接返回字符串类型
  3. toString():由于getText()返回的是Editable对象,为了将其转换为String类型,需要调用toString()方法。这样做是为了方便后续对文本的操作,比如比较、存储或者展示等
  4. trim():这个方法的作用是去除字符串两端的空白字符(包括空格、制表符、换行符等)。这对于确保输入数据的有效性非常有用,因为它可以避免因为意外输入的额外空白而导致逻辑错误或者界面显示问题

接收消息方法:

receiveMessages

private void receiveMessages() {//耗时任务new!new!new!new Thread(() -> {try {//无限循环保证在线状态下,可以实时接收消息while (true) {//接收消息类型int type = in.readInt();//处理消息switch (type) {//类型为1,读取发送来的用户个数,循环读取case 1:int count = in.readInt();for (int i = 0; i < count; i++) {String user = in.readUTF();//添加到在线用户列表集合,调用方法updateOnlineUsers(user);}break;//类型为2,读取消息case 2:String msg = in.readUTF();//添加到消息列表集合,调用方法updateMessage(msg);break;}}} catch (IOException e) {e.printStackTrace();//断开连接后,将用户从在线列表中移除handler.post(this::disconnect);}}).start();}

添加消息方法:

updateMessage

private void updateMessage(final String message) {handler.post(() -> {//将输入参数存入消息集合messages.add(message);//通知适配器,有新消息存入,更新显示内容messageAdapter.notifyDataSetChanged();});}

添加在线用户方法:

updateOnlineUsers

private void updateOnlineUsers(String user) {handler.post(() -> {//判断在线用户集合里是否存在新输入参数,有则不会重复添加if (!onlineUsers.contains(user)) {//参数添加到在线用户集合onlineUsers.add(user);}//通知适配器,有新用户名存入,更新显示内容userAdapter.notifyDataSetChanged();});}

断开连接,删除管道方法:

disconnect

private void disconnect() {//管道无连接,即断开状态,客户端关闭管道if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}// 可以在这里添加重新连接逻辑或提示用户断开连接}

关闭程序方法:

onDestroy

@Overrideprotected void onDestroy() {super.onDestroy();disconnect();}
  • 调用父类(这里是Activity类)的onDestroy()方法。这是非常重要的一步,因为它确保了所有必要的清理工作由父类完成。每个Activity都继承自Activity基类,而该基类的onDestroy()方法可能包含一些必要的资源释放逻辑。
  • 忽略这一步可能会导致内存泄漏或其他未预期的行为
super.onDestroy();

对了,别忘了最重要的一步:

配置网络

AndroidManifest.xml中添加如下代码

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

完成后如下

在这里插入图片描述

我滴任务完成辣!

如有问题,可评论留言,鄙人会试着解决

新手刚上路,错误之处欢迎指出,大家共勉!
}
//通知适配器,有新用户名存入,更新显示内容
userAdapter.notifyDataSetChanged();
});
}

断开连接,删除管道方法:`disconnect````java
private void disconnect() {//管道无连接,即断开状态,客户端关闭管道if (socket != null) {try {socket.close();} catch (IOException e) {e.printStackTrace();}}// 可以在这里添加重新连接逻辑或提示用户断开连接}

关闭程序方法:

onDestroy

@Overrideprotected void onDestroy() {super.onDestroy();disconnect();}
  • 调用父类(这里是Activity类)的onDestroy()方法。这是非常重要的一步,因为它确保了所有必要的清理工作由父类完成。每个Activity都继承自Activity基类,而该基类的onDestroy()方法可能包含一些必要的资源释放逻辑。
  • 忽略这一步可能会导致内存泄漏或其他未预期的行为
super.onDestroy();

对了,别忘了最重要的一步:

配置网络

AndroidManifest.xml中添加如下代码

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

完成后如下

[外链图片转存中…(img-8lTKFuqP-1739372330386)]

我滴任务完成辣!

如有问题,可评论留言,鄙人会试着解决

新手刚上路,错误之处欢迎指出,大家共勉!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/895366.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java韩顺平最新教程,Java工程师进阶

简介 HikariCP 是用于创建和管理连接&#xff0c;利用“池”的方式复用连接减少资源开销&#xff0c;和其他数据源一样&#xff0c;也具有连接数控制、连接可靠性测试、连接泄露控制、缓存语句等功能&#xff0c;另外&#xff0c;和 druid 一样&#xff0c;HikariCP 也支持监控…

如何在 IDE 里使用 DeepSeek?

近期&#xff0c;阿里云百炼平台重磅推出 DeepSeek-V3、DeepSeek-R1、DeepSeek-R1-Distill-Qwen-32B 等 6 款模型&#xff0c;进一步丰富其 AI 模型矩阵。与此同时&#xff0c;通义灵码也紧跟步伐&#xff0c;全新上线模型选择功能&#xff0c;支持基于百炼的 DeepSeek-V3 和 D…

网络安全技术复习总结

1|0第一章 概论 1.网络安全发展阶段包括四个阶段&#xff1a;通信安全、计算机安全、网络安全、网络空间安全。 2.2017年6月1日&#xff0c;我国第一部全面规范网络空间安全的基础性法律《中华人民共和国网络安全法》正式实施。 3.2021年 6月10日&#xff0c;《中华人民共和…

DedeBIZ系统审计小结

之前简单审计过DedeBIZ系统&#xff0c;网上还没有对这个系统的漏洞有过详尽的分析&#xff0c;于是重新审计并总结文章&#xff0c;记录下自己审计的过程。 https://github.com/DedeBIZ/DedeV6/archive/refs/tags/6.2.10.zip &#x1f4cc;DedeBIZ 系统并非基于 MVC 框架&…

业务开发 | 基础知识 | Maven 快速入门

Maven 快速入门 1.Maven 全面概述 Apache Maven 是一种软件项目管理和理解工具。基于项目对象模型的概念&#xff08;POM&#xff09;&#xff0c;Maven 可以从中央信息中管理项目的构建&#xff0c;报告和文档。 2.Maven 基本功能 因此实际上 Maven 的基本功能就是作为 Ja…

2.11 sqlite3数据库【数据库的相关操作指令、函数】

练习&#xff1a; 将 epoll 服务器 客户端拿来用 客户端&#xff1a;写一个界面&#xff0c;里面有注册登录 服务器&#xff1a;处理注册和登录逻辑&#xff0c;注册的话将注册的账号密码写入数据库&#xff0c;登录的话查询数据库中是否存在账号&#xff0c;并验证密码是否正确…

Python(十九)实现各大跨境船公司物流查询数据处理优化

一、前言 之前已经实现了常用 跨境物流船司 基础信息查询功能&#xff0c;如下所示 实现各大跨境船公司[COSCO/ZIM/MSK/MSC/ONE/PIL]的物流信息查询&#xff1a;https://blog.csdn.net/Makasa/article/details/145484999?spm1001.2014.3001.5501 然后本章在其基础上做了一些…

基于微信小程序的博物馆预约系统的设计与实现

hello hello~ &#xff0c;这里是 code袁~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f981;作者简介&#xff1a;一名喜欢分享和记录学习的在校大学生…

深度学习框架TensorFlow怎么用?

大家好呀&#xff0c;以下是使用 TensorFlow 的详细步骤&#xff0c;从安装到构建和训练模型&#xff1a; 一、安装 TensorFlow 安装 Python&#xff1a;TensorFlow 基于 Python&#xff0c;确保已安装 Python&#xff08;推荐 Python 3.8 及以上版本&#xff09;。可通过 Pyt…

如何在华为harmonyOS上调试软件

1、设置-》关于手机-》HarmonyOS 版本连按多下&#xff0c;输入锁屏密码。显示开发者模式已打开。 2、设置-》搜索“开发人员选项”-》开启“开发人员选项”选项。 3、在 开发者选项 中找到 “USB 调试” 并开启。 4、开启 “仅充电时允许 ADB 调试”。 5、设置中开启 &quo…

fpga系列 HDL:Quartus II JTAG 间接配置文件 Indirect Configuration File (.jic) AS模式烧录

先编译生成pof文件 File->Convert Programming Files 转换文件 Tools->Programer 烧录

Python:凯撒密码

题目内容&#xff1a; 凯撒密码是古罗马恺撒大帝用来对军事情报进行加密的算法&#xff0c;它采用了替换方法对信息中的每一个英文字符循环替换为字母表序列该字符后面第三个字符&#xff0c;对应关系如下&#xff1a; 原文&#xff1a;A B C D E F G H I J K L M N O P Q R …

【大模型知识点】什么是KV Cache?为什么要使用KV Cache?使用KV Cache会带来什么问题?

1.什么是KV Cache&#xff1f;为什么要使用KV Cache&#xff1f; 理解此问题&#xff0c;首先需理解自注意机制的计算和掩码自注意力机制&#xff0c;在Decoder架构的模型中&#xff0c;每生成一个新的token&#xff0c;便需要重新执行一次自注意力计算&#xff0c;这个过程中…

【STM32】HAL库Host MSC读写外部U盘及FatFS文件系统的USB Disk模式

【STM32】HAL库Host MSC读写外部U盘及FatFS文件系统的USB Disk模式 在先前 分别介绍了FatFS文件系统和USB虚拟U盘MSC配置 前者通过MCU读写Flash建立文件系统 后者通过MSC连接电脑使其能够被操作 这两者可以合起来 就能够实现同时在MCU、USB中操作Flash的文件系统 【STM32】通过…

本地生活服务平台开发进入发展热潮

本地生活服务平台&#xff1a;当下的发展热潮 本地生活服务平台开发模式 在当今数字化时代&#xff0c;本地生活服务平台开发已成为人们日常生活中不可或缺的一部分。只需动动手指&#xff0c;打开手机上的 APP&#xff0c;就能轻松满足各类生活需求。像某团、饿XX这样的平台&a…

LSTM变种模型

GRU GRU简介 门控循环神经网络 (Gated Recurrent Neural Network&#xff0c;GRNN) 的提出&#xff0c;旨在更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可学习的门来控制信息的流动。其中&#xff0c;门控循环单元 (Gated Recurrent Unit &#xff0c; GRU) 是…

微服务与网关

什么是网关 背景 单体项目中&#xff0c;前端只用访问指定的一个端口8080&#xff0c;就可以得到任何想要的数据 微服务项目中&#xff0c;ip是不断变化的&#xff0c;端口是多个的 解决方案&#xff1a;网关 网关&#xff1a;就是网络的关口&#xff0c;负责请求的路由、转发…

二分算法篇:二分答案法的巧妙应用

二分算法篇&#xff1a;二分答案法的巧妙应用 那么看到二分这两个字想必我们一定非常熟悉&#xff0c;那么在大学期间的c语言的教学中会专门讲解二分查找&#xff0c;那么我们来简单回顾一下二分查找算法&#xff0c;我们知道二分查找是在一个有序的序列中寻找一个数在这个序列…

C# OpenCV机器视觉:模仿Halcon各向异性扩散滤波

在一个充满创意与挑战的图像处理工作室里&#xff0c;阿强是一位热情的图像魔法师。他总是在追求更加出色的图像效果&#xff0c;然而&#xff0c;传统的图像处理方法有时候并不能满足他的需求。 有一天&#xff0c;阿强听说了 Halcon 中的各向异性扩散滤波功能&#xff0c;它…

实现:多活的基础中间件

APIRouter &#xff1a; 路由分发服务 API Router 是一个 HTTP 反向代理和负载均衡器&#xff0c;部署在公有云中作为 HTTP API 流量的入口&#xff0c;它能识别 出流量的归属 shard &#xff0c;并根据 shard 将流量转发到对应的 ezone 。 API Router 支持多种路由键&am…