java基于socket的网络通信,实现一个服务端多个客户端的群聊,传输文件功能,界面使用Swing
最近在复习java的io流及网络编程。但复习写那些样板程序总是乏味的。便准备写个项目来巩固。想来想去还是聊天项目比较好玩。如果日后完成的比较好自己也可以用(哈哈哈)。并且自己后面也要继续巩固java多线程和集合(这两部分学的很差)。
我给这个项目命名为很大众的名字——" chat "
这算是"chat 1.0" 吧目前只实现了群聊+文件传输功能,没有用户注册模块。
因为自己在阿里云上的服务器之前到期没有续费,所以就无法将服务端实现两个局域网之间的信息传输。只能在自己电脑上开多个客户端自己和自己聊天。
不是很甘心,上网找到了一个比较好的方法,内网穿透。某神,某花,都是免费可以映射一两个公网ip的。这样就可以实现不同局域网之间的通信了
效果演示:
打开图形界面不会立刻执行,会有启动服务器,连接服务器按钮。图中2昵称仅支持本地自己的界面显示来分辨是自己发的消息(哈哈哈)
点击发送文件按钮,会显示文件对话框,来选择要发送的本地文件。
发送完毕后其它客户端会收到文件消息,并询问是否接收文件。
接收时也会弹出文件对话框,并选择存储的路径和文件名称
字不多打,上代码。(主要使用tcp通信)
服务端:
Server类实现一直监听客户端的套接字连接,并开启服务端读写线程
1 import java.io.IOException; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 import view.ServerStart_View; 8 9 public class Server implements Runnable{ 10 11 public static ArrayList<Socket> socketList = new ArrayList<>(); //存储连接的Socket对象 12 private static int num = 0; 13 static ServerStart_View gui; //定义为静态的 14 // public static final Server server = new Server(); 15 16 public void get(ServerStart_View gui) { 17 this.gui=gui; 18 } 19 20 public void run() { 21 ServerSocket socket=null; 22 Socket socket2 = null; 23 try { 24 socket = new ServerSocket(12345); 25 } catch (IOException e) { 26 e.printStackTrace(); 27 } 28 29 while(true) { 30 // System.out.println("didi"); 31 //无限监听客户端连接,并为此开启读写线程。 32 try { 33 Thread.sleep(1); 34 socket2=socket.accept(); 35 socketList.add(socket2); 36 ++num; 37 } catch (IOException e) { 38 e.printStackTrace(); 39 } catch (InterruptedException e1) { 40 e1.printStackTrace(); 41 } 42 gui.setText("第"+num+"个客户端已经连接"); 43 try { 44 //开启消息读入转发线程。 45 Server_IO io = new Server_IO(socket2,gui); 46 Thread thread = new Thread(io); 47 thread.start(); 48 } catch (IOException e) { 49 e.printStackTrace(); 50 } 51 } 52 } 53 }
Server_IO类是实现服务端的读写功能,读取客户端传来的字节并转发给其它客户端
1 import java.io.DataInputStream; 2 import java.io.DataOutputStream; 3 import java.io.IOException; 4 import java.net.Socket; 5 6 import view.ServerStart_View; 7 8 public class Server_IO implements Runnable { 9 private DataInputStream stream = null; 10 private Socket socket = null; 11 private static ServerStart_View start_View; 12 13 public Server_IO(Socket socket,ServerStart_View start_View) throws IOException { 14 this.start_View = start_View; 15 this.socket = socket; 16 stream = new DataInputStream(socket.getInputStream()); 17 } 18 19 @Override 20 public void run() { 21 String rece = null; 22 // boolean judg = true; 23 while(true) { 24 try { 25 Thread.sleep(1); 26 if (stream.available()!=0) { 27 try { 28 rece = stream.readUTF(); 29 start_View.setText(rece); 30 } catch (IOException e) { 31 e.printStackTrace(); 32 // judg=false; 33 } 34 for (Socket Ssocket:Server.socketList) 35 new DataOutputStream(Ssocket.getOutputStream()).writeUTF(rece); 36 37 // System.out.println(rece); 38 } 39 } catch (IOException e) { 40 e.printStackTrace(); 41 } catch (InterruptedException e1) { 42 // TODO Auto-generated catch block 43 e1.printStackTrace(); 44 } 45 } 46 } 47 }
服务端文件传输:
服务端消息和文件传输是分开的,两个不同的端口。但原理一样,都是服务端不断监听客户端发来的连接请求。然后三次握手建立连接通信,开启输入流,输出流。
File类和Server类功能大致相同,只不过是用于接收转发文件信息
1 import java.io.IOException; 2 import java.net.ServerSocket; 3 import java.net.Socket; 4 import java.util.ArrayList; 5 import java.util.List; 6 7 public class File implements Runnable{ 8 9 public static ArrayList<Socket> socketList_IO = new ArrayList<>(); 10 ServerSocket ssocket; 11 Socket socket; 12 13 public void run() { 14 //文件传输开启另一个端口监听。 15 try { 16 ssocket = new ServerSocket(12306); 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } 20 while(true) { 21 try { 22 Thread.sleep(1); 23 socket = ssocket.accept(); 24 socketList_IO.add(socket); 25 } catch (IOException e) { 26 e.printStackTrace(); 27 } catch (InterruptedException e1) { 28 e1.printStackTrace(); 29 } 30 File_IO file_IO; 31 try { 32 file_IO = new File_IO(socket); 33 Thread thread = new Thread(file_IO); 34 thread.start(); 35 } catch (IOException e) { 36 e.printStackTrace(); 37 } 38 39 } 40 } 41 42 }
File_IO类 接收客户端的文件,并转发给其他客户端
1 import java.io.DataInputStream; 2 import java.io.DataOutputStream; 3 import java.io.IOException; 4 import java.net.Socket; 5 6 public class File_IO implements Runnable { 7 DataInputStream input; 8 DataOutputStream output; 9 Socket socket; 10 11 public File_IO(Socket socket) throws IOException{ 12 this.socket = socket; 13 System.out.println(socket.getInetAddress()); 14 } 15 16 @Override 17 public void run() { 18 byte[] read = new byte[1024]; 19 String name; 20 try { 21 input = new DataInputStream(socket.getInputStream()); 22 } catch (IOException e1) { 23 e1.printStackTrace(); 24 } 25 while(true) { 26 try { 27 Thread.sleep(1); 28 if(input.available()!=0) { 29 name=input.readUTF(); 30 System.out.println(name); 31 int len=0; 32 while((len=input.read(read,0,read.length))>0) { 33 for(Socket soc:File.socketList_IO) { 34 if(soc != socket) 35 { 36 output = new DataOutputStream(soc.getOutputStream()); 37 output.writeUTF(name); 38 output.write(read,0,len); 39 output.flush(); 40 // System.out.println("开始向客户机转发"); 41 } 42 } 43 // System.out.println("执行"); 44 // System.out.println(len); 45 } 46 } 47 } catch (IOException e) { 48 e.printStackTrace(); 49 } catch (InterruptedException e) { 50 e.printStackTrace(); 51 } 52 } 53 } 54 }
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类
1 package client; 2 3 import view.ClientStart_View; 4 import java.io.DataOutputStream; 5 import java.io.IOException; 6 import java.net.Socket; 7 import java.net.UnknownHostException; 8 9 public class chat_Client{ 10 public chat_Client() { 11 } 12 13 private Socket socket = null; 14 // private int Port = 0; 15 private static ClientStart_View jframe; //定义为静态的 16 17 public void text(ClientStart_View jframe) { 18 this.jframe = jframe; 19 } 20 21 //“连接服务器” button 按钮点击事件,调用此方法 22 public void con(int port) throws UnknownHostException, IOException { 23 try { 24 socket = new Socket("127.0.0.1", port); 25 // System.out.println("客户端------------------------"); 26 // System.out.println("连接到的地址"+socket.getInetAddress()+":"+socket.getLocalPort()); //打印在控制台 27 jframe.TextA("已连接"); 28 } catch (IOException e1) { 29 e1.printStackTrace(); 30 } 31 32 Runnable runnable = new chat_Client_I(socket,jframe); //启动消息读写线程,并将传递socket,jframe对象变量 33 Thread thread = new Thread(runnable); 34 thread.start(); 35 } 36 37 //send()方法,发送消息给服务器。 “发送”button 按钮点击事件,调用此方法 38 public void send(String send) throws IOException { 39 DataOutputStream stream = new DataOutputStream(socket.getOutputStream()); 40 stream.writeUTF(send); 41 } 42 43 }
chat_Client_I类 客户端的消息字符读取类。读取服务端发来的消息字符并打印在界面的文本域中
1 import view.ClientStart_View; 2 3 import java.io.DataInputStream; 4 import java.io.IOException; 5 import java.net.Socket; 6 7 public class chat_Client_I implements Runnable{ 8 private String rece = null; 9 private static DataInputStream stream; 10 private ClientStart_View jframe; 11 12 public chat_Client_I(Socket socket,ClientStart_View jframe) { 13 this.jframe = jframe; 14 try { 15 stream = new DataInputStream(socket.getInputStream()); 16 } catch (IOException e2) { 17 e2.printStackTrace(); 18 } 19 } 20 21 @Override 22 public void run() { 23 while(true) { 24 try { 25 //if条件判断是否有可获取的字节 ,如果不是判断而一直读取的话,在结束时会有读取异常抛出。运行时是没有的。 26 if(stream.available()!=0) { 27 rece = stream.readUTF(); 28 jframe.TextA(rece); //调用 ClientStart_View.java类中方法将读入的字符串打印在文本域中 29 } 30 } catch (IOException e) { 31 // TODO Auto-generated catch block 32 e.printStackTrace(); 33 } 34 } 35 } 36 }
客户端的文件模块写的有点乱。客户端文件模块要实现发送和接收保存
发送要从计算机本地通过输入流读取文件,并通过输出流发送给服务端。
接收要从服务端通过输入流进行读入,然后通过输出流写入本地保存。
File_start类,连接服务端,并开启输入流读取服务端发来的文件
1 import java.io.IOException; 2 import java.net.Socket; 3 import java.net.UnknownHostException; 4 5 public class File_start implements Runnable{ 6 Socket socket=null; 7 8 @Override 9 public void run() { 10 try { 11 socket = new Socket("127.0.0.1", 12306); 12 // System.out.println("已连接"); 13 // System.out.println(socket.getInetAddress()+":"+socket.getLocalPort()); 14 } catch (UnknownHostException e) { 15 // TODO Auto-generated catch block 16 e.printStackTrace(); 17 } catch (IOException e) { 18 e.printStackTrace(); 19 } 20 21 File_I file_i; 22 try { 23 File_O file_O = new File_O(socket); 24 //开启从服务器的输入流线程 25 file_i = new File_I(socket); 26 Thread thread = new Thread(file_i); 27 thread.start(); 28 } catch (IOException e) { 29 e.printStackTrace(); 30 } 31 32 } 33 }
File_O类,读取本地文件并发送到服务端
1 import java.io.DataInputStream; 2 import java.io.DataOutputStream; 3 import java.io.FileInputStream; 4 import java.io.IOException; 5 import java.net.Socket; 6 7 public class File_O{ 8 public static File_O file_O = new File_O(); 9 // private Socket socket=null; 10 private byte[] readAllBytes = new byte[1024]; 11 private static DataOutputStream outputstream; 12 private DataInputStream inputstream; 13 14 public File_O() {} 15 public File_O(Socket socket) throws IOException { 16 // this.socket = socket; 17 outputstream = new DataOutputStream(socket.getOutputStream()); 18 } 19 20 //读取本地文件,并发送到服务端。面板中发送文件按钮在事件处理时会调用此函数 21 public void read(String url,String name) throws IOException { 22 outputstream.writeUTF(name); 23 inputstream = new DataInputStream(new FileInputStream(url)); 24 int len=0; 25 while((len=inputstream.read(readAllBytes))>0) { 26 outputstream.write(readAllBytes, 0, len); 27 outputstream.flush(); 28 } 29 //outputstream.close(); 30 // System.out.println("发送完成"); 31 } 32 33 //此方法暂时没有用到 34 public byte[] getread() { 35 return readAllBytes; 36 } 37 }
File_I类 从服务端的输出流读取文件,并保存到本地。
1 import view.ClientStart_View; 2 3 import java.awt.HeadlessException; 4 import java.io.DataInputStream; 5 import java.io.DataOutputStream; 6 import java.io.File; 7 import java.io.FileOutputStream; 8 import java.io.IOException; 9 import java.net.Socket; 10 11 import javax.swing.JOptionPane; 12 13 public class File_I implements Runnable{ 14 15 // public static File_I file_I = new File_I(); 16 private static ClientStart_View jframe; 17 private DataOutputStream output; 18 private static DataInputStream input; 19 private Socket socket; 20 21 public File_I() {} 22 public File_I(Socket socket) throws IOException { 23 this.socket = socket; 24 input = new DataInputStream(socket.getInputStream()); 25 } 26 27 public void getfile(ClientStart_View jframe) { 28 this.jframe = jframe; 29 } 30 31 @Override 32 public void run() { 33 String url = null; 34 String name = null; 35 byte[] rece = new byte[1024]; 36 while(true) { 37 try { 38 //if判断服务端输出流中是否有可读取字节 39 if (input.available()!=0) { 40 name = input.readUTF(); 41 // System.out.println(name); 42 43 //是否接受此文件,接收才会进行输入流读入保存 44 JOptionPane jOptionPane = new JOptionPane(); 45 int result=jOptionPane.showConfirmDialog(null, "是否接收该文件", "嘀嘀:一份文件消息", jOptionPane.YES_NO_OPTION); 46 // System.out.println(result); 47 if (result == jOptionPane.YES_OPTION) { 48 //默认存储路径及从服务端获取的文件名称 49 File file = new File("D:\\", name); 50 // file_I.jframe.getJF().setCurrentDirectory(new File("D:\\")); 51 jframe.getJF().setSelectedFile(file); 52 jframe.getJF().showSaveDialog(null); 53 url = jframe.getJF().getSelectedFile().getPath(); 54 // name =file_I.jframe.getJF().getSelectedFile().getName(); 55 56 int len; 57 try { 58 while((len=input.read(rece,0,rece.length))!=-1) { 59 // System.out.println("zhixing-1"); 60 output = new DataOutputStream(new FileOutputStream(url)); 61 output.write(rece, 0, len); 62 output.flush(); 63 } 64 } catch (IOException e) { 65 e.printStackTrace(); 66 } 67 } 68 } 69 } catch (HeadlessException | IOException e) { 70 e.printStackTrace(); 71 } 72 } 73 } 74 }
客户端的图形界面也是使用插件,所以只贴上主要的代码段
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
进入主页查看阅读最新文章