java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

时间:2021-09-19 07:19:39

最近在复习java的io流及网络编程。但复习写那些样板程序总是乏味的。便准备写个项目来巩固。想来想去还是聊天项目比较好玩。如果日后完成的比较好自己也可以用(哈哈哈)。并且自己后面也要继续巩固java多线程和集合(这两部分学的很差)。

我给这个项目命名为很大众的名字——" chat "

这算是"chat 1.0" 吧目前只实现了群聊+文件传输功能,没有用户注册模块。

因为自己在阿里云上的服务器之前到期没有续费,所以就无法将服务端实现两个局域网之间的信息传输。只能在自己电脑上开多个客户端自己和自己聊天。

不是很甘心,上网找到了一个比较好的方法,内网穿透。某神,某花,都是免费可以映射一两个公网ip的。这样就可以实现不同局域网之间的通信了

效果演示:

打开图形界面不会立刻执行,会有启动服务器,连接服务器按钮。图中2昵称仅支持本地自己的界面显示来分辨是自己发的消息(哈哈哈)

java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

点击发送文件按钮,会显示文件对话框,来选择要发送的本地文件。

java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

发送完毕后其它客户端会收到文件消息,并询问是否接收文件。

java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

接收时也会弹出文件对话框,并选择存储的路径和文件名称

java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing

字不多打,上代码。(主要使用tcp通信)

服务端:

Server类实现一直监听客户端的套接字连接,并开启服务端读写线程

 import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List; import view.ServerStart_View; public class Server implements Runnable{ public static ArrayList<Socket> socketList = new ArrayList<>(); //存储连接的Socket对象
private static int num = 0;
static ServerStart_View gui; //定义为静态的
// public static final Server server = new Server(); public void get(ServerStart_View gui) {
this.gui=gui;
} public void run() {
ServerSocket socket=null;
Socket socket2 = null;
try {
socket = new ServerSocket(12345);
} catch (IOException e) {
e.printStackTrace();
} while(true) {
// System.out.println("didi");
//无限监听客户端连接,并为此开启读写线程。
try {
Thread.sleep(1);
socket2=socket.accept();
socketList.add(socket2);
++num;
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
gui.setText("第"+num+"个客户端已经连接");
try {
//开启消息读入转发线程。
Server_IO io = new Server_IO(socket2,gui);
Thread thread = new Thread(io);
thread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Server_IO类是实现服务端的读写功能,读取客户端传来的字节并转发给其它客户端

 import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket; import view.ServerStart_View; public class Server_IO implements Runnable {
private DataInputStream stream = null;
private Socket socket = null;
private static ServerStart_View start_View; public Server_IO(Socket socket,ServerStart_View start_View) throws IOException {
this.start_View = start_View;
this.socket = socket;
stream = new DataInputStream(socket.getInputStream());
} @Override
public void run() {
String rece = null;
// boolean judg = true;
while(true) {
try {
Thread.sleep(1);
if (stream.available()!=0) {
try {
rece = stream.readUTF();
start_View.setText(rece);
} catch (IOException e) {
e.printStackTrace();
// judg=false;
}
for (Socket Ssocket:Server.socketList)
new DataOutputStream(Ssocket.getOutputStream()).writeUTF(rece); // System.out.println(rece);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}

服务端文件传输:

服务端消息和文件传输是分开的,两个不同的端口。但原理一样,都是服务端不断监听客户端发来的连接请求。然后三次握手建立连接通信,开启输入流,输出流。

File类和Server类功能大致相同,只不过是用于接收转发文件信息

 import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List; public class File implements Runnable{ public static ArrayList<Socket> socketList_IO = new ArrayList<>();
ServerSocket ssocket;
Socket socket; public void run() {
//文件传输开启另一个端口监听。
try {
ssocket = new ServerSocket(12306);
} catch (IOException e) {
e.printStackTrace();
}
while(true) {
try {
Thread.sleep(1);
socket = ssocket.accept();
socketList_IO.add(socket);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
File_IO file_IO;
try {
file_IO = new File_IO(socket);
Thread thread = new Thread(file_IO);
thread.start();
} catch (IOException e) {
e.printStackTrace();
} }
} }

File_IO类 接收客户端的文件,并转发给其他客户端

 import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket; public class File_IO implements Runnable {
DataInputStream input;
DataOutputStream output;
Socket socket; public File_IO(Socket socket) throws IOException{
this.socket = socket;
System.out.println(socket.getInetAddress());
} @Override
public void run() {
byte[] read = new byte[1024];
String name;
try {
input = new DataInputStream(socket.getInputStream());
} catch (IOException e1) {
e1.printStackTrace();
}
while(true) {
try {
Thread.sleep(1);
if(input.available()!=0) {
name=input.readUTF();
System.out.println(name);
int len=0;
while((len=input.read(read,0,read.length))>0) {
for(Socket soc:File.socketList_IO) {
if(soc != socket)
{
output = new DataOutputStream(soc.getOutputStream());
output.writeUTF(name);
output.write(read,0,len);
output.flush();
// System.out.println("开始向客户机转发");
}
}
// System.out.println("执行");
// System.out.println(len);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

Swing图形界面。服务端如果是放在Linux系统的服务器中是不需要图形界面的。但我这主要在自己电脑上运行所以做个简单的可视化界面比较好。

客户端还是服务端的图形界面我都是借助于eclipse的windowbuilder插件进行绘制的,所以图形界面类我贴上主要的方法及按钮的事件监听代码部分。

服务端的界面

public class ServerStart_View extends JFrame {

    private JPanel contentPane;
private JScrollPane scrollPane;
private JTextArea textArea;
private static Server server = new Server(); //定义为静态的
private static File file = new File();
private final Action action = new SwingAction();
private JButton btnNewButton; //打印收到的消息在服务端的文本域中
public void setText(String string) {
textArea.append("\n"+">>: "+string);
} //
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
ServerStart_View frame = new ServerStart_View();
frame.setVisible(true);
server.get(frame); //传递对象变量
}
});
} ............
............
............ //启动服务按钮的事件处理。
private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "启动服务");
putValue(SHORT_DESCRIPTION, "Some short description");
}
public void actionPerformed(ActionEvent e) {
//开启服务端监听客户端连接线程(消息服务)
Thread thread = new Thread(server);
thread.start();
//同上 (文件服务)
Thread thread2 = new Thread(file);
thread2.start();
}
} }

服务端代码到此结束。主要是消息和文件分别各监听一个端口的等待连接。

客户端:

本来是定义Client类的但因为某些前期原因导致客户端的类名不是很规范。

chat_Client类

 package client;

 import view.ClientStart_View;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException; public class chat_Client{
public chat_Client() {
} private Socket socket = null;
// private int Port = 0;
private static ClientStart_View jframe; //定义为静态的 public void text(ClientStart_View jframe) {
this.jframe = jframe;
} //“连接服务器” button 按钮点击事件,调用此方法
public void con(int port) throws UnknownHostException, IOException {
try {
socket = new Socket("127.0.0.1", port);
// System.out.println("客户端------------------------");
// System.out.println("连接到的地址"+socket.getInetAddress()+":"+socket.getLocalPort()); //打印在控制台
jframe.TextA("已连接");
} catch (IOException e1) {
e1.printStackTrace();
} Runnable runnable = new chat_Client_I(socket,jframe); //启动消息读写线程,并将传递socket,jframe对象变量
Thread thread = new Thread(runnable);
thread.start();
} //send()方法,发送消息给服务器。 “发送”button 按钮点击事件,调用此方法
public void send(String send) throws IOException {
DataOutputStream stream = new DataOutputStream(socket.getOutputStream());
stream.writeUTF(send);
} }

chat_Client_I类  客户端的消息字符读取类。读取服务端发来的消息字符并打印在界面的文本域中

 import view.ClientStart_View;

 import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket; public class chat_Client_I implements Runnable{
private String rece = null;
private static DataInputStream stream;
private ClientStart_View jframe; public chat_Client_I(Socket socket,ClientStart_View jframe) {
this.jframe = jframe;
try {
stream = new DataInputStream(socket.getInputStream());
} catch (IOException e2) {
e2.printStackTrace();
}
} @Override
public void run() {
while(true) {
try {
//if条件判断是否有可获取的字节 ,如果不是判断而一直读取的话,在结束时会有读取异常抛出。运行时是没有的。
if(stream.available()!=0) {
rece = stream.readUTF();
jframe.TextA(rece); //调用 ClientStart_View.java类中方法将读入的字符串打印在文本域中
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

客户端的文件模块写的有点乱。客户端文件模块要实现发送和接收保存

发送要从计算机本地通过输入流读取文件,并通过输出流发送给服务端。

接收要从服务端通过输入流进行读入,然后通过输出流写入本地保存。

File_start类,连接服务端,并开启输入流读取服务端发来的文件

 import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException; public class File_start implements Runnable{
Socket socket=null; @Override
public void run() {
try {
socket = new Socket("127.0.0.1", 12306);
// System.out.println("已连接");
// System.out.println(socket.getInetAddress()+":"+socket.getLocalPort());
} catch (UnknownHostException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} File_I file_i;
try {
File_O file_O = new File_O(socket);
//开启从服务器的输入流线程
file_i = new File_I(socket);
Thread thread = new Thread(file_i);
thread.start();
} catch (IOException e) {
e.printStackTrace();
} }
}

File_O类,读取本地文件并发送到服务端

 import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket; public class File_O{
public static File_O file_O = new File_O();
// private Socket socket=null;
private byte[] readAllBytes = new byte[1024];
private static DataOutputStream outputstream;
private DataInputStream inputstream; public File_O() {}
public File_O(Socket socket) throws IOException {
// this.socket = socket;
outputstream = new DataOutputStream(socket.getOutputStream());
} //读取本地文件,并发送到服务端。面板中发送文件按钮在事件处理时会调用此函数
public void read(String url,String name) throws IOException {
outputstream.writeUTF(name);
inputstream = new DataInputStream(new FileInputStream(url));
int len=0;
while((len=inputstream.read(readAllBytes))>0) {
outputstream.write(readAllBytes, 0, len);
outputstream.flush();
}
//outputstream.close();
// System.out.println("发送完成");
} //此方法暂时没有用到
public byte[] getread() {
return readAllBytes;
}
}

File_I类 从服务端的输出流读取文件,并保存到本地。

 import view.ClientStart_View;

 import java.awt.HeadlessException;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Socket; import javax.swing.JOptionPane; public class File_I implements Runnable{ // public static File_I file_I = new File_I();
private static ClientStart_View jframe;
private DataOutputStream output;
private static DataInputStream input;
private Socket socket; public File_I() {}
public File_I(Socket socket) throws IOException {
this.socket = socket;
input = new DataInputStream(socket.getInputStream());
} public void getfile(ClientStart_View jframe) {
this.jframe = jframe;
} @Override
public void run() {
String url = null;
String name = null;
byte[] rece = new byte[1024];
while(true) {
try {
//if判断服务端输出流中是否有可读取字节
if (input.available()!=0) {
name = input.readUTF();
// System.out.println(name); //是否接受此文件,接收才会进行输入流读入保存
JOptionPane jOptionPane = new JOptionPane();
int result=jOptionPane.showConfirmDialog(null, "是否接收该文件", "嘀嘀:一份文件消息", jOptionPane.YES_NO_OPTION);
// System.out.println(result);
if (result == jOptionPane.YES_OPTION) {
//默认存储路径及从服务端获取的文件名称
File file = new File("D:\\", name);
// file_I.jframe.getJF().setCurrentDirectory(new File("D:\\"));
jframe.getJF().setSelectedFile(file);
jframe.getJF().showSaveDialog(null);
url = jframe.getJF().getSelectedFile().getPath();
// name =file_I.jframe.getJF().getSelectedFile().getName(); int len;
try {
while((len=input.read(rece,0,rece.length))!=-1) {
// System.out.println("zhixing-1");
output = new DataOutputStream(new FileOutputStream(url));
output.write(rece, 0, len);
output.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
} catch (HeadlessException | IOException e) {
e.printStackTrace();
}
}
}
}

客户端的图形界面也是使用插件,所以只贴上主要的代码段

public class ClientStart_View extends JFrame {

    public JPanel contentPane;
private JTextField textField;
JTextArea textArea;
private static client.chat_Client chat_Client=new chat_Client();
private static File_I file_I = new File_I();
private JFileChooser fileChooser =new JFileChooser();
private final Action action = new SwingAction();
private final Action action_2 = new SwingAction_2();
private JTextField textField_1;
private final Action action_3 = new SwingAction_3();
//JFileChooser 用于创建文件对话框
public JFileChooser getJF() {
return fileChooser;
} //此方法将接收到服务端的消息打印文本域中
public void TextA(String rece) {
textArea.append("\n"+textField_1.getText()+" > "+rece);
System.out.println(rece);
} public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ClientStart_View frame = new ClientStart_View();
frame.setVisible(true);
chat_Client.text(frame);
// chat_Client.get().text(frame);
file_I.getfile(frame);
} catch (Exception e) {
e.printStackTrace();
}
}
});
} .........
.........
.........
......... //事件处理,连接服务端,并开启客户端的文件读取线程
private class SwingAction extends AbstractAction {
public SwingAction() {
putValue(NAME, "连接服务器");
putValue(SHORT_DESCRIPTION, "Some short description");
}
public void actionPerformed(ActionEvent e) {
try {
//chat_Client.get().con(12345); //QQ
chat_Client.con(12345);
} catch (IOException e1) {
e1.printStackTrace();
}
File_start file_start = new File_start();
Thread thread = new Thread(file_start);
thread.start(); }
} //发送按钮的事件处理,从输入文本中读取输入的字符,发送至服务端
private class SwingAction_2 extends AbstractAction {
public SwingAction_2() {
putValue(NAME, "发送");
putValue(SHORT_DESCRIPTION, "Some short description");
}
public void actionPerformed(ActionEvent e) {
String text = null;
text = textField.getText();
if(text.length()!=0) {
try {
// chat_Client.get().send(text);
chat_Client.send(text);
} catch (IOException e1) {
e1.printStackTrace();
}}
textField.setText(null); //输入文本框置空
}
} //发送文件按钮事件处理,从本地读取文件信息,发送至服务端
private class SwingAction_3 extends AbstractAction {
public SwingAction_3() {
putValue(NAME, "发送文件/图片");
putValue(SHORT_DESCRIPTION, "Some short description");
}
public void actionPerformed(ActionEvent e) {
String name=null; String paths=null;
// fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new java.io.File("E:\\"));
int result=fileChooser.showOpenDialog(null);
if (result == fileChooser.APPROVE_OPTION) {
paths = fileChooser.getSelectedFile().getPath(); //返回所选文件的路径
name= fileChooser.getSelectedFile().getName(); //返回所选文件的名称
// System.out.println(name); System.out.println(paths);
try {
File_O.file_O.read(paths,name);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}

至此代码结束

这段代码只是实现一部分简单功能,还参杂着不少问题,”线程开的太多,并且是死循环,在运行是cpu使用总是达到100%“,”文件的发送只有文本txt能成功打开,其他发送后格式乱码无法打开“。,,,,

欢迎大家交流和指教。

ps : 项目更新,增加登录,注册功能,发现并优化了一些已知bug

进入主页查看阅读最新文章

链接:https://www.cnblogs.com/yuqingsong-cheng/p/12822628.html