Client-Server发送文件时对话框选择目录功能

若要实现客户端向服务器端发送文件的功能,客户端须有选择文件的按钮,服务器端须有选择储存目录、打开目录的按钮,解一下这两种做法。

首先客户端:三条语句即可

 

FileDialog filedialog = new FileDialog(new Frame(), "打开文件对话框",FileDialog.LOAD);

filedialog.setVisible(true);

filePathFiled.setText(filedialog.getDirectory()+ filedialog.getFile());//将路径填入选择文件的路径文本框

 

FileDialog (Frame parent, String title, int mode)
创建一个具有指定标题的文件对话框窗口,用于加载或保存文件。

getDirectory()+getFile() = 文件路径

public class FileDialog extends Dialog
FileDialog 类显示出一个对话框窗口,用户可以从中选择文件。
因为它是一个模式对话框,当应用调用它的 show 方法来显示对话框时,它会阻塞应用的其余部分直到用户选择了一个文件。

 

变量索引
LOAD 该常量值表明文件对话框窗口的用途是确定从哪里读取文件。
SAVE 该常量值表明文件对话框窗口的用途是确定向哪里写入文件。

构造子索引
FileDialog(Frame) 创建一个读取文件的文件对话框。
FileDialog(Frame, String) 以指定的标题创建一个读取文件的文件对话框窗口。
FileDialog(Frame, String, int) 以指定的标题创建一个读取或保存文件的文件对话框窗口。

如果 mode 值是 LOAD ,那么文件对话框将找到文件来读取。 如果mode 值是SAVE ,那么文件对话框将找到位置来写入文件。
参数: parent - 对话框的拥有者。title - 对话框的标题。mode - 对话框的模式。

方法索引
addNotify() 创建文件对话框的同级件。
getDirectory() 获取该文件对话框的目录。
getFile() 获取该文件对话框所选中的文件。
getFilenameFilter() 确定该文件对话框的文件名过滤器。
getMode() 表示该文件对话框是用于读取文件或是用于保存文件。
paramString() 返回表示该文件对话框窗口的状态的参数字符串。
setDirectory(String) 将该文件对话框窗口的目录设置为指定的目录。
setFile(String) 将该文件对话框窗口的选定文件设置为指定的文件。
setFilenameFilter(FilenameFilter) 将该文件对话框窗口的文件名过滤器设置为指定的过滤器。
setMode(int) 设置文件对话框的模式。

如下:
public static final int LOAD 该常量值表明文件对话框窗口的用途是确定从哪里读取文件。
public static final int SAVE 该常量值表示文件对话框窗口的用途是确定向哪里写入文件。

具体事件处理

chooseButton = new JButton("\u9009\u62E9\u6587\u4EF6");
chooseButton.addActionListener(new ActionListener() {//采用匿名内部类方式,简洁便于管理
public void actionPerformed(ActionEvent e) {
FileDialog filedialog = new FileDialog(new Frame(), "打开文件对话框",
FileDialog.LOAD);
filedialog.setVisible(true);
filePathFiled.setText(filedialog.getDirectory()+ filedialog.getFile());
}
});
chooseButton.setBounds(284, 34, 93, 23);
contentPane.add(chooseButton);

服务器端:

选择目录

setSelectedFile(),返回选中的文件

JFileChooser jfc = new JFileChooser();创建一个文件选择器对象

jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

该对象设置为只选择目录

该对象调用showOpenDialog(null)会弹出对话框

选完之后,getSelectedFile()返回选中的文件

获得被选择文件的路径String path = jfc.getSelectedFile().getAbsolutePath();

显示到路径文本框this.savePath.setText(path);

 打开目录

Desktop d = Desktop.getDesktop();

d.open(new File(this.savePath.getText()));

参数为文件对象,若是目录,则启动文件管理器打开。

// 四个按钮的单击事件
public void actionPerformed(ActionEvent e) {
if (e.getSource() == selectButton) {
JFileChooser jfc = new JFileChooser();
jfc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);//设置为只选择目录
int res = jfc.showOpenDialog(null);//弹出文件选择器对话框
if (res == JFileChooser.APPROVE_OPTION) {//若对话框等于选择确认后的返回值
String path = jfc.getSelectedFile().getAbsolutePath();//获得被选择文件的路径
this.savePath.setText(path);
}
} else if (e.getSource() == open) {
Desktop d = Desktop.getDesktop();
try {
d.open(new File(this.savePath.getText()));//参数为文件,若该文件是目录,则启动文件管理器打开
} catch (IOException e1) {
e1.printStackTrace();
}


} else if (e.getSource() == cleanButton) {
this.textArea.setText("");
} else {
set.sendToAll(textArea.getText() + "\n");//set对象完成向数组中的各个socket传递消息。
JOptionPane.showMessageDialog(null, "发送成功!");
}
}
}

以上。

<think>根据用户要求,我们需要在修复后的客户端代码基础上添加文件传输功能文件传输功能需要以下步骤: 1. 客户端选择发送文件。 2. 客户端将文件信息(文件名、大小等)发送给服务器。 3. 客户端分块发送文件内容,并显示进度。 4. 服务器接收并保存文件。 由于原问题中已经修复了接收方法,现在主要添加文件发送功能。 我们将修改客户端,添加一个发送文件的按钮,并实现文件选择文件信息发送和分块传输的功能。 注意:文件传输使用新的消息类型来区分普通消息和文件传输。我们定义协议如下: - 发送文件开始发送消息:`FILE_START:{filename}:{filesize}` - 发送文件内容:直接发送二进制数据块 - 发送文件结束:`FILE_END` 但是,由于我们的聊天消息和文件数据混合在一个套接字中,我们需要设计一个简单的协议来区分。然而,这样混合传输可能会使协议复杂化。因此,我们可以考虑以下两种方案: 方案1:使用两个连接,一个用于聊天消息,一个用于文件传输(需要服务器支持两个端口)。 方案2:在同一个连接中,使用不同的消息类型标识(文本消息和文件数据块)。 由于间关系,我们采用方案2,并设计协议如下: - 文本消息:直接发送UTF-8编码的字符串。 - 文件传输消息:先发送一个控制消息(文本),然后发送二进制数据块。 但是,这样会导致接收方难以区分。因此,我们可以在发送文件数据块之前发送一个控制消息,告诉接收方接下来是二进制数据块。同,为了不破坏原有接收逻辑,我们需要修改接收方法,使其能够处理二进制数据。 然而,由于原接收方法只处理文本消息(使用recv(1024)然后decode),如果遇到二进制数据,decode会出错。因此,我们需要重新设计接收逻辑,使其能够根据消息类型分别处理。 考虑到间,我们可以采用另一种简单方法:文件传输使用新的套接字连接。这样,聊天和文件传输分开,互不干扰。但这样需要服务器额外监听一个文件传输端口。 为了简化,我们这里采用同一个连接,但使用Base64编码将文件数据转换为文本发送。这样接收方可以统一按文本接收,但效率会降低(大约增加33%的数据量)。对于大文件可能不适用,但作为示例可行。 另一种方法是修改接收逻辑:在接收消息,先接收一个消息头(固定长度,比如4字节)表示消息类型(文本或二进制),然后根据类型接收后续数据。但这样需要较大改动。 鉴于用户要求修复接收方法后添加文件传输,我们采用Base64编码方式发送文件,这样接收方法不需要大改,只需要在客户端和服务器端都增加对文件消息的解析。 具体协议设计: - 客户端发送文件,先发送一条特殊文本消息:`FILE:{filename}:{filesize}` - 然后发送一条或多条消息,每条消息都是文件块的Base64编码字符串(注意:每个Base64字符串以换行符结束,以便接收方按行读取) - 最后发送`FILE_END`表示文件传输结束。 服务器接收- 当收到以`FILE:`开头的消息,就知道接下来是文件数据,直到收到`FILE_END`。 - 服务器将中间的Base64字符串解码并写入文件。 但这样需要服务器维护每个客户端的文件传输状态(哪个客户端在传文件文件名是什么,文件大小等),较为复杂。 因此,我们简化:每个文件块都发送一条消息,消息格式为: `FILE_BLOCK:{filename}:{block_index}:{base64_data}` 这样每个数据块都是独立的消息,服务器只需按块接收并写入文件即可,无需维护状态。 但是,这样会使得消息量很大,且每个消息都有文件名和块索引,增加额外开销。 综合考虑,我们采用以下折中方案: 1. 客户端发送文件开始消息:`FILE_START:{filename}:{filesize}` 2. 然后发送多个文件块消息:`FILE_BLOCK:{base64_data}` (注意:这里我们假设服务器已经知道当前在接收哪个文件,所以不需要重复文件名。但这样就需要服务器为每个客户端维护一个当前传输的文件状态) 3. 最后发送`FILE_END` 为了简化服务器状态管理,我们可以在文件开始消息中包含一个唯一的文件传输ID(比如间戳),然后在每个块中也包含这个ID。 但是,由于间关系,我们这里不实现太复杂的,而是假设同一间一个客户端只传输一个文件,且服务器为每个客户端维护一个当前传输的文件。 因此,服务器端需要记录: - 每个客户端当前是否在接收文件 - 当前接收的文件- 当前接收的文件大小 - 已接收的数据量 - 文件句柄 由于原问题只要求客户端代码,我们这里只展示客户端实现,服务器端实现需要相应调整(不在本题要求)。 客户端实现步骤: 1. 添加一个“发送文件”按钮。 2. 点击按钮后弹出文件选择对话框。 3. 读取文件,分块进行Base64编码(注意:每块大小要小于1024字节,因为Base64编码后每块会变大,所以原始数据块大小设为700字节,这样Base64后大约936字节,小于1024)。 4. 先发送文件开始消息:`FILE_START:{filename}:{filesize}` 5. 然后循环发送每个块:`FILE_BLOCK:{base64_data}` 6. 最后发送`FILE_END` 同,在客户端接收逻辑中,我们也要处理来自服务器的文件传输消息(如果需要接收文件,则同样按照协议解析)。但本题只要求发送,所以先实现发送功能。 注意:在同一个连接中,文件传输消息和普通聊天消息会交叉出现,因此接收方需要能够区分。 修改客户端接收方法:在接收到消息后,判断消息是否以`FILE_START`、`FILE_BLOCK`或`FILE_END`开头,如果是则按文件传输处理,否则按普通消息显示。 由于我们之前修复的接收方法已经可以显示任何消息,所以文件传输的控制消息也会被显示出来。这并不友好,我们需要在客户端中处理文件传输消息,将其保存为文件。 但题目要求是发送文件,所以我们先完成发送功能。接收文件功能需要另外实现,不在本题要求。 因此,我们只实现客户端的文件发送功能,接收功能暂不实现(即接收到的文件消息仍然显示为文本,但实际是Base64数据,用户无法直接使用)。 以下是修改后的客户端代码,添加了发送文件功能:</think>### 修复接收逻辑并添加文件传输功能的完整客户端代码 以下是修复了`receive`方法接收逻辑,并添加了文件传输功能的完整客户端代码: ```python import socket import threading import tkinter as tk import os import base64 from tkinter import filedialog, simpledialog, messagebox, scrolledtext class ChatClient: def __init__(self, host='127.0.0.1', port=5555): self.host = host self.port = port self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.nickname = "" self.file_transfer_mode = False # 文件传输模式标志 self.current_file = None # 当前接收的文件信息 # 初始化GUI self.root = tk.Tk() self.root.title("聊天客户端") self.root.geometry("700x550") # 连接状态标签 self.status_label = tk.Label(self.root, text="未连接到服务器", font=("Arial", 10)) self.status_label.pack(pady=5) # 聊天区域 self.chat_area = scrolledtext.ScrolledText(self.root, state='disabled') self.chat_area.pack(fill='both', expand=True, padx=10, pady=5) # 文件传输状态标签 self.file_status = tk.Label(self.root, text="", font=("Arial", 9)) self.file_status.pack(fill='x', padx=10) # 消息输入框 self.msg_entry = tk.Entry(self.root, font=("Arial", 12)) self.msg_entry.pack(fill='x', padx=10, pady=5) self.msg_entry.bind("<Return>", self.send_message) # 按钮框架 button_frame = tk.Frame(self.root) button_frame.pack(fill='x', padx=10, pady=5) # 连接按钮 self.connect_button = tk.Button(button_frame, text="连接服务器", command=self.connect_to_server) self.connect_button.pack(side='left', padx=5) # 发送文件按钮 self.file_button = tk.Button(button_frame, text="发送文件", command=self.send_file, state='disabled') self.file_button.pack(side='left', padx=5) self.running = False self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.root.mainloop() def display_message(self, message): """在聊天区域显示消息""" self.chat_area.config(state='normal') self.chat_area.insert(tk.END, message + "\n") self.chat_area.yview(tk.END) self.chat_area.config(state='disabled') def connect_to_server(self): """连接到服务器""" try: # 获取昵称 self.nickname = simpledialog.askstring("昵称", "请输入您的昵称:", parent=self.root) if not self.nickname: messagebox.showerror("错误", "必须输入昵称") return # 连接服务器 self.client_socket.connect((self.host, self.port)) self.client_socket.send(f"NICK:{self.nickname}".encode('utf-8')) # 更新UI状态 self.status_label.config(text=f"已连接到 {self.host}:{self.port} | 昵称: {self.nickname}") self.connect_button.config(text="已连接", state="disabled") self.file_button.config(state='normal') self.display_message(f"[系统] 已连接到服务器,您的昵称是: {self.nickname}") # 启动接收线程 self.running = True receive_thread = threading.Thread(target=self.receive, daemon=True) receive_thread.start() except Exception as e: messagebox.showerror("连接错误", f"无法连接到服务器: {str(e)}") def receive(self): """接收服务器消息的核心方法(已修复)""" while self.running: try: # 接收消息 message = self.client_socket.recv(4096) # 增加缓冲区大小以处理文件块 if not message: # 服务器关闭连接 self.display_message("[系统] 服务器已断开连接") self.running = False break # 检查是否是文件传输控制消息 if message.startswith(b'FILE_START:'): # 文件传输开始 file_info = message.decode('utf-8').split(':', 2) if len(file_info) >= 3: sender = file_info[1] filename = file_info[2] self.start_file_receive(filename, sender) continue # 检查是否是文件块 if message.startswith(b'FILE_BLOCK:'): # 处理文件块 self.process_file_block(message) continue # 检查是否是文件结束消息 if message == b'FILE_END': # 文件传输结束 self.complete_file_receive() continue # 普通文本消息 self.display_message(message.decode('utf-8')) except ConnectionResetError: self.display_message("[系统] 连接被服务器重置") self.running = False break except ConnectionAbortedError: self.display_message("[系统] 连接已终止") self.running = False break except OSError as e: self.display_message(f"[系统错误] 连接问题: {str(e)}") self.running = False break except Exception as e: self.display_message(f"[系统错误] 接收消息出错: {str(e)}") self.running = False break def start_file_receive(self, filename, sender): """开始接收文件""" try: # 询问保存位置 save_path = filedialog.asksaveasfilename( initialfile=filename, title=f"保存来自 {sender} 的文件" ) if not save_path: self.display_message(f"[系统] 已取消接收文件: {filename}") return # 初始化文件接收状态 self.file_transfer_mode = True self.current_file = { 'filename': filename, 'sender': sender, 'file_path': save_path, 'bytes_received': 0, 'file_size': 0, # 将在接收第一个块设置 'file_handle': open(save_path, 'wb') } self.display_message(f"[系统] 开始接收文件: {filename} 来自 {sender}") self.file_status.config(text=f"接收文件中: {filename}...") except Exception as e: self.display_message(f"[错误] 无法开始接收文件: {str(e)}") self.file_transfer_mode = False if self.current_file and 'file_handle' in self.current_file: self.current_file['file_handle'].close() self.current_file = None def process_file_block(self, message): """处理接收到的文件块""" if not self.file_transfer_mode or not self.current_file: self.display_message("[错误] 收到文件块但未在文件接收模式") return try: # 解析文件块消息 parts = message.split(b':', 1) if len(parts) < 2: self.display_message("[错误] 无效的文件块格式") return block_data = base64.b64decode(parts[1]) # 如果是第一个块,获取文件大小 if self.current_file['bytes_received'] == 0: # 文件大小存储在第一个块的前4个字节 file_size = int.from_bytes(block_data[:4], 'big') self.current_file['file_size'] = file_size block_data = block_data[4:] # 移除文件大小信息 # 写入文件 self.current_file['file_handle'].write(block_data) self.current_file['bytes_received'] += len(block_data) # 更新进度 if self.current_file['file_size'] > 0: progress = int((self.current_file['bytes_received'] / self.current_file['file_size']) * 100) self.file_status.config( text=f"接收文件: {self.current_file['filename']} - {progress}% 完成" ) except Exception as e: self.display_message(f"[错误] 处理文件块失败: {str(e)}") self.cancel_file_receive() def complete_file_receive(self): """完成文件接收""" if not self.file_transfer_mode or not self.current_file: return try: self.current_file['file_handle'].close() filename = self.current_file['filename'] sender = self.current_file['sender'] file_size = self.current_file['bytes_received'] self.display_message(f"[系统] 成功接收文件: {filename} ({file_size} 字节) 来自 {sender}") self.file_status.config(text=f"文件接收完成: {filename}") except Exception as e: self.display_message(f"[错误] 完成文件接收失败: {str(e)}") finally: self.file_transfer_mode = False self.current_file = None def cancel_file_receive(self): """取消文件接收""" if self.file_transfer_mode and self.current_file: try: if 'file_handle' in self.current_file: self.current_file['file_handle'].close() # 删除未完成的文件 if os.path.exists(self.current_file['file_path']): os.remove(self.current_file['file_path']) except: pass self.display_message("[系统] 文件接收已取消") self.file_status.config(text="文件接收已取消") self.file_transfer_mode = False self.current_file = None def send_message(self, event=None): """发送消息到服务器""" message = self.msg_entry.get().strip() if not message: return try: # 发送消息 self.client_socket.send(message.encode('utf-8')) self.msg_entry.delete(0, tk.END) except Exception as e: self.display_message(f"[错误] 发送失败: {str(e)}") def send_file(self): """发送文件到服务器""" try: # 选择文件 file_path = filedialog.askopenfilename( title="选择发送文件" ) if not file_path: return filename = os.path.basename(file_path) file_size = os.path.getsize(file_path) # 通知服务器开始文件传输 start_msg = f"FILE_START:{self.nickname}:{filename}" self.client_socket.send(start_msg.encode('utf-8')) self.display_message(f"[系统] 开始发送文件: {filename} ({file_size} 字节)") self.file_status.config(text=f"发送文件中: {filename}...") # 发送文件块 with open(file_path, 'rb') as file: # 发送第一个块(包含文件大小) first_block = True while True: chunk = file.read(3072) # 3KB的块 if not chunk: break # 如果是第一个块,添加文件大小信息 if first_block: size_info = file_size.to_bytes(4, 'big') chunk = size_info + chunk first_block = False # 编码为Base64并发送 encoded_chunk = base64.b64encode(chunk) block_msg = b'FILE_BLOCK:' + encoded_chunk self.client_socket.send(block_msg) # 更新进度 progress = int((file.tell() / file_size) * 100) self.file_status.config( text=f"发送文件: {filename} - {progress}% 完成" ) # 发送文件结束标记 self.client_socket.send(b'FILE_END') self.display_message(f"[系统] 文件发送完成: {filename}") self.file_status.config(text=f"文件发送完成: {filename}") except Exception as e: self.display_message(f"[错误] 文件发送失败: {str(e)}") self.file_status.config(text="文件发送失败") def on_closing(self): """关闭客户端的清理操作""" self.running = False try: if self.client_socket: self.client_socket.close() except: pass # 清理未完成的文件传输 if self.file_transfer_mode: self.cancel_file_receive() self.root.destroy() if __name__ == "__main__": ChatClient() ``` ### 关键修复与文件传输实现说明 #### 1. 接收逻辑修复 ```python def receive(self): while self.running: try: message = self.client_socket.recv(4096) # 增加缓冲区大小 if not message: # 服务器关闭连接 break # 文件传输控制消息处理 if message.startswith(b'FILE_START:'): # 处理文件开始 continue if message.startswith(b'FILE_BLOCK:'): # 处理文件块 continue if message == b'FILE_END': # 处理文件结束 continue # 普通文本消息 self.display_message(message.decode('utf-8')) # 异常处理保持不变... ``` 修复重点: - 增加了接收缓冲区大小(4096字节)以处理文件- 添加了文件传输协议识别和处理逻辑 - 保持原有的异常处理机制 #### 2. 文件传输协议设计 文件传输使用以下协议格式: 1. **文件开始**:`FILE_START:发送者:文件名` 2. **文件块**:`FILE_BLOCK:Base64编码的数据块` 3. **文件结束**:`FILE_END` #### 3. 文件发送实现 ```python def send_file(self): # 选择文件 file_path = filedialog.askopenfilename() # 发送开始标记 start_msg = f"FILE_START:{self.nickname}:{filename}" self.client_socket.send(start_msg.encode('utf-8')) # 分块发送文件 with open(file_path, 'rb') as file: while True: chunk = file.read(3072) # 3KB的块 if not chunk: break # Base64编码并发送 encoded_chunk = base64.b64encode(chunk) block_msg = b'FILE_BLOCK:' + encoded_chunk self.client_socket.send(block_msg) # 发送结束标记 self.client_socket.send(b'FILE_END') ``` #### 4. 文件接收实现 ```python def start_file_receive(self, filename, sender): # 询问保存位置 save_path = filedialog.asksaveasfilename() # 初始化接收状态 self.current_file = { 'file_path': save_path, 'file_handle': open(save_path, 'wb') } def process_file_block(self, message): # 解析并解码数据块 block_data = base64.b64decode(message.split(b':', 1)[1]) # 写入文件 self.current_file['file_handle'].write(block_data) # 更新进度显示 progress = (bytes_received / file_size) * 100 self.file_status.config(text=f"接收文件: {filename} - {progress}% 完成") def complete_file_receive(self): # 关闭文件并显示完成消息 self.current_file['file_handle'].close() self.display_message(f"[系统] 成功接收文件: {filename}") ``` ### 文件传输优化说明 1. **分块传输**: - 文件被分成3KB的块进行传输 - 每个块使用Base64编码确保安全传输 - 接收端实更新进度显示[^3] 2. **进度反馈**: ```python # 发送端 progress = int((file.tell() / file_size) * 100) self.file_status.config(text=f"发送文件: {filename} - {progress}% 完成") # 接收端 progress = int((self.current_file['bytes_received'] / self.current_file['file_size']) * 100) self.file_status.config(text=f"接收文件: {filename} - {progress}% 完成") ``` 提供实进度反馈,提升用户体验[^3] 3. **错误处理**: ```python def cancel_file_receive(self): if self.current_file: try: self.current_file['file_handle'].close() os.remove(self.current_file['file_path']) except: pass ``` 在传输中断自动清理未完成的文件 4. **内存优化**: ```python with open(file_path, 'rb') as file: while True: chunk = file.read(3072) # 分块读取 if not chunk: break ``` 使用流式传输避免大文件占用过多内存[^4] ### 使用说明 1. **连接服务器**: - 点击"连接服务器"按钮 - 输入昵称后建立连接 2. **发送文件**: - 点击"发送文件"按钮 - 选择发送文件 - 观察底部状态栏的传输进度 3. **接收文件**: - 当收到文件传输请求,系统会提示保存位置 - 选择保存路径后开始接收 - 接收进度显示在底部状态栏 4. **取消传输**: - 关闭客户端会自动取消进行中的传输 - 未完成的文件会被自动删除
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值