程序说明:
以下代码,利用java的网络编程,使用UDP通信作为通信协议,描述了一个简易的多人聊天程序,此程序可以使用公网或者是局域网进行聊天,要求有一台服务器。程序一共分为2个包,第一个包:udp,放置服务器端代码,包括:Server.java,第二个包:ui,放置客户端代码,包括:Login.java,Chat.java,Sender.java,Reciever.java,Test.java,期中Chat与Login为ui界面。
没有公网服务器的同学可以选择阿里云租赁【可以选择云翼计划】【非广告】,或者使用局域网,此代码使用公网ip测试成功,没有试过局域网,感兴趣的同学可以试一试。
具体代码:
Server.java:
1 package udp; 2 3 import java.io.IOException; 4 import java.io.UnsupportedEncodingException; 5 import java.net.*; 6 import java.util.LinkedList; 7 import java.util.List; 8 9 public class Server 10 { 11 12 public static void main(String[] args) 13 { 14 new Thread(new Server_Run()).start(); 15 } 16 17 } 18 19 class Server_Run implements Runnable 20 { 21 DatagramSocket server; 22 DatagramPacket packetIn = null; 23 DatagramPacket packetOut = null; 24 ListaddressList = new LinkedList (); 25 26 public void doForThis() throws IOException 27 { 28 try 29 { 30 InetAddress local = InetAddress.getLocalHost();// 得到本地地址 31 System.out.println("Server local address:" + local); 32 server = new DatagramSocket(8823, local);// 8823负责接收 33 34 while (true) 35 { 36 byte[] buff = new byte[4096]; 37 packetIn = new DatagramPacket(buff, 1024); 38 // 一旦调用这一方法, 会程序的阻塞, 直到你收到有数据报为止。 39 server.receive(packetIn); 40 41 // 每次建立连接,获取用户地址,并存储在列表中 42 InetSocketAddress clientAddress = (InetSocketAddress) packetIn.getSocketAddress(); // 获取客户端地址 43 String ip = clientAddress.getAddress().getHostAddress(); 44 int clientport = clientAddress.getPort();// 必须要通过端口号来找到客户端 45 if (!addressList.contains(clientAddress)) 46 { 47 addressList.add(new InetSocketAddress(ip, clientport)); 48 } 49 50 byte[] temp = packetIn.getData(); 51 int size = packetIn.getLength(); 52 String content = new String(temp, 0, size, "UTF-8"); 53 URLDecoder.decode(content, "utf-8"); 54 if (size > 0) 55 { 56 System.out.println(content); 57 } 58 URLEncoder.encode(content, "utf-8"); 59 for (InetSocketAddress clientisa : addressList) 60 { 61 packetOut = new DatagramPacket(content.getBytes("UTF-8"), 0, content.getBytes("UTF-8").length, 62 clientisa);// offset=0 偏移量 63 server.send(packetOut); 64 } 65 66 } 67 } catch (SocketException e) 68 { 69 e.printStackTrace(); 70 } catch (UnsupportedEncodingException e) 71 { 72 e.printStackTrace(); 73 } 74 } 75 76 public void run() 77 { 78 try 79 { 80 doForThis(); 81 } catch (IOException e) 82 { 83 e.printStackTrace(); 84 } 85 } 86 87 }
Login.java:
1 package ui; 2 3 import java.awt.FlowLayout; 4 import java.awt.Font; 5 import java.awt.Image; 6 import java.awt.event.ActionEvent; 7 import java.awt.event.ActionListener; 8 import java.awt.event.KeyEvent; 9 import java.awt.event.KeyListener; 10 import java.io.IOException; 11 import java.net.ConnectException; 12 import java.net.InetAddress; 13 import java.net.UnknownHostException; 14 import java.util.regex.Pattern; 15 16 import javax.swing.ImageIcon; 17 import javax.swing.JButton; 18 import javax.swing.JFrame; 19 import javax.swing.JLabel; 20 import javax.swing.JOptionPane; 21 import javax.swing.JPanel; 22 import javax.swing.JTextField; 23 import javax.swing.UIManager; 24 25 class Login implements ActionListener, KeyListener 26 { 27 28 JFrame frame; 29 JLabel logo; 30 JLabel lbl1, lbl2; 31 JTextField jtf1, jtf2; 32 JButton jb1, jb2; 33 JPanel jp1, jp2, jp3, jp4; 34 ImageIcon img, head; 35 int width = 400; 36 int height = 650; 37 38 String address; 39 String name; 40 public static void setUIFont() 41 { 42 Font f = new Font("宋体",Font.BOLD,18); 43 String names[]={ "Label", "CheckBox", "PopupMenu","MenuItem", "CheckBoxMenuItem", 44 "JRadioButtonMenuItem","ComboBox", "Button", "Tree", "ScrollPane", 45 "TabbedPane", "EditorPane", "TitledBorder", "Menu", "TextArea", 46 "OptionPane", "MenuBar", "ToolBar", "ToggleButton", "ToolTip", 47 "ProgressBar", "TableHeader", "Panel", "List", "ColorChooser", 48 "PasswordField","TextField", "Table", "Label", "Viewport", 49 "RadioButtonMenuItem","RadioButton", "DesktopPane", "InternalFrame" 50 }; 51 for (String item : names) { 52 UIManager.put(item+ ".font",f); 53 } 54 } 55 56 public Login() throws IOException 57 { 58 setUIFont(); 59 img = new ImageIcon(Login.class.getResource("/img/logo.jpg")); 60 head = new ImageIcon(Login.class.getResource("/img/head.png"));// 此句暂时无效 61 img.setImage(img.getImage().getScaledInstance(width, 360, Image.SCALE_DEFAULT)); 62 lbl1 = new JLabel("请输入服务器地址:"); 63 lbl2 = new JLabel("请输入用户名:"); 64 logo = new JLabel(img);// 预备图片 65 jb1 = new JButton("加入"); 66 jb2 = new JButton("退出"); 67 jb1.addActionListener(this); 68 jb2.addActionListener(this); 69 jtf1 = new JTextField(20); 70 jtf2 = new JTextField(20); 71 jp1 = new JPanel(); 72 jp2 = new JPanel(); 73 jp3 = new JPanel(); 74 jp4 = new JPanel(); 75 jp1.add(logo); 76 jp2.add(lbl1); 77 jp2.add(jtf1); 78 jp3.add(lbl2); 79 jp3.add(jtf2); 80 jp4.add(jb1); 81 jp4.add(jb2); 82 83 jtf1.addKeyListener(this);// 绑定enter 84 jtf2.addKeyListener(this);// 绑定enter 85 frame = new JFrame("PLMM聊天室"); 86 frame.setIconImage(head.getImage()); 87 frame.setLayout(new FlowLayout(1, 20, 30)); 88 frame.add(jp1); 89 frame.add(jp2); 90 frame.add(jp3); 91 frame.add(jp4); 92 frame.setLocationRelativeTo(null); 93 frame.setSize(width, height); 94 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 95 frame.setVisible(true); 96 } 97 98 // 以下代码判断ip是否可连接,未完成 99 // public boolean isHostConnectable(String host, int port) 100 // { 101 // Socket socket = new Socket(); 102 // try 103 // { 104 // socket.connect(new InetSocketAddress(host, port)); 105 // } catch (ConnectException e) 106 // { 107 // e.printStackTrace(); 108 // return false; 109 // } catch (IOException e) 110 // { 111 // e.printStackTrace(); 112 // return false; 113 // } finally 114 // { 115 // try 116 // { 117 // socket.close(); 118 // } catch (IOException e) 119 // { 120 // e.printStackTrace(); 121 // } 122 // } 123 // return true; 124 // } 125 126 // // 以下代码判断ip是否超时 127 public boolean isHostReachable(String host, Integer timeOut) 128 { 129 try 130 { 131 return InetAddress.getByName(host).isReachable(timeOut); 132 } catch (ConnectException e) 133 { 134 e.printStackTrace(); 135 } catch (UnknownHostException e) 136 { 137 e.printStackTrace(); 138 } catch (IOException e) 139 { 140 e.printStackTrace(); 141 } 142 return false; 143 } 144 145 public void connect() 146 { 147 // 下面判断用户名 148 if (jtf2.getText().length() > 8) 149 { // 对用户名长度进行限制 150 JOptionPane.showMessageDialog(null, "用户名长度必须小于8!", "Warning", JOptionPane.ERROR_MESSAGE); 151 } else if (jtf2.getText().length() == 0) 152 { // 为空判断 153 JOptionPane.showMessageDialog(null, "用户名不能为空!", "Warning", JOptionPane.ERROR_MESSAGE); 154 } else 155 { 156 name = jtf2.getText(); 157 } 158 // 下面判断ip地址 159 address = jtf1.getText(); 160 Pattern pattern = Pattern.compile( 161 "^(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])\\.(\\d|[1-9]\\d|1\\d{2}|2[0-5][0-5])$"); // IP正则表达式 162 boolean flag = pattern.matcher(address).matches(); 163 if (address.length() == 0) 164 { // 为空判断 165 JOptionPane.showMessageDialog(null, "IP地址不能为空!", "Warning", JOptionPane.ERROR_MESSAGE); 166 } else if (flag == false) 167 { // IP正则匹配 168 JOptionPane.showMessageDialog(null, "IP地址不正确!", "Warning", JOptionPane.ERROR_MESSAGE); 169 } else if (isHostReachable(address, 1000) == false) 170 {// 判断是否连接超时 171 JOptionPane.showMessageDialog(null, "IP连接超时!", "Warning", JOptionPane.ERROR_MESSAGE); 172 } else 173 { 174 frame.setVisible(false); // 如何实现真正的关闭?且不退出程序(不中断下一步执行) 175 frame = null; 176 frame = new Chat(address, name);// 传入address 177 } 178 } 179 180 public void actionPerformed(ActionEvent e) 181 { 182 if (e.getSource() == jb1) 183 { 184 connect(); 185 186 } else if (e.getSource() == jb2) 187 { 188 System.exit(0); 189 } 190 } 191 192 public void keyTyped(KeyEvent e) 193 { 194 195 } 196 197 public void keyPressed(KeyEvent e) 198 { 199 if (e.getKeyCode() == KeyEvent.VK_ENTER) 200 { 201 connect(); 202 } 203 204 } 205 206 public void keyReleased(KeyEvent e) 207 { 208 209 } 210 211 }
Chat.java:
1 package ui; 2 3 import java.awt.BorderLayout; 4 import java.awt.FlowLayout; 5 import java.awt.event.ActionEvent; 6 import java.awt.event.ActionListener; 7 import java.awt.event.KeyEvent; 8 import java.awt.event.KeyListener; 9 10 import javax.swing.JButton; 11 import javax.swing.JFrame; 12 import javax.swing.JOptionPane; 13 import javax.swing.JPanel; 14 import javax.swing.JScrollPane; 15 import javax.swing.JSplitPane; 16 import javax.swing.JTextArea; 17 18 public class Chat extends JFrame implements ActionListener, KeyListener 19 { 20 private static final long serialVersionUID = 1L; 21 double initWidth = 800; 22 double initHeight = 600; 23 24 JPanel jp1, jp2; // 定义面板 25 JSplitPane jsp; // 定义拆分窗格 26 JTextArea jta1, jta2; // 定义文本域 27 JScrollPane jspane1, jspane2; // 定义滚动窗格 28 29 JButton jb1, jb2; // 定义按钮 30 31 String addressSTR; 32 String name; 33 String messageOut = ""; 34 String messageIn = ""; 35 36 public Chat(String addressSTR, String name) 37 { 38 39 this.addressSTR = addressSTR; 40 this.name = name; 41 42 jta1 = new JTextArea(); // 创建多行文本框 43 jta2 = new JTextArea(); 44 jta1.setLineWrap(true); // 设置多行文本框自动换行 45 jta1.setEditable(false); // 禁止用户修改公屏信息 46 jta1.addKeyListener(this); 47 jta2.setLineWrap(true); 48 jta2.addKeyListener(this); 49 jspane1 = new JScrollPane(jta1); // 创建滚动窗格 50 jspane2 = new JScrollPane(jta2); 51 jsp = new JSplitPane(JSplitPane.VERTICAL_SPLIT, jspane1, jspane2); // 创建拆分窗格 52 53 double initSep = initHeight * 2 / 3; 54 55 jsp.setDividerLocation((int) initSep); // 设置拆分窗格分频器初始位置 56 jsp.setDividerSize(1); // 设置分频器大小 57 jb1 = new JButton("发送"); // 创建按钮 58 jb2 = new JButton("关闭"); 59 jb1.addActionListener(this); 60 jb2.addActionListener(this); 61 jp1 = new JPanel(); // 创建面板 62 jp2 = new JPanel(); 63 jp1.setLayout(new BorderLayout()); // 设置面板布局 64 jp2.setLayout(new FlowLayout(FlowLayout.RIGHT)); 65 jp1.add(jsp); // 分频器 66 jp2.add(jb1); // 按钮 67 jp2.add(jb2); 68 69 this.add(jp1, BorderLayout.CENTER); 70 this.add(jp2, BorderLayout.SOUTH); 71 72 // 设置窗体实行 73 this.setTitle("澳门聊天室-人间天堂"); // 设置界面标题 74 // this.setIconImage(new ImageIcon(User_chat.class.getClassLoader().getResource("Head.png")).getImage()); 75 this.setSize((int) initWidth, (int) initHeight); // 设置界面像素 76 this.setLocationRelativeTo(null); // 居中运行 77 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置虚拟机和界面一同关闭 78 this.setVisible(true); // 设置界面可视化 79 80 new Thread(new Reciever(addressSTR, messageIn, jta1)).start(); 81 } 82 83 public void sendOut() 84 { 85 messageOut = jta2.getText(); 86 if (messageOut.hashCode() != 0) // 此处必须用hash 87 { 88 new Thread(new Sender(addressSTR, name, messageOut)).start(); 89 jta2.setText(""); 90 } else 91 { 92 JOptionPane.showMessageDialog(null, "发送消息不能为空!", "Warning", JOptionPane.ERROR_MESSAGE); 93 } 94 95 } 96 97 public void scrollAndSetCursor(JTextArea jta) 98 { 99 // 自动滚动 100 jta.setSelectionStart(jta.getText().length()); 101 102 } 103 104 public void actionPerformed(ActionEvent e) 105 { 106 if (e.getSource() == jb1) 107 { 108 // 发送 109 sendOut(); 110 111 } else if (e.getSource() == jb2) 112 { 113 System.exit(0); 114 115 } 116 } 117 118 public void keyTyped(KeyEvent e) 119 { 120 121 } 122 123 public void keyPressed(KeyEvent e) 124 { 125 if (e.getKeyCode() == KeyEvent.VK_CONTROL + KeyEvent.VK_ENTER) 126 { 127 sendOut(); 128 } 129 } 130 131 public void keyReleased(KeyEvent e) 132 { 133 if (e.getKeyCode() == KeyEvent.VK_ENTER) 134 { 135 jta2.setText(""); 136 } 137 138 } 139 }
Sender.java:
1 package ui; 2 3 import java.io.IOException; 4 import java.net.DatagramPacket; 5 import java.net.DatagramSocket; 6 import java.net.InetSocketAddress; 7 import java.net.SocketException; 8 import java.net.URLEncoder; 9 import java.net.UnknownHostException; 10 11 public class Sender implements Runnable 12 { 13 String address = ""; 14 String name = ""; 15 DatagramSocket socket = null; 16 DatagramPacket packetOut = null; 17 String messageOut = ""; 18 19 public Sender(String address, String name, String messageOut) 20 { 21 this.address = address; 22 this.name = name; 23 this.messageOut = messageOut; 24 } 25 26 public void doForThis() throws IOException 27 { 28 29 try 30 { 31 if (messageOut.length() != 0) 32 { 33 socket = new DatagramSocket(8823); // 进行一次发送 34 InetSocketAddress isa = new InetSocketAddress(address, 8823); 35 36 String content = name + ":" + messageOut; 37 URLEncoder.encode(content, "utf-8"); 38 39 packetOut = new DatagramPacket(content.getBytes("UTF-8"), content.getBytes("UTF-8").length, isa); 40 socket.send(packetOut); 41 messageOut = ""; 42 socket.close(); 43 // socket.receive(packetOut); 44 // String message = new String(packetOut.getData(), 0, packetOut.getLength()); 45 // System.out.println("本机端口和IP信息:" + message); 46 // int clientListenPort = Integer.valueOf(message.split(":")[1]); 47 // System.out.println(clientListenPort); 48 } 49 } catch (SocketException e) 50 { 51 e.printStackTrace(); 52 } catch (UnknownHostException e) 53 { 54 e.printStackTrace(); 55 } finally 56 { 57 58 } 59 60 } 61 62 public void run() 63 { 64 try 65 { 66 System.out.println("sender is running"); 67 doForThis(); 68 } catch (IOException e) 69 { 70 e.printStackTrace(); 71 72 } 73 } 74 75 }
Reciever.java:
1 package ui; 2 3 import java.io.IOException; 4 import java.io.UnsupportedEncodingException; 5 import java.net.DatagramPacket; 6 import java.net.DatagramSocket; 7 import java.net.InetSocketAddress; 8 import java.net.SocketAddress; 9 import java.net.SocketException; 10 import java.net.URLDecoder; 11 12 import javax.swing.JTextArea; 13 14 public class Reciever implements Runnable 15 { 16 String strAddress = ""; 17 String name = ""; 18 String messageOut = ""; 19 String messageIn = ""; 20 DatagramSocket socket = null; 21 DatagramPacket packet = null; 22 int clientListenPort; 23 JTextArea jta1; 24 25 public Reciever(String strAddress, String messageIn, JTextArea jta1) 26 { 27 this.strAddress = strAddress; 28 this.messageIn = messageIn; 29 this.jta1 = jta1; 30 } 31 32 // @SuppressWarnings("resource") 此处也不能加 33 public void doForThis() throws IOException 34 { 35 SocketAddress server = new InetSocketAddress(strAddress, 8823); 36 @SuppressWarnings("resource") 37 DatagramSocket ds = new DatagramSocket(); // ds必须经过一次发送和接收,为什么呢? 38 byte buff[] = new byte[1024]; 39 DatagramPacket dp = new DatagramPacket(buff, 0, 0, server); 40 ds.send(dp);// 发送信息到服务器 41 ds.receive(dp); 42 try 43 { 44 socket = new DatagramSocket(); // 负责接收 45 46 while (true) 47 { 48 packet = new DatagramPacket(buff, 1024);// 实现接收 49 50 ds.receive(packet); 51 52 byte[] temp = packet.getData(); 53 int size = packet.getLength(); 54 if (size > 0) 55 { 56 String content = new String(temp, 0, size, "UTF-8"); 57 // System.out.println(content); 58 URLDecoder.decode(content, "utf-8"); 59 messageIn = "\n" + content; 60 jta1.append(messageIn); 61 // 自动滚动 62 scrollAndSetCursor(jta1); 63 } 64 } 65 } catch (SocketException e) 66 { 67 e.printStackTrace(); 68 } catch (UnsupportedEncodingException e) 69 { 70 e.printStackTrace(); 71 } 72 } 73 74 public void scrollAndSetCursor(JTextArea jta) 75 { 76 // 自动滚动 77 jta.setSelectionStart(jta.getText().length()); 78 79 } 80 81 public void run() 82 { 83 try 84 { 85 System.out.println("receiver is running"); 86 doForThis(); 87 } catch (IOException e) 88 { 89 e.printStackTrace(); 90 } 91 } 92 }
Test.java:
1 package ui; 2 3 import java.io.IOException; 4 5 public class Test 6 { 7 public static void main(String[] args) throws IOException 8 { 9 new Login(); 10 } 11 }
测试结果:
注意我Login代码里的img,没有在对应路径放置图片的话是无法显示的,我放在bin目录下自己创建的img文件夹里。
登录界面,【忽略窗口名】:
登录进去后,测试聊天【忽略窗口名】: