如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天
手把手教你用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天
目录
文章目录
- 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天
- **目录**
- @[toc]
- **基本实现**
- **问题分析**
- **服务端**
- Idea:
- 结构预览
- Server类
- 代码解读
- ServerReader类
- 代码解读
- **客户端**
- Android:
- 结构预览
- 布局文件 activity_main.xml
- 代码解读
- MainActivity
- 代码解读
- 配置网络
- 配置网络
文章目录
- 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天
- **目录**
- @[toc]
- **基本实现**
- **问题分析**
- **服务端**
- Idea:
- 结构预览
- Server类
- 代码解读
- ServerReader类
- 代码解读
- **客户端**
- Android:
- 结构预览
- 布局文件 activity_main.xml
- 代码解读
- MainActivity
- 代码解读
- 配置网络
- 配置网络
基本实现
-
实现客户端和服务端之间的通信
-
实现服务端转接客户端消息,并发送给其他局域网在线成员
-
实现服务端接收客户端消息,并判断相应类型,做出对应应答
-
实现客户端消息发送者 发送时间 当前在线用户基本可视化
问题分析
-
服务端开发
- 在IntelliJ IDEA中创建一个Java项目。
- 实现一个简单的TCP服务器,能够接收客户端消息并回显(或广播)消息给所有已连接的客户端。
-
客户端开发
- 在Android Studio中创建一个Android项目。
- 实现一个简单的TCP客户端,能够发送消息到服务端并显示从服务端接收到的消息。
-
网络通信
- 确保服务端和客户端在同一局域网内,并且客户端可以正确连接到服务端。
- 处理多线程问题,确保服务端可以同时处理多个客户端连接。
服务端
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的类
注:本文所有读取和发送都使用特殊流(DataInputStream与DataOutputStream)
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();}}}
}
代码解读
- 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();}}
-
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();
- 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:gravity和android: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();
- inputField:这是一个引用,指向一个实现了
getText()方法的对象,通常是EditText或TextView等视图组件。它代表了用户可以输入文本的地方 - getText():这是
EditText类中的一个方法,用来获取当前输入框内的文本内容。这个方法返回的是一个Editable对象,而不是直接返回字符串类型 - toString():由于
getText()返回的是Editable对象,为了将其转换为String类型,需要调用toString()方法。这样做是为了方便后续对文本的操作,比如比较、存储或者展示等 - 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)]
我滴任务完成辣!
如有问题,可评论留言,鄙人会试着解决
新手刚上路,错误之处欢迎指出,大家共勉!
相关文章:
如何使用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天
手把手教你用Java语言在Idea和Android中分别建立服务端和客户端实现局域网聊天 目录 文章目录 手把手教你用**Java**语言在**Idea**和**Android**中分别建立**服务端**和**客户端**实现局域网聊天**目录**[toc]**基本实现****问题分析****服务端**Idea:结构预览Server类代码解…...
Java_多线程
并发和并行 并发 在同一时刻,有多个指令在单个CPU上交替执行 并行 在同一时刻,有多个指令在多个CPU上同时执行 多线程的实现方式 继承Thread类的方式 注意给线程设置名字,启动线程等操作 实现Runable的方式 自己创建一个类然后去实现…...
uniapp开发h5部署到服务器
1.发行>网站-PC Web或手机H5(仅适用于uniapp) 2.填写网站域名 3.编译成功后会生成一个unpackage文件夹找到下面的h5 4.接下来会使用一个工具把h5里面的文件放到服务器上面(WinSCP使用其他能部署的工具也行) 5.登录 6.登录成功后…...
如何在个人电脑本地化部署Deepseek-R1大模型
文章目录 Deepseek概述公司简介DeepSeek模型优势DeepSeek模型发展历史Ollama安装Deepseek版本选择Deepseek支持的客户端工具编程语言客户端库桌面客户端插件类其他工具客户端工具配置cherryStudio配置测试如何使用DeepSeek满血版什么是 DeepSeek R1 满血版?deepseek官方第三方…...
Java中关于JSON的基本使用
Java中关于JSON的基本使用 Java中可以操作json的类库jar有很多,网上一找一大片,每种封装的jar包有自己的方法,需要查阅资料使用,但无非就是json转对象,对象转json这些 Java中常见的json类jar包:Gson,Jackso…...
简化的动态稀疏视觉Transformer的PyTorch代码
存一串代码(简化的动态稀疏视觉Transformer的PyTorch代码) import torch import torch.nn as nn import torch.nn.functional as F class DynamicSparseAttention(nn.Module): def __init__(self, dim, num_heads8, dropout0.1): super().__init__()…...
PADS多层板减少层数
前提 PADS是硬件工程师必备的画图软件,相信很多朋友遇到过为降低成本把6层板改为4层,或8层改为6层的经历,正常是把不需要的两层上所有东西删掉,然后修改层设置,下面举例说明。 首先是将要删除的层上的数据全部删除&a…...
你需要提供管理员权限才能删除此文件夹解决方法
立即高级启动 windows10 搜索“设置”,然后“更新和安全””->“恢复”->“立即重新启动” windows11 搜索“设置”,然后“Windows更新”->“更新历史记录”->“恢复”->“立即重新启动” 疑难解答 点击“疑难解答” 高级选项 启…...
螺旋折线(蓝桥杯18G)
、 #include<iostream> using namespace std; typedef pair<int,int> Dot;//存储坐标 int dy[] { 0,1,0,-1 }; int dx[] { -1,0,1,0 }; int main() {int direction 0,x,y,dis 0;Dot pos make_pair(0,0);cin >> x >> y;for (int i 1;; i) {for (…...
常见的数据仓库有哪些?
数据仓库(Data Warehouse,简称数仓)是企业用于存储、管理和分析大量数据的重要工具,其核心目标是通过整合和处理数据,为决策提供高质量、一致性和可信度的数据支持。在构建和使用数仓时,选择合适的工具和技术至关重要。以下是常见的数仓工具及其特点的详细介绍: 1. Hiv…...
数据科学之数据管理|NumPy数据管
一、Numpy介绍 (一) 什么是numpy NumPy是Python中科学计算的基础包。它是一个Python库,提供多维数组对象,各种派生对象(如掩码数组和矩阵),以及用于数组快速操作的各种API,有包括数学、逻辑、形状操作、排序、选择、输入输出、离散傅立叶变换、基本线性代数,基本统计运…...
LSTM 学习笔记 之pytorch调包每个参数的解释
0、 LSTM 原理 整理优秀的文章 LSTM入门例子:根据前9年的数据预测后3年的客流(PyTorch实现) [干货]深入浅出LSTM及其Python代码实现 整理视频 李毅宏手撕LSTM [双语字幕]吴恩达深度学习deeplearning.ai 1 Pytorch 代码 这里直接调用了nn.l…...
ASUS/华硕飞行堡垒9 FX506H FX706H 原厂Win10系统 工厂文件 带ASUS Recovery恢复
华硕工厂文件恢复系统 ,安装结束后带隐藏分区,带一键恢复,以及机器所有的驱动和软件。 支持型号:FX506HC, FX506HE, FX506HM, FX706HC, FX706HE, FX706HM, FX506HHR, FX706HMB, FX706HEB, FX706HCB, FX506HMB, FX506HEB, FX506HC…...
Unity使用iTextSharp导出PDF-04图形
坐标系 pdf文档页面的原点(0,0)在左下角,向上为y,向右为x。 文档的PageSize可获取页面的宽高数值 单位:像素 绘制矢量图形 使用PdfContentByte类进行绘制,注意文档打开后才有此对象的实例。 绘制方法 …...
JDBC如何连接数据库
首先,我们要去下载JDBC的驱动程序 官网下载地址:https://downloads.mysql.com/archives/c-j/ 选择最新版本就可以 然后回到我们idea点击file - project Structure - Modules, 就行了 参考1:如何解决JDBC连接数据库出现问题且对进行数据库操…...
Unity URP的2D光照简介
官网工程,包括2d光照,动画,动效介绍: https://unity.com/cn/blog/games/happy-harvest-demo-latest-2d-techniques https://docs.unity3d.com/6000.0/Documentation/Manual/urp/Lights-2D-intro.html 人物脸部光照细节和脚上的阴影…...
【IC】AI处理器核心--第二部分 用于处理 DNN 的硬件设计
第 II 部分 用于处理 DNN 的硬件设计 第 3 章 关键指标和设计目标 在过去的几年里,对 DNN 的高效处理进行了大量研究。因此,讨论在比较和评估不同设计和拟议技术的优缺点时应考虑的关键指标非常重要,这些指标应纳入设计考虑中。虽然效率通常…...
从 0 开始本地部署 DeepSeek:详细步骤 + 避坑指南 + 构建可视化(安装在D盘)
个人主页:chian-ocean 前言: 随着人工智能技术的迅速发展,大语言模型在各个行业中得到了广泛应用。DeepSeek 作为一个新兴的 AI 公司,凭借其高效的 AI 模型和开源的优势,吸引了越来越多的开发者和企业关注。为了更好地…...
如何本地部署DeepSeek集成Word办公软件
目录 本地部署DeepSeek安装Ollama下载并部署DeepSeek模型安装ChatBox客户端(可选) 将DeepSeek集成到Word修改Word中的VBA代码执行操作 ✍️相关问答如何在Word中安装和使用VBA宏DeepSeek模型有哪些常见的API接口?如何优化DeepSeek在Word中的集…...
Centos10 Stream 基础配置
NetworkManger 安装 dnf install NetworkManager 查看网络配置 nmcli [rootCentos-S-10 /]# nmcli ens33:已连接 到 ens33"Intel 82545EM"ethernet (e1000), 00:0C:29:08:3E:71, 硬件, mtu 1500ip4 默认inet4 192.168.31.70/24route4 default …...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
七、数据库的完整性
七、数据库的完整性 主要内容 7.1 数据库的完整性概述 7.2 实体完整性 7.3 参照完整性 7.4 用户定义的完整性 7.5 触发器 7.6 SQL Server中数据库完整性的实现 7.7 小结 7.1 数据库的完整性概述 数据库完整性的含义 正确性 指数据的合法性 有效性 指数据是否属于所定…...
uniapp手机号一键登录保姆级教程(包含前端和后端)
目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号(第三种)后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...
人工智能--安全大模型训练计划:基于Fine-tuning + LLM Agent
安全大模型训练计划:基于Fine-tuning LLM Agent 1. 构建高质量安全数据集 目标:为安全大模型创建高质量、去偏、符合伦理的训练数据集,涵盖安全相关任务(如有害内容检测、隐私保护、道德推理等)。 1.1 数据收集 描…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
【免费数据】2005-2019年我国272个地级市的旅游竞争力多指标数据(33个指标)
旅游业是一个城市的重要产业构成。旅游竞争力是一个城市竞争力的重要构成部分。一个城市的旅游竞争力反映了其在旅游市场竞争中的比较优势。 今日我们分享的是2005-2019年我国272个地级市的旅游竞争力多指标数据!该数据集源自2025年4月发表于《地理学报》的论文成果…...
命令行关闭Windows防火墙
命令行关闭Windows防火墙 引言一、防火墙:被低估的"智能安检员"二、优先尝试!90%问题无需关闭防火墙方案1:程序白名单(解决软件误拦截)方案2:开放特定端口(解决网游/开发端口不通)三、命令行极速关闭方案方法一:PowerShell(推荐Win10/11)方法二:CMD命令…...
前端工具库lodash与lodash-es区别详解
lodash 和 lodash-es 是同一工具库的两个不同版本,核心功能完全一致,主要区别在于模块化格式和优化方式,适合不同的开发环境。以下是详细对比: 1. 模块化格式 lodash 使用 CommonJS 模块格式(require/module.exports&a…...
2025.6.9总结(利与弊)
凡事都有两面性。在大厂上班也不例外。今天找开发定位问题,从一个接口人不断溯源到另一个 接口人。有时候,不知道是谁的责任填。将工作内容分的很细,每个人负责其中的一小块。我清楚的意识到,自己就是个可以随时替换的螺丝钉&…...
Go 语言中的内置运算符
1. 算术运算符 注意: (自增)和--(自减)在 Go 语言中是单独的语句,并不是运算符。 package mainimport "fmt"func main() {fmt.Println("103", 103) // 13fmt.Println("10-3…...
