Socket聊天程序(Java)

Socket聊天程序(Java)

大三网络实验课写的

内容

  1. 实现了一个基于TCP协议、Client/Server模式的聊天程序,相当于一个多人聊天室。

  2. 该聊天室允许多位用户同时在聊天室文本交流、传输文件和接受文件。

  3. 该聊天室支持离线文件和断点续传。

客户端与服务器功能

客户端

服务器

服务器程序(Server.java)设置了sockets队列,用来存储连接的socket。服务器创建服务端,开放端口后,在while循环里不断地阻塞等待用户的连接,当用户连接后,将该socket放入sockets队列里,并启动一个新的服务器线程ServerThread为该socket服务。使用服务器线程的方法使服务器允许多用户连接。
服务器线程程序(ServerThread.java)进行用户与服务器连接后的工作。服务器线程程序处理用户在线的整个过程,主要提供用户登录、用户基本文字聊天、用户之间传输文件(包括二进制大文件)、离线文件和断点续传等功能。实现方法如下:

  1. 用户登录功能:服务器将已经注册的用户名和密码存储在account_password.txt文件中,若用户登录时发送用户名和密码,服务器将其与存储的用户名密码进行匹配,若匹配成功则发送“登录成功”提示,用户可以继续进行后续操作,否则,发送“登录失败!账号或密码有误!”提示,并关闭该socket,用户则无法进行后续操作。

  2. 用户基本文字聊天功能:服务器阻塞等待接收用户的输入,一旦收到输入,则判断是否为文件操作,否则将在输入的字符串前加上该用户的ip地址和端口组成新的消息,然后给sockets队列加锁,防止对sockets队列的使用产生冲突,加锁成功后向sockets队列里所有连接的socket发送该消息。通过该方法提供聊天室功能,即一位用户发送的消息所有在线用户都能收到。

  3. 用户之间传输文件(包括二进制大文件)功能:服务器对用户每次的输入进行判断,若为发送文件,则打开接收文件的端口,与用户连接,启动ReceiveFileServerThread在新连接的socket上接收用户发送的文件,若为接收文件,则同理打开发送文件的端口,与用户连接,启动SendFileServerThread在新连接的socket上发送给用户所请求的文件。通过将聊天、发送文件、接收文件的端口分开,可以使用户同时进行聊天、发送文件、接收文件的多个操作,并且发送文件和接收文件使用多线程运行,因此用户可以同时发送和接收多个文件。

  4. 离线文件功能:服务器通过将用户上传的文件存放在本地的方法提供离线文件功能,用户若想接收不在线时别的用户发送的文件,只需向服务器发送下载请求即可,相对的,若用户发送文件的对象不在线,也可上传文件提供发送对象下载。

  5. 断点续传功能:服务器在接收文件的过程中,是对该文件的.temp文件进行写入,当文件接收完毕时,才将文件改为正式文件。通过.temp文件方法,可以判断文件是否传输完毕,若某次传输中断,再次传输时,服务器在本地发现了同名的.temp文件,即可知道上次文件未传输完毕,于是在断点位置继续写入,避免了已接收内容的重复接收。

效果演示

客户端连接与断开服务器

当启动客户端程序后,该用户界面自动弹出,并且已经默认填写好服务器IP、端口和默认的用户名密码,点击连接后,若连接成功,则会弹出连接成功的提示,若连接失败,则会弹出连接失败的提示。点击断开按钮即可断开与服务器的连接,退出聊天室。

该过程在服务器后台有着相应的消息显示。

在客户端抓包,可以看见本机与服务器的连接。基本聊天功能连接的是服务器的6655端口。并且可以捕获到服务器与主机的信息交互,如用户名密码的发送等等。

服务器上抓包。

用户之间的聊天

用户的聊天以IP地址、端口号、用户名与消息的形式发送给所有在线用户。若用户试图发送空的消息,则会弹出消息不能为空的提示。

该过程在服务器后台有着相应的消息显示。

在客户端抓包,可以看见本机与服务器消息交互的数据包。在服务器抓包,可以在Data字段里看见服务器转发给客户端的消息。

用户之间的文件传输

用户点击上传文件按钮时,就弹出选择文件窗口。

当选择好文件,点击打开或按回车时,开始发送文件。发送过程中会有文件传输窗口。当文件传输完成时也有相应的提示,并且显示1秒后自动关闭。

传输完成后所有在线用户可以看见服务器发送的消息。

服务器显示用户传输文件的消息。

其他所有用户会弹出文件接收提示窗口。选择是后开始接收文件。同样接收成功后也会有相应的接收成功提示,并且显示1秒后自动关闭。

接收完成后所有在线用户可以看见服务器发送的消息。

服务器显示用户接收文件的消息。

在客户端抓包,可以看见本机与服务器文件传输的数据包。文件发送连接的是服务器的8822端口,文件接收连接的是服务器的9911端口。在数据包中可以看出文件是以二进制的形式传输。

离线文件和断电续传

用户点击下载文件按钮,即弹出下载文件输入窗口。输入要下载的文件名后,开始下载离线文件,服务器以本地的文件进行发送。过程同用户接收文件。


在客户端抓包。此时为客户端接收文件,连接的是服务器的9911端口。传输的同样为二进制数据。

当用户在传输过程中点击取消按钮,则取消发送文件,会有取消发送的提示窗口。在服务器存放文件的目录下会有该文件的.temp文件。

当下次继续发送该文件时,直接从上次断点位置开始,直观显示为进度条为上次中断的位置。

通过抓包可以看出,取消发送后,客户端断开了与服务器8822断开的连接,当继续上传文件时,再次建立连接。通过断点信息交互后继续从断点位置开始传输文件。

错误处理

用户输入的用户名密码不正确,用户重复连接,用户重复断开,用户发送空消息等错误操作都会弹出相应的提示窗口,并且不会断开与服务器的连接。

代码

Client.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package mysocket2;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.*;
import java.net.*;
import java.util.*;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

public class Client {
//private String SERVER_IP="127.0.0.1";
private String SERVER_IP="121.36.14.65";
private static int SERVER_PORT=6655;
private static int SERVER_FILE_SEND_PORT=8822;
private static int SERVER_FILE_RECEIVE_PORT=9911;

private JPanel sendPanel;
private JButton btn_send_file;
private JButton btn_download_file;
private JFrame frame;
private JTextArea textArea;
private JTextField textField;
private JTextField txt_port;
private JTextField txt_hostIp;
private JTextField txt_username;
private JTextField txt_userpwd;
private JButton btn_start;
private JButton btn_stop;
private JButton btn_send;
private JPanel northPanel;
private JPanel southPanel;
private JScrollPane rightScroll;
private JScrollPane leftScroll;
private JSplitPane centerSplit;

private boolean isConnected = false;
private Socket socket;
private PrintWriter writer;
private BufferedReader reader;
private MessageThread messageThread;// 负责接收消息的线程

public static void main(String[] args){
new Client();
}

public Client(){
// GUI
textArea = new JTextArea();
textArea.setEditable(false);
textArea.setForeground(Color.blue);
textField = new JTextField();
txt_port = new JTextField("6655");
//txt_hostIp = new JTextField("127.0.0.1");
txt_hostIp = new JTextField("121.36.14.65");
txt_username = new JTextField("asd");
txt_userpwd = new JTextField("123");
btn_start = new JButton("连接");
btn_stop = new JButton("断开");
btn_send = new JButton("发送");
btn_send_file = new JButton("上传文件");
btn_download_file = new JButton("下载文件");
northPanel = new JPanel();
northPanel.setLayout(new GridLayout(1, 10));
northPanel.add(new JLabel("服务器IP"));
northPanel.add(txt_hostIp);
northPanel.add(new JLabel("端口"));
northPanel.add(txt_port);
northPanel.add(new JLabel("用户名"));
northPanel.add(txt_username);
northPanel.add(new JLabel("密码"));//
northPanel.add(txt_userpwd);
northPanel.add(btn_start);
northPanel.add(btn_stop);
northPanel.setBorder(new TitledBorder("连接信息"));
rightScroll = new JScrollPane(textArea);
rightScroll.setBorder(new TitledBorder("消息显示区"));
southPanel = new JPanel(new BorderLayout());
sendPanel = new JPanel(new BorderLayout());
southPanel.setBorder(new TitledBorder("写消息"));
southPanel.add(textField, "Center");
sendPanel.add(btn_send, BorderLayout.NORTH);
sendPanel.add(btn_send_file, BorderLayout.CENTER);
sendPanel.add(btn_download_file, BorderLayout.SOUTH);
southPanel.add(sendPanel, "East");
centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroll, rightScroll);
centerSplit.setDividerLocation(150);
frame = new JFrame("客户机");
frame.setLayout(new BorderLayout());
frame.add(northPanel, "North");
frame.add(centerSplit, "Center");
frame.add(southPanel, "South");
frame.setSize(800, 600);
int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;
int screen_height = Toolkit.getDefaultToolkit().getScreenSize().height;
frame.setLocation((screen_width - frame.getWidth()) / 2, (screen_height - frame.getHeight()) / 2);
frame.setVisible(true);

// 写消息的文本框中按回车键时事件
textField.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
send();
}
});

// 单击发送按钮时事件
btn_send.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
send();
}
});

// 单击文件按钮时事件
btn_send_file.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
sendFile();
}
});

// 单击下载按钮时事件
btn_download_file.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
String fn = JOptionPane.showInputDialog(null,"请输入要下载的文件名:","下载文件",JOptionPane.PLAIN_MESSAGE);
textArea.append("接收文件:" + fn + "\r\n");
sendMessage(frame.getTitle() + "接收文件:" + fn);
ReceiveFileClientThread rfct_download = new ReceiveFileClientThread(SERVER_IP, SERVER_FILE_RECEIVE_PORT, fn);
rfct_download.start();
}
});

// 单击连接按钮时事件
btn_start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(isConnected){
JOptionPane.showMessageDialog(frame, "已经连接!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
int port = Integer.parseInt(txt_port.getText().trim());
String hostIp = txt_hostIp.getText().trim();
String username = txt_username.getText().trim();
String userpwd = txt_userpwd.getText().trim();
boolean flag = connectServer(port, hostIp, username, userpwd);
if(flag){
frame.setTitle(username);
JOptionPane.showMessageDialog(frame, "连接成功!");
} else {
JOptionPane.showMessageDialog(frame, "连接失败!");
}
}
});

// 单击断开按钮时事件
btn_stop.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if(!isConnected){
JOptionPane.showMessageDialog(frame, "已经断开!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
boolean flag = closeConnection();
if(flag == false){
JOptionPane.showMessageDialog(frame, "断开失败!", "错误", JOptionPane.ERROR_MESSAGE);
}
JOptionPane.showMessageDialog(frame, "断开成功!");
textArea.setText("");
}
});

// 关闭窗口事件
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if(isConnected){
closeConnection();// 关闭连接
}
System.exit(0);
}
});
}

// 发送消息
public void send() {
if (!isConnected) {
JOptionPane.showMessageDialog(frame, "还没有连接服务器,无法发送消息!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
String message = textField.getText().trim();
if (message == null || message.equals("")) {
JOptionPane.showMessageDialog(frame, "消息不能为空!", "错误", JOptionPane.ERROR_MESSAGE);
return;
}
sendMessage(frame.getTitle() + ":" + message);
textField.setText("");
}

// 发送文件
public void sendFile() {
JFileChooser sourceFileChooser = new JFileChooser(".");
sourceFileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
int status = sourceFileChooser.showOpenDialog(frame);
String pathname = sourceFileChooser.getSelectedFile().getPath();
File sourceFile = new File(pathname);
textArea.append("发送文件:" + sourceFile.getName() + "\r\n");
sendMessage(frame.getTitle() + "发送文件:" + sourceFile.getName());
SendFileClientThread sfct = new SendFileClientThread(SERVER_IP, SERVER_FILE_SEND_PORT, pathname);
sfct.start();
}

// 连接服务器
public boolean connectServer(int port, String hostIp, String username, String userpwd) {
// 连接服务器
try {
socket = new Socket(hostIp, port);// 根据端口号和服务器ip建立连接
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream());
///////////////
ObjectOutputStream oos= new ObjectOutputStream(socket.getOutputStream());
User u=new User(username, userpwd);
oos.writeObject(u);
/////////////////
String login_message = reader.readLine();
textArea.append(login_message + "\r\n");
if(login_message.contains("成功")){
// 开启接收消息的线程
messageThread = new MessageThread(reader, textArea);
messageThread.start();
isConnected = true;// 已经连接上了
return true;
} else {
isConnected = false;
return false;
}
} catch (Exception e) {
textArea.append("与端口号为:" + port + " IP地址为:" + hostIp + " 的服务器连接失败!" + "\r\n");
isConnected = false;// 未连接上
e.printStackTrace();
return false;
}
}

// 接收服务器消息
class MessageThread extends Thread{
private BufferedReader reader;
private JTextArea textArea;
public MessageThread(BufferedReader reader, JTextArea textArea){
this.reader = reader;
this.textArea = textArea;
}
public void run() {
String message = "";
while(true){
try {
message = reader.readLine();
if(message!=null) {
textArea.append(message + "\r\n");
if (message.contains("发送文件:")) {
if (!message.contains(frame.getTitle())) {
String[] m = message.split("发送文件:");
String fn = m[1];
int option = JOptionPane.showConfirmDialog(null, "是否接收文件" + fn + "?", "文件接收提示", JOptionPane.YES_NO_OPTION);
if (option == JOptionPane.YES_OPTION) {
textArea.append("接收文件:" + fn + "\r\n");
sendMessage(frame.getTitle() + "接收文件:" + fn);
ReceiveFileClientThread rfct = new ReceiveFileClientThread(SERVER_IP, SERVER_FILE_RECEIVE_PORT, fn);
rfct.start();
}
}
}
}
}catch (IOException e) {
e.printStackTrace();
}
}
}
}

// 向服务器发送消息
public void sendMessage(String message) {
writer.println(message);
writer.flush();
}

// 断开连接
public synchronized boolean closeConnection() {
try {
sendMessage("CLOSE");// 发送断开连接命令给服务器
messageThread.stop();// 停止接受消息线程
// 释放资源
if (reader != null) {
reader.close();
}
if (writer != null) {
writer.close();
}
if (socket != null) {
socket.close();
}
isConnected = false;
return true;
} catch (IOException e1) {
e1.printStackTrace();
isConnected = true;
return false;
}
}

}

SendFileClientThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package mysocket2;

import javafx.scene.control.Labeled;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.*;
import java.net.*;
import java.awt.*;
import javax.swing.*;

public class SendFileClientThread extends Thread {
String server_ip;
int server_port;
String pn;

JFrame jframe;
Container contentPanel;
JProgressBar progressbar;
JLabel label;


public SendFileClientThread(String server_ip, int server_port, String pn) {
this.server_ip = server_ip;
this.server_port = server_port;
this.pn = pn;
}
public void run() {
FileInputStream fis;
DataInputStream dis;
DataOutputStream dos;
try {
jframe = new JFrame("文件传输");
Socket socket = new Socket(server_ip, server_port);
File file=new File(pn);
fis = new FileInputStream(file);
//BufferedInputStream bi=new BufferedInputStream(new InputStreamReader(new FileInputStream(file),"GBK"));
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());//client.getOutputStream()返回此套接字的输出流
//文件名、大小等属性
dos.writeUTF(file.getName());
dos.flush();
dos.writeLong(file.length());
dos.flush();
///////////////
RandomAccessFile rad = new RandomAccessFile(pn, "r");
long size=dis.readLong();//读取文件已发送的大小
dos.writeLong(rad.length());//完整文件大小
dos.flush();
//开始传输的位置
long offset=size;
// 文件分块
int barSize=(int) (rad.length()/1024);
int barOffset=(int)(offset/1024);

//传输界面
jframe.setSize(380,120);
contentPanel = jframe.getContentPane();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
progressbar = new JProgressBar();//进度条
label = new JLabel(file.getName()+" 发送中");
contentPanel.add(label);
progressbar.setOrientation(JProgressBar.HORIZONTAL);
progressbar.setMinimum(0);
progressbar.setMaximum(barSize);
progressbar.setValue(barOffset);
progressbar.setStringPainted(true);
progressbar.setPreferredSize(new Dimension(150, 20));
progressbar.setBorderPainted(true);
progressbar.setBackground(Color.pink);
JButton cancel=new JButton("取消");
JPanel barPanel=new JPanel();
barPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
barPanel.add(progressbar);
barPanel.add(cancel);
contentPanel.add(barPanel);
cancel.addActionListener(new CancelActionListener(label,dis,dos,rad,jframe,socket));
//jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;
int screen_height = Toolkit.getDefaultToolkit().getScreenSize().height;
jframe.setLocation((screen_width - jframe.getWidth()) / 2, (screen_height - jframe.getHeight()) / 2);
jframe.setVisible(true);

System.out.println("发送文件 " + file.getName());
System.out.println("######## 开始传输文件 ########");
byte[] buf=new byte[1024];
int length;
if (offset<rad.length()) {
rad.seek(offset);
while((length=rad.read(buf))>0){
dos.write(buf,0,length);
progressbar.setValue(++barOffset);
dos.flush();
}
}
System.out.println("######## 文件传输成功 ########");
label.setText(file.getName()+" 发送完成");
dis.close();
dos.close();
rad.close();
sleep(1000);
///////////////
}catch(IOException | InterruptedException e){
label.setText(" 取消发送,连接关闭");
//e.printStackTrace();
//System.out.println("客户端文件传输异常");
//JOptionPane.showMessageDialog(frame, "无法发送文件!", "错误", JOptionPane.ERROR_MESSAGE);
} finally {
jframe.dispose();
}
}
}

ReceiveFileClientThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
package mysocket2;

import java.awt.*;
import java.io.*;
import java.net.*;
import javax.swing.*;

public class ReceiveFileClientThread extends Thread {
String server_ip;
int server_port;
String filename;

JFrame jframe;
Container contentPanel;
JProgressBar progressbar;
JLabel label;

public ReceiveFileClientThread(String server_ip, int server_port, String filename) {
this.server_ip = server_ip;
this.server_port = server_port;
this.filename = filename;
}

public void run() {
DataInputStream dis;
DataOutputStream dos;
try {
jframe=new JFrame("接收文件");
Socket socket = new Socket(server_ip, server_port);
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());

File directory = new File("./client");
if(!directory.exists()) {
directory.mkdir();
}
// 文件名
String fn = dis.readUTF();
String fileName = directory.getAbsolutePath() + File.separatorChar + filename;
// 传输时为.temp文件
File file=new File(fileName+".temp");
RandomAccessFile rad=new RandomAccessFile(fileName+".temp", "rw");
//获得文件大小
long size=0;
if(file.exists()&& file.isFile()){
size=file.length();
}
dos.writeLong(size); //发送已接收的大小
dos.flush();
long allSize=dis.readLong(); // 完整文件大小
// 文件分块
int barSize=(int)(allSize/1024);
int barOffset=(int)(size/1024);

//传输界面
jframe.setSize(300,120);
contentPanel =jframe.getContentPane();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
progressbar = new JProgressBar();//进度条
label=new JLabel(filename+" 接收中");
contentPanel.add(label);
progressbar.setOrientation(JProgressBar.HORIZONTAL);
progressbar.setMinimum(0);
progressbar.setMaximum(barSize);
progressbar.setValue(barOffset);
progressbar.setStringPainted(true);
progressbar.setPreferredSize(new Dimension(150, 20));
progressbar.setBorderPainted(true);
progressbar.setBackground(Color.pink);
JButton cancel=new JButton("取消");
JPanel barPanel=new JPanel();
barPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
barPanel.add(progressbar);
barPanel.add(cancel);
contentPanel.add(barPanel);
cancel.addActionListener(new CancelActionListener(label,dis,dos,rad,jframe,socket));
//jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;
int screen_height = Toolkit.getDefaultToolkit().getScreenSize().height;
jframe.setLocation((screen_width - jframe.getWidth()) / 2, (screen_height - jframe.getHeight()) / 2);
jframe.setVisible(true);

System.out.println("######## 开始接收文件 ########");
rad.seek(size);
int length;
byte[] buf=new byte[1024];
while((length=dis.read(buf, 0, buf.length))!=-1){
rad.write(buf,0,length);
progressbar.setValue(++barOffset);
}
dis.close();
dos.close();
rad.close();
File f=new File(fileName);
// 覆盖已有的文件
if(f.exists()){
f.delete();
}
// 将.temp文件转为正式文件
if(barOffset >= barSize){
file.renameTo(new File(fileName));
}
//////////////
System.out.println("######## 文件接收成功 [File Name:" + fileName + "] ########");
label.setText(filename+" 接收完成");
sleep(1000);
} catch (Exception e) {
//e.printStackTrace();
label.setText(" 已取消接收,连接关闭!");
} finally {
jframe.dispose();
}
}
}

User.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package mysocket2;

import java.io.Serializable;

public class User implements Serializable{
/**
*
*/
private static final long serialVersionUID = -3217779599604368894L;
private String name;
private String password;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public User(String name, String password) {
super();
this.name = name;
this.password = password;
}
public User() {
super();
}

}

Servers.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
package mysocket2;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Vector;

public class Servers {
private static int SERVER_PORT=6655;
private static int SERVER_FILE_SEND_PORT=8822;
private static int SERVER_FILE_RECEIVE_PORT=9911;

//将接收到的socket变成一个集合
protected static List<Socket> sockets = new Vector<>();
//protected static List<Socket> file_send_sockets = new Vector<>();
//protected static List<Socket> file_receive_sockets = new Vector<>();

public static void main(String[] args) throws IOException {
//创建服务端
ServerSocket server = new ServerSocket(SERVER_PORT);
//ServerSocket file_send_server = new ServerSocket(SERVER_FILE_SEND_PORT);
//ServerSocket file_receive_server = new ServerSocket(SERVER_FILE_RECEIVE_PORT);
boolean flag = true;
//接受客户端请求
while (flag){
try {
//阻塞等待客户端的连接
Socket accept = server.accept();
synchronized (sockets){
sockets.add(accept);
}
//多个服务器线程进行对客户端的响应
Thread thread = new Thread(new ServerThead(accept));
thread.start();
//捕获异常
}catch (Exception e){
flag = false;
e.printStackTrace();
}
}
//关闭服务器
//file_send_server.close();
//file_receive_server.close();
server.close();
}
}

ServerThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package mysocket2;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

/**
* 服务器线程,主要来处理多个客户端的请求
*/
class ServerThead extends mysocket2.Servers implements Runnable{

Socket socket;
String socketName;

private static int SERVER_PORT=5200;
private static int SERVER_FILE_SEND_PORT=8822;
private static int SERVER_FILE_RECEIVE_PORT=9911;

/////////////////////
/////////////////////

public ServerThead(Socket socket){
this.socket = socket;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"请求登录...");
//3.获取输入流(对象流)
ObjectInputStream ois;
//5.获取输出流(数据流),反馈给客户端登录信息
DataOutputStream dos;
try {
ois = new ObjectInputStream(socket.getInputStream());
User user = (User) ois.readObject();
System.out.println(socket.getInetAddress().getHostAddress()+"请求登录\t用户名:"+user.getName()+"\t密码:"+user.getPassword());
String str;
//对用户名和密码进行验证
BufferedReader aps = new BufferedReader(new FileReader("src/mysocket2/account_password.txt"));
String apline = null;
boolean b = false;
String[] ap;
while((apline=aps.readLine())!=null)
{
ap=apline.split(" "); // 空格分隔用户名和密码
if(ap[0].equals(user.getName())&&ap[1].equals(user.getPassword()))
{
b = true;
break;
}
}
if(b) {
str=user.getName()+"登录成功!";
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println(str);
pw.flush();
/////////////////
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
//设置该客户端的端点地址
socketName = socket.getRemoteSocketAddress().toString();
System.out.println("Client@"+socketName+"已加入聊天");
print("Client@"+socketName+"已加入聊天");
boolean flag = true;
while (flag)
{
//阻塞,等待该客户端的输出流
String line = reader.readLine();
System.out.println("read line "+ line);
//若客户端退出,则退出连接。
if (line == null){
flag = false;
continue;
}
if(line.contains("发送文件:")){
String msg = "Client@" + socketName + ":" + line;
System.out.println(msg);
print(msg);
//向在线客户端输出信息
ServerSocket file_send_server = new ServerSocket(SERVER_FILE_SEND_PORT);
Socket file_send_accept = file_send_server.accept();
ReceiveFileServerThread rfst = new ReceiveFileServerThread(file_send_accept);
rfst.start();
file_send_server.close();

}else if(line.contains("接收文件:")){
String msg = "Client@" + socketName + ":" + line;
System.out.println(msg);
print(msg);
//向在线客户端输出信息
String[] l=line.split("接收文件:");
String fn=l[1];
ServerSocket file_receive_server = new ServerSocket(SERVER_FILE_RECEIVE_PORT);
Socket file_receive_accept = file_receive_server.accept();
SendFileServerThread sfst = new SendFileServerThread(file_receive_accept,fn);
sfst.start();
file_receive_server.close();

}
else {
String msg = "Client@" + socketName + ":" + line;
System.out.println(msg);
//向在线客户端输出信息
print(msg);
}
}
closeConnect();
} else {
str="登录失败!账号或密码有误!";
PrintWriter pw = new PrintWriter(socket.getOutputStream());
pw.println(str);
pw.flush();
closeConnect();
}
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
try {
closeConnect();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
/**
* 向所有在线客户端socket转发消息
* @param msg
* @throws IOException
*/
private void print(String msg) throws IOException {
PrintWriter out = null;
synchronized (sockets){
for (Socket sc : sockets){
out = new PrintWriter(sc.getOutputStream());
out.println(msg);
out.flush();
}
}
}
/**
* 关闭该socket的连接
* @throws IOException
*/
public void closeConnect() throws IOException {
System.out.println("Client@"+socketName+"已退出聊天");
print("Client@"+socketName+"已退出聊天");
//移除没连接上的客户端
synchronized (sockets){
sockets.remove(socket);
}
socket.close();
}
}

SendFileServerThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package mysocket2;

import java.io.*;
import java.net.*;

public class SendFileServerThread extends Thread {
Socket socket;
String fn;

public SendFileServerThread(Socket socket, String fn) {
this.socket = socket;
this.fn = fn;
}

public void run() {
FileInputStream fis;
DataInputStream dis;
DataOutputStream dos;
try {
File file=new File("./"+fn);
fis = new FileInputStream(file);
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());
//文件名、大小等属性
dos.writeUTF(file.getName());
dos.flush();
dos.writeLong(file.length());
dos.flush();
///////////////
RandomAccessFile rad = new RandomAccessFile(fn, "r");
long size=dis.readLong();//读取文件已发送的大小
dos.writeLong(rad.length());//完整文件大小
dos.flush();
//开始传输的位置
long offset=size;
// 文件分块
int barSize=(int) (rad.length()/1024);
int barOffset=(int)(offset/1024);

System.out.println("发送文件 " + file.getName());
System.out.println("######## 开始传输文件 ########");
byte[] buf=new byte[1024];
int length;
if (offset<rad.length()) {
rad.seek(offset);
while((length=rad.read(buf))>0){
dos.write(buf,0,length);
dos.flush();
}
}
System.out.println("######## 文件传输成功 ########");
dis.close();
dos.close();
rad.close();
///////////////
}catch(IOException e){
e.printStackTrace();
System.out.println("文件传输异常");
//JOptionPane.showMessageDialog(frame, "无法发送文件!", "错误", JOptionPane.ERROR_MESSAGE);
}
}
}

ReceiveFileServerThread.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package mysocket2;

import java.io.*;
import java.net.*;

public class ReceiveFileServerThread extends Thread {
Socket socket;

private static int SERVER_FILE_RECEIVE_PORT=9911;

public ReceiveFileServerThread(Socket socket) {
this.socket = socket;
}

public void run() {
DataInputStream dis;
DataOutputStream dos;
try {
dis = new DataInputStream(socket.getInputStream());
dos = new DataOutputStream(socket.getOutputStream());

// 文件名和长度
String fileName = dis.readUTF();
//////////////
File file=new File(fileName+".temp");
RandomAccessFile rad=new RandomAccessFile(fileName+".temp", "rw");
//获得文件大小
long size=0;
if(file.exists()&& file.isFile()){
size=file.length();
}
dos.writeLong(size);//发送已接收的大小
dos.flush();
long allSize=dis.readLong();//接收完整文件大小
// 文件分块
int barSize=(int)(allSize/1024); // 块数
int barOffset=(int)(size/1024); //
System.out.println("######## 开始接收文件 ########");
rad.seek(size);
int length;
byte[] buf=new byte[1024];
while((length=dis.read(buf, 0, buf.length))!=-1){
rad.write(buf,0,length);
barOffset += 1;
}
dis.close();
dos.close();
rad.close();
File f=new File(fileName);
// 覆盖已有的文件
if(f.exists()){
f.delete();
}
// 将.temp文件转为正式文件
if(barOffset >= barSize){
file.renameTo(new File(fileName));
}
//////////////
System.out.println("######## 文件接收成功 [File Name:" + fileName + "] ########");
} catch (Exception e) {
e.printStackTrace();
}
}
}

CancelActionListener.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package mysocket2;

import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.Socket;

public class CancelActionListener implements ActionListener {
JLabel label;
DataInputStream dis;
DataOutputStream dos;
RandomAccessFile rad;
JFrame frame;
Socket socket;
public CancelActionListener(JLabel label,DataInputStream dis,DataOutputStream dos,RandomAccessFile rad,JFrame frame,Socket socket){
this.label = label;
this.dis = dis;
this.dos = dos;
this.rad = rad;
this.frame = frame;
this.socket = socket;
}

public void actionPerformed(ActionEvent e3){
try {
label.setText(" 取消发送,连接关闭");
JOptionPane.showMessageDialog(frame, "取消发送给,连接关闭!", "提示:", JOptionPane.INFORMATION_MESSAGE);
dis.close();
dos.close();
rad.close();
frame.dispose();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Socket聊天程序(Java)
https://wangaaayu.github.io/blog/posts/545a2897/
作者
WangAaayu
发布于
2023年6月4日
更新于
2023年6月4日
许可协议