文章目录
教学与实践目的:学会在网络应用开发中运用Java多线程技术。
一、程序的基本调试技术
程序无语法错误、能运行,但没有出现预期的结果,说明程序可能存在逻辑错误,解决这类错误的主要方法是查看程序运行过程中的内存变量值。一个常用的手段是通过打印语句打印出变量的值,例如使用System.out.println(待排查的变量)。但更强大的方法是使用IDE提供的断点功能。
在idea设断点并查看变量的方法:
鼠标点击要查看变量所在代码行的行号右侧空白处,出现棕红色实心圆,即表示在此处打了断点,调试时程序会在此处停住,方便观察程序运行的状况和各变量的即时值。
首先新建一个包,命名为chapter03,然后将上一讲的TCPServer.java、TCPClient.java、TCPClientFX.java复制到这个包中,注意程序中第一行语句是否自动修改为package chapter03;
假如我们要观察获取的IP地址是否符合预期,可以在客户端窗口程序TCPClientFX中选择一行有相关变量的代码行,如图3.1,鼠标点击行号右侧标注断点;
右上角下拉框选中“TCPClientFX”,再点击“调试”图标(也可以直接从“run”菜单或右键点击主窗体的弹出菜单中选择debug方式运行),窗口程序运行到红色断点行时会停留,便于观察此时IP、port等变量的状态值,如图3.2所示。通过图3.2所示红色框区域,可以让程序单步执行,一步一步地观察程序执行的情况,如果当前行代码中有方法的调用,step over表示跳过方法之间运行下一行代码,而step into则继续下钻,可以进入方法内部,一般只是用于进入自定义方法。
如果要调试的代码行是在一个新的线程的代码块中,则需要右键点击断点图标,在弹出菜单中,将"Suspend:" 后面的单选按钮从"All" 改为 “Thread”。
图3.2 调试暂停界面
二、理解阻塞语句
在同一个进程中,一条阻塞语句的执行影响着下条语句何时被执行。如果该条语句没有执行完,那么下条语句是不可能进入执行状态的,因此,从字面层上理解,该条语句阻塞了下面语句的执行。
JAVA类BufferedReader中readLine( )方法的调用是阻塞语句,若该套接字的输入流中没有带行结束符(如\n)的字符可读,则该语句会处于阻塞状态,直到条件出现行结束符,才会执行下面的语句。
阻塞状态程序演示:
(1)将TCPServer.java程序中的发送语句临时禁用(验证完再还原),如:
//向输出流中输出一行字符串,远程客户端可以读取该字符串
//pw.println("来自服务器:" + msg); 临时禁用
即服务器不回传信息;
(2)启动TCPServer.java服务程序,再启动TCPClientFX.java客户端程序,发送信息,发现客户程序不能正常运行,发送按钮甚至整个程序失去响应。
(3)强行终止TCPClientFX,在窗口程序的发送语句处设置断点,如图3.3所示。然后在调试状态运行该程序,逐行调试(遇到自定义的方法,建议使用step into跟踪进入)。在执行到receive()方法时,使用step into跟踪进方法会发现程序会阻塞在msg = br.readLine(); 处(因为服务器没有返回,客户端的输入流队列中是空的,所以被阻塞)。所以程序设计时一定要小心。
图3.3 断点位置
三、理解读一行功能
同理,若套接字的输入流中有多行信息,调用一次readLine()方法,只是读出当前的一行(当然你可以调用其他的“读”方法)。
程序演示:
(1)在TCPServer.java程序中多增加一条信息返回语句,如:
pw.println("来自服务器:" + msg);
//下面多增加一条信息返回语句
pw.println("来自服务器,重复发送: " + msg);
然后启动服务端程序;
(2)启动客户端TCPClientFX程序,发现客户显示区每次只显示一条信息,且与你发送的信息不同步。因为每一次互动,服务器返回两行信息,而客户端只是读取最前面的一行信息。
如何解决阻塞和多行信息的读写问题? 一个常用的解决方案就是多线程。
四、多线程技术
多线程程序的执行如图3.4所示。
图3.4 程序调用的顺序执行与线程调用的并行执行
有了多线程技术,我们就有了更多选择。
1. 编写读取服务器信息的线程
在TCPClientFX.java程序中,发送信息是可以通过“发送”按钮来实现主动控制,可接收信息是被动的,你不知道输入流中有多少信息。
为此,在窗口程序中添加一个线程专门负责读取输入流中的信息, 同时,“发送”按钮动作中,读取输入流信息的代码就需要删除。
现在右键选择TCPClientFX.java重构(Refactor),重命名为TCPClientThreadFX.java(采用如图3.5所示的方式)
并在合适的位置(例如,在btnConnect的动作事件中,在建立了连接之后)编写如下线程代码,用于接收服务器的信息,为了简洁,匿名内部类使用了lambda的写法:
public class TCPClientThreadFX extends Application {
Thread receiveThread; //定义成员变量,读取服务器信息的线程
//…… 省略……
//以下代码位于btnConnect.setOnActon方法中的合适位置
//用于接收服务器信息的单独线程
receiveThread = new Thread(()->{
String msg = null;
//不知道服务器有多少回传信息,就持续不断接收
//由于在另外一个线程,不会阻塞主线程的正常运行
while ((msg = tcpClient.receive()) != null) {
//runLater中的lambda表达式不能直接访问外部非final类型局部变量
//所以这里使用了一个临时常量,可以省略final,但本质还是会作为常量使用
final String msgTemp = msg; //msgTemp实质是final类型
Platform.runLater(()->{
taDisplay.appendText( msgTemp + "\n");
});
}
//跳出了循环,说明服务器已关闭,读取为null,提示对话关闭
Platform.runLater(()->{
taDisplay.appendText("对话已关闭!\n" );
});
}, "my-readServerThread"); //给新线程取别名,方便识别
receiveThread.start(); //启动线程
…… 省略……
以上代码中有四点注意:
(1)由于是新开的一个线程循环读取服务器的信息,所以不用考虑服务器是否有发欢迎信息,就算读取不到信息也只是阻塞这个线程,主程序本身使用没有任何影响(单线程就会卡住)。事实上服务器发多少信息都没问题,该线程通过循环语句来读取,没信息过来就阻塞等待,当服务器关闭连接时,就会跳出循环语句,结束本线程;
(2)现在接收并显示服务端信息的任务交给了一个单独的线程,那么原来主线程中连接按钮和发送按钮的动作事件代码中,关于接收并显示服务端信息的代码都必须删除,因为该任务完全由单独线程接管,如果这些代码保留,会存在潜在隐患(可能出现什么问题?)
(3)对于JavaFX窗体界面,在新线程中无法直接更新界面中有关控件的内容,只能将更新代码放在PlatForm.runLater(Runnable XXX)方法的Runnable子类实例中,如以上代码第15-17行、20-22行所示;
(4)匿名内部类或lambda表达式中,不能访问外部类方法中的非final类型的局部变量,例如上面第16行代码如果直接使用taDisplay.appendText( msg + “\n”);就会报错,所以代码第14行使用了个临时常量来解决这个问题,其实不使用final关键字,也会自动识别为final类型来使用(如果将msg定义为类中的成员变量,就没有这个限制,可以直接访问)
2. 程序退出部分思考
由于“退出”按钮和关闭窗体的事件响应都需要调用这部分代码,所以将之封装为exit()方法:
private void exit() {
if(tcpClient != null){
tcpClient.send("bye"); //向服务器发送关闭连接的约定信息
tcpClient.close();
}
System.exit(0);
}
先成功连接服务器,在正常发送一些信息后,不通过按钮发送信息输入区的bye告知服务器,这时候直接点击退出,很大概率会抛出异常信息后结束程序,其实这不算问题,交互还是可以正常完成。如果你的程序出现了这种情况,请思考抛出异常的原因?请尝试解决。
项目结构
完整代码
chapter03/TCPClient.java
package chapter03;
import java.io.*;
import java.net.Socket;
/**
* @projectName: NetworkApp
* @package: chapter02
* @className: TCPCilent
* @author: GCT
* @description: TODO
* @date: 2022/9/4 18:06
* @version: 1.0
*/
public class TCPClient {
private Socket socket; //定义套接字
//定义字符输入流和输出流
private PrintWriter pw;
private BufferedReader br;
public TCPClient(String ip, String port) throws IOException {
//主动向服务器发起连接,实现TCP的三次握手过程
//如果不成功,则抛出错误信息,其错误信息交由调用者处理
socket = new Socket(ip, Integer.parseInt(port));
//得到网络输出字节流地址,并封装成网络输出字符流
OutputStream socketOut = socket.getOutputStream();
pw = new PrintWriter( // 设置最后一个参数为true,表示自动flush数据
new OutputStreamWriter(//设置utf-8编码
socketOut, "utf-8"), true);
//得到网络输入字节流地址,并封装成网络输入字符流
InputStream socketIn = socket.getInputStream();
br = new BufferedReader(
new InputStreamReader(socketIn, "utf-8"));
}
public void send(String msg) {
//输出字符流,由Socket调用系统底层函数,经网卡发送字节流
pw.println(msg);
}
public String receive() {
String msg = null;
try {
//从网络输入字符流中读信息,每次只能接受一行信息
//如果不够一行(无行结束符),则该语句阻塞等待,
// 直到条件满足,程序才往下运行
msg = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return msg;
}
public void close() {
try {
if (socket != null) {
//关闭socket连接及相关的输入输出流,实现四次握手断开
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException{
TCPClient tcpClient = new TCPClient("127.0.0.1", "8008");
tcpClient.send("hello");
System.out.println(tcpClient.receive());
}
}
chapter03/TCPClientThreadFX.java
package chapter03;
import chapter01.TextFileIO;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* @projectName: NetworkApp
* @package: chapter02
* @className: TCPClientFX
* @author: GCT
* @description: TODO
* @date: 2022/9/4 18:08
* @version: 1.0
*/
public class TCPClientThreadFX extends Application {
private Button btnExit = new Button("退出");
private Button btnSend = new Button("发送");
private Button btnConnect = new Button("连接");
Thread receiveThread; //定义成员变量,读取服务器信息的线程
// private Button btnOpen = new Button("加载");
// private Button btnSave = new Button("保存");
//待发送信息的文本框
private TextField tfip = new TextField();
private TextField tfport = new TextField();
private TextField tfSend = new TextField();
//显示信息的文本区域
private TextArea taDisplay = new TextArea();
private TCPClient tcpClient;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// 将TextFileIO类实例化为textFileIO
TextFileIO textFileIO = new TextFileIO();
BorderPane mainPane = new BorderPane();
// 顶部的ip和端口输入框区域
HBox topHBox = new HBox();
topHBox.setSpacing(10);
topHBox.setPadding(new Insets(10,20,10,20));
topHBox.setAlignment(Pos.CENTER);
topHBox.getChildren().addAll(new Label("IP: "),tfip,new Label("端口号:"),tfport,btnConnect);
mainPane.setTop(topHBox);
//内容显示区域
VBox vBox = new VBox();
vBox.setSpacing(10);//各控件之间的间隔
//VBox面板中的内容距离四周的留空区域
vBox.setPadding(new Insets(10,20,10,20));
vBox.getChildren().addAll(new Label("信息显示区:"),
taDisplay,new Label("信息输入区:"), tfSend);
//设置显示信息区的文本区域可以纵向自动扩充范围
VBox.setVgrow(taDisplay, Priority.ALWAYS);
mainPane.setCenter(vBox);
//底部按钮区域
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setPadding(new Insets(10,20,10,20));
hBox.setAlignment(Pos.CENTER_RIGHT);
// hBox.getChildren().addAll(btnSend,btnSave,btnOpen,btnExit);
hBox.getChildren().addAll(btnSend,btnExit);
mainPane.setBottom(hBox);
Scene scene = new Scene(mainPane,700,400);
primaryStage.setScene(scene);
primaryStage.show();
//……
--------事件处理代码部分--------
//……
// 连接
btnConnect.setOnAction(event -> {
String ip = tfip.getText().trim();
String port = tfport.getText().trim();
try {
//tcpClient不是局部变量,是本程序定义的一个TCPClient类型的成员变量
tcpClient = new TCPClient(ip,port);
//成功连接服务器,接收服务器发来的第一条欢迎信息
String firstMsg = tcpClient.receive();
taDisplay.appendText(firstMsg + "\n");
//用于接收服务器信息的单独线程
receiveThread = new Thread(()->{
String msg = null;
//不知道服务器有多少回传信息,就持续不断接收
//由于在另外一个线程,不会阻塞主线程的正常运行
while ((msg = tcpClient.receive()) != null) {
//runLater中的lambda表达式不能直接访问外部非final类型局部变量
//所以这里使用了一个临时常量,可以省略final,但本质还是会作为常量使用
final String msgTemp = msg; //msgTemp实质是final类型
Platform.runLater(()->{
taDisplay.appendText( msgTemp + "\n");
});
}
//跳出了循环,说明服务器已关闭,读取为null,提示对话关闭
Platform.runLater(()->{
taDisplay.appendText("对话已关闭!\n" );
});
}, "my-readServerThread"); //给新线程取别名,方便识别
receiveThread.start(); //启动线程
} catch (Exception e) {
taDisplay.appendText("服务器连接失败!" + e.getMessage() + "\n");
}
});
btnExit.setOnAction(event -> {
if(tcpClient != null){
//向服务器发送关闭连接的约定信息
tcpClient.send("bye");
tcpClient.close();
}
System.exit(0);
});
// 设置taDisplay自动换行
taDisplay.setWrapText(true);
// 设置taDisplay只读
taDisplay.setEditable(false);
// 退出按钮事件
// btnExit.setOnAction(event -> {System.exit(0);});
btnExit.setOnAction(event -> endSystem());
// 发送按钮事件
btnSend.setOnAction(event -> {
String sendMsg = tfSend.getText();
if (sendMsg.equals("bye")){
btnConnect.setDisable(false);
btnSend.setDisable(true);
}
tcpClient.send(sendMsg);//向服务器发送一串字符
taDisplay.appendText("客户端发送:" + sendMsg + "\n");
String receiveMsg = tcpClient.receive();//从服务器接收一行字符
taDisplay.appendText(receiveMsg + "\n");
});
// tfSend.setOnKeyPressed(event -> {
// if (event.getCode() == KeyCode.ENTER){
// if (event.isShiftDown()){
// String msg = tfSend.getText();
// taDisplay.appendText("echo: "+ msg + "\n");
// tfSend.clear();
// }
// else{
// String msg = tfSend.getText();
// taDisplay.appendText(msg + "\n");
// tfSend.clear();
// }
// }
//
// });
// btnSave.setOnAction(event -> {
// //添加当前时间信息进行保存
// textFileIO.append(
// LocalDateTime.now().withNano(0) +" "+ taDisplay.getText());
// });
//
// btnOpen.setOnAction(event -> {
// String msg = textFileIO.load();
// if(msg != null){
// taDisplay.clear();
// taDisplay.setText(msg);
// }
// });
}
private void endSystem(){
if (tcpClient!=null){
tcpClient.send("bye");
tcpClient.close();
}
System.exit(0);
}
}
chapter03/TCPServer.java
package chapter03;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* @projectName: NetworkApp
* @package: chapter02
* @className: TCPServer
* @author: GCT
* @description: TODO
* @date: 2022/8/30 20:28
* @version: 1.0
*/
public class TCPServer {
private int port = 8008; //服务器监听端口
private ServerSocket serverSocket; //定义服务器套接字
public TCPServer() throws IOException {
serverSocket = new ServerSocket(port);
System.out.println("服务器启动监听在 " + port + " 端口");
}
private PrintWriter getWriter(Socket socket) throws IOException {
//获得输出流缓冲区的地址
OutputStream socketOut = socket.getOutputStream();
//网络流写出需要使用flush,这里在PrintWriter构造方法中直接设置为自动flush
return new PrintWriter(
new OutputStreamWriter(socketOut, "utf-8"), true);
}
private BufferedReader getReader(Socket socket) throws IOException {
//获得输入流缓冲区的地址
InputStream socketIn = socket.getInputStream();
return new BufferedReader(
new InputStreamReader(socketIn, "utf-8"));
}
//单客户版本,即每一次只能与一个客户建立通信连接
public void Service() {
while (true) {
Socket socket = null;
try {
//此处程序阻塞等待,监听并等待客户发起连接,有连接请求就生成一个套接字。
socket = serverSocket.accept();
//本地服务器控制台显示客户端连接的用户信息
System.out.println("New connection accepted: " + socket.getInetAddress().getHostAddress());
BufferedReader br = getReader(socket);//定义字符串输入流
PrintWriter pw = getWriter(socket);//定义字符串输出流
//客户端正常连接成功,则发送服务器的欢迎信息,然后等待客户发送信息
pw.println("From 服务器:欢迎使用本服务!");
String msg = null;
//此处程序阻塞,每次从输入流中读入一行字符串
while ((msg = br.readLine()) != null) {
//如果客户发送的消息为"bye",就结束通信
if (msg.trim().equals("bye")) {
//向输出流中输出一行字符串,远程客户端可以读取该字符串
pw.println("From服务器:服务器断开连接,结束服务!");
System.out.println("客户端离开");
break; //结束循环
}
//向输出流中输出一行字符串,远程客户端可以读取该字符串
pw.println("From服务器:" + msg);
//下面多增加一条信息返回语句
pw.println("来自服务器,重复发送: " + msg);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(socket != null)
socket.close(); //关闭socket连接及相关的输入输出流
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException{
new TCPServer().Service();
}
}
lookupscore/LookUpScore.java
package lookupscore;
import java.io.*;
import java.net.Socket;
/**
* @projectName: NetworkApp
* @package: chapter02
* @className: TCPCilent
* @author: GCT
* @description: TODO
* @date: 2022/9/4 18:06
* @version: 1.0
*/
public class LookUpScore {
private Socket socket; //定义套接字
//定义字符输入流和输出流
private PrintWriter pw;
private BufferedReader br;
public LookUpScore(String ip, String port) throws IOException {
//主动向服务器发起连接,实现TCP的三次握手过程
//如果不成功,则抛出错误信息,其错误信息交由调用者处理
socket = new Socket(ip, Integer.parseInt(port));
//得到网络输出字节流地址,并封装成网络输出字符流
OutputStream socketOut = socket.getOutputStream();
pw = new PrintWriter( // 设置最后一个参数为true,表示自动flush数据
new OutputStreamWriter(//设置utf-8编码
socketOut, "utf-8"), true);
//得到网络输入字节流地址,并封装成网络输入字符流
InputStream socketIn = socket.getInputStream();
br = new BufferedReader(
new InputStreamReader(socketIn, "utf-8"));
}
public void send(String msg) {
//输出字符流,由Socket调用系统底层函数,经网卡发送字节流
pw.println(msg);
}
public String receive() {
String msg = null;
try {
//从网络输入字符流中读信息,每次只能接受一行信息
//如果不够一行(无行结束符),则该语句阻塞等待,
// 直到条件满足,程序才往下运行
msg = br.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return msg;
}
public void close() {
try {
if (socket != null) {
//关闭socket连接及相关的输入输出流,实现四次握手断开
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException{
LookUpScore tcpClient = new LookUpScore("127.0.0.1", "8008");
tcpClient.send("hello");
System.out.println(tcpClient.receive());
}
}
lookupscore/LookUpScoreFX.java
package lookupscore;
import chapter01.TextFileIO;
import chapter03.TCPClient;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
/**
* @projectName: NetworkApp
* @package: chapter02
* @className: TCPClientFX
* @author: GCT
* @description: TODO
* @date: 2022/9/4 18:08
* @version: 1.0
*/
public class LookUpScoreFX extends Application {
private Button btnExit = new Button("退出");
private Button btnSend = new Button("发送");
private Button btnConnect = new Button("连接");
Thread receiveThread; //定义成员变量,读取服务器信息的线程
// private Button btnOpen = new Button("加载");
// private Button btnSave = new Button("保存");
//待发送信息的文本框
private TextField tfip = new TextField();
private TextField tfport = new TextField();
private TextField tfSend = new TextField();
//显示信息的文本区域
private TextArea taDisplay = new TextArea();
private TCPClient tcpClient;
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
// 将TextFileIO类实例化为textFileIO
TextFileIO textFileIO = new TextFileIO();
BorderPane mainPane = new BorderPane();
// 顶部的ip和端口输入框区域
HBox topHBox = new HBox();
topHBox.setSpacing(10);
topHBox.setPadding(new Insets(10,20,10,20));
topHBox.setAlignment(Pos.CENTER);
topHBox.getChildren().addAll(new Label("IP: "),tfip,new Label("端口号:"),tfport,btnConnect);
mainPane.setTop(topHBox);
//内容显示区域
VBox vBox = new VBox();
vBox.setSpacing(10);//各控件之间的间隔
//VBox面板中的内容距离四周的留空区域
vBox.setPadding(new Insets(10,20,10,20));
vBox.getChildren().addAll(new Label("信息显示区:"),
taDisplay,new Label("信息输入区:"), tfSend);
//设置显示信息区的文本区域可以纵向自动扩充范围
VBox.setVgrow(taDisplay, Priority.ALWAYS);
mainPane.setCenter(vBox);
//底部按钮区域
HBox hBox = new HBox();
hBox.setSpacing(10);
hBox.setPadding(new Insets(10,20,10,20));
hBox.setAlignment(Pos.CENTER_RIGHT);
// hBox.getChildren().addAll(btnSend,btnSave,btnOpen,btnExit);
hBox.getChildren().addAll(btnSend,btnExit);
mainPane.setBottom(hBox);
Scene scene = new Scene(mainPane,700,400);
primaryStage.setScene(scene);
primaryStage.setTitle("登录查成绩");
primaryStage.show();
//……
--------事件处理代码部分--------
//……
// 连接
btnConnect.setOnAction(event -> {
String ip = tfip.getText().trim();
String port = tfport.getText().trim();
try {
//tcpClient不是局部变量,是本程序定义的一个TCPClient类型的成员变量
tcpClient = new TCPClient(ip,port);
//成功连接服务器,接收服务器发来的第一条欢迎信息
String firstMsg = tcpClient.receive();
taDisplay.appendText(firstMsg + "\n");
} catch (Exception e) {
taDisplay.appendText("服务器连接失败!" + e.getMessage() + "\n");
}
//用于接收服务器信息的单独线程
receiveThread = new Thread(()->{
String msg = null;
//不知道服务器有多少回传信息,就持续不断接收
//由于在另外一个线程,不会阻塞主线程的正常运行
while ((msg = tcpClient.receive()) != null) {
//runLater中的lambda表达式不能直接访问外部非final类型局部变量
//所以这里使用了一个临时常量,可以省略final,但本质还是会作为常量使用
final String msgTemp = msg; //msgTemp实质是final类型
Platform.runLater(()->{
taDisplay.appendText( msgTemp + "\n");
});
}
//跳出了循环,说明服务器已关闭,读取为null,提示对话关闭
Platform.runLater(()->{
taDisplay.appendText("对话已关闭!\n" );
});
}, "my-readServerThread"); //给新线程取别名,方便识别
receiveThread.start(); //启动线程
});
btnExit.setOnAction(event -> {
if(tcpClient != null){
//向服务器发送关闭连接的约定信息
tcpClient.send("bye");
tcpClient.close();
}
System.exit(0);
});
// 设置taDisplay自动换行
taDisplay.setWrapText(true);
// 设置taDisplay只读
taDisplay.setEditable(false);
// 退出按钮事件
// btnExit.setOnAction(event -> {System.exit(0);});
btnExit.setOnAction(event -> endSystem());
// 发送按钮事件
btnSend.setOnAction(event -> {
String sendMsg = tfSend.getText();
if (sendMsg.equals("bye")){
btnConnect.setDisable(false);
btnSend.setDisable(true);
}
tcpClient.send(sendMsg);//向服务器发送一串字符
taDisplay.appendText("客户端发送:" + sendMsg + "\n");
String receiveMsg = tcpClient.receive();//从服务器接收一行字符
taDisplay.appendText(receiveMsg + "\n");
});
// tfSend.setOnKeyPressed(event -> {
// if (event.getCode() == KeyCode.ENTER){
// if (event.isShiftDown()){
// String msg = tfSend.getText();
// taDisplay.appendText("echo: "+ msg + "\n");
// tfSend.clear();
// }
// else{
// String msg = tfSend.getText();
// taDisplay.appendText(msg + "\n");
// tfSend.clear();
// }
// }
//
// });
// btnSave.setOnAction(event -> {
// //添加当前时间信息进行保存
// textFileIO.append(
// LocalDateTime.now().withNano(0) +" "+ taDisplay.getText());
// });
//
// btnOpen.setOnAction(event -> {
// String msg = textFileIO.load();
// if(msg != null){
// taDisplay.clear();
// taDisplay.setText(msg);
// }
// });
}
private void endSystem(){
if (tcpClient!=null){
tcpClient.send("bye");
tcpClient.close();
}
System.exit(0);
}
}