上一篇博客,我们通过多线程,将主线程和接受信息的线程分开,实现了客户端和服务器的自由交流。本篇博客将分享文件传输以及服务端的多线程。
首先是文件传输,文件传输就讲解客户端怎么请求和下载文件。文件传输其实分为两个部分,一个是负责与服务器通信的8008端口,另外一个是负责接收文件内容的2020端口(具体端口多少其实都可以自己设置,只要不占用一些重要的端口就好)
先来说一下界面,界面如下图,其实和上一篇博客的界面差不多,就是多了一个下载按钮。按照之前的操作,我们先要建立一个文件传输的类,负责接收文件的流。

import java.io.*;
import java.net.Socket;
public class FileDataClient {
private Socket dataSocket;
public FileDataClient(String ip, String port) throws IOException{
dataSocket = new Socket(ip , Integer.parseInt(port));
}
public void getFile(File saveFile) throws IOException {
if (dataSocket != null) {
FileOutputStream fileOut = new FileOutputStream(saveFile);
byte[] buf = new byte[1024];
InputStream socketIn = dataSocket.getInputStream();
OutputStream socketOut = dataSocket.getOutputStream();
PrintWriter pw = new PrintWriter(new OutputStreamWriter(socketOut, "utf-8"), true);
pw.println(saveFile.getName());
int size = 0;
while ((size = socketIn.read(buf)) != -1) {
fileOut.write(buf, 0, size);
}
fileOut.flush();
fileOut.close();
if (dataSocket != null) {
dataSocket.close();
}
} else {
System.err.println("连接ftp数据服务器失败");
}
}
}
这里其实接下来要用到很常见的FileChooser这个类,因此我这里就给大家总结一下一些常见的方法。

我们构造完这个文件传输的类后,就可以给保存这个按钮设置时间,然后实例化这个类来下载文件了。
btnDownload.setOnAction(event -> {
if(tfSend.getText().equals(""))
return;
String fName = tfSend.getText().trim();
tfSend.clear();
FileChooser fileChooser = new FileChooser();
fileChooser.setInitialFileName(fName);
File saveFile = fileChooser.showSaveDialog(null);
if (saveFile == null) {
return;
}
try {
String ip = tfIP.getText().trim();
new FileDataClient(ip, "2020").getFile(saveFile);
Alert alert = new Alert(Alert.AlertType.INFORMATION);
alert.setContentText(saveFile.getName() + " 下载完毕!");
alert.showAndWait();
fileDialogClient.send("客户端开启下载");
} catch (IOException e) {
e.printStackTrace();
}
});
上述其实已经实现了文件传输的基本内容。这里给大家一个有意思的功能,我们选择对话框的内容,就可以将内容直接出现在发送框。
taDisplay.selectionProperty().addListener((observable, oldValue, newValue) -> {
if(!taDisplay.getSelectedText().equals(""))
tfSend.setText(taDisplay.getSelectedText());
else if (taDisplay.getSelectedText().equals(""))
tfSend.clear();
});
其实我们可以发现文件传输和网络对话都有非常相似的地方,比如要构建网络的输入输出流,不过不同之处在于,网络对话中接收信息我们使用的是用BufferReader封装好的输入流中的readline方法进行读取服务器发送过来的信息,而文件传输则直接对底层的网络流inputstream进行操作,通过循环读取字节进行缓存和写入。
接下来是服务器的多线程,之前版本我们实现的是服务器的多线程,服务器一次只能跟一个用户进行连接,原因就是因为我们服务器把主线程作为网络交流,而其中有readline()这个阻塞语句,因此再不结束对话的前提下,会一直阻塞主线程。根据我们多线程客户端的经验,我们只要把跟客户端对话的这个readline()语句放在多线程中,就不会阻塞主线程,但由于连接的用户数量不固定,因此我们要设置一个静态或者动态的线程池来对新连接的用户进行线程分配。
package chapter05;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class GroupServer {
private int port = 8008;
private ServerSocket serverSocket;
private ExecutorService executorService = Executors. newCachedThreadPool();
private static Set<Socket> members = new CopyOnWriteArraySet<Socket>();
public GroupServer() throws IOException {
serverSocket = new ServerSocket(8008);
System.out.println("服务器启动监听在 " + port + " 端口");
}
private PrintWriter getWriter(Socket socket) throws IOException {
OutputStream socketOut = socket.getOutputStream();
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"));
}
class Handler implements Runnable{
private Socket socket;
public Handler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
System.out.println("New connection accepted: " + socket.getInetAddress().getHostAddress());
try {
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
pw.println("From 服务器:欢迎使用本服务!");
String msg = null;
while ((msg = br.readLine()) != null) {
if (msg.equals("bye")) {
pw.println("From服务器:服务器断开连接,结束服务!");
System.out.println(socket.getInetAddress().getHostAddress() + "客户端离开");
members.remove(socket);
break;
}
sendToAllMembers(msg, socket.getInetAddress().getHostAddress());
}
} catch (IOException e) {
e.printStackTrace();
}finally {
try {
if(socket != null)
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public void Service() throws IOException{
while (true) {
Socket socket = null;
socket = serverSocket.accept();
members.add(socket);
Handler handler = new Handler(socket);
executorService.execute(handler);
}
}
private void sendToAllMembers(String msg,String hostAddress) throws IOException{
PrintWriter pw;
OutputStream out;
for (Socket tempSocket : members) {
out = tempSocket.getOutputStream();
pw = new PrintWriter(
new OutputStreamWriter(out, "utf-8"), true);
pw.println(hostAddress + " 发言:" + msg );
}
}
public static void main(String[] args) throws IOException{
new GroupServer().Service();
}
}