java課編程作業:在老師給的demo的基礎上實現客戶端發送數據到服務器端,服務器端接受客戶端后進行數據廣播。
整體功能類似於聊天室,代碼部分不是太難,但是在本機測試的時候出現這樣的問題:
服務端通過將每一個Socket客戶端的IP存入Set集合,每次接受到數據后都向當前所有的IP轉發。但是本機演示的時候所有開的ChatClient客戶端都是同一IP,怎么測試呢?
解決辦法就是本機測試時候服務端向多個不同的端口轉發就好了,這樣跑起來的客戶端是在不同端口上進行監聽的(只是為了實現廣播,實際應用下還是通過IP來轉發)。
客戶端代碼:
import javax.print.attribute.HashPrintServiceAttributeSet; import javax.swing.*; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Toolkit; import java.awt.event.*; import java.io.*; import java.net.*; import java.util.*; public class ChatClient extends JFrame { JTextArea contents = new JTextArea(); JLabel label1 = new JLabel("服務器地址"); JTextField address = new JTextField(); JLabel label2 = new JLabel("用戶名"); JTextField username = new JTextField(); JButton online = new JButton("上線"); JButton offline = new JButton("下線"); JTextField input = new JTextField(); JButton send = new JButton("發送"); boolean onlineFlag = false; public ChatClient() { super("Chat Client"); contents.setEditable(false); getContentPane().add(contents); JPanel p1 = new JPanel(); p1.setLayout(new FlowLayout(FlowLayout.LEADING)); address.setPreferredSize(new Dimension(100, 28)); username.setPreferredSize(new Dimension(80, 28)); input.setPreferredSize(new Dimension(180, 28)); p1.add(label1); p1.add(address); p1.add(label2); p1.add(username); p1.add(online); p1.add(offline); p1.add(input); p1.add(send); offline.setEnabled(false); send.setEnabled(false); getContentPane().add(p1, BorderLayout.SOUTH); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { onlineFlag = false; Map<String, Object> m1 = new HashMap<String, Object>(); m1.put("time", new Date()); // 上線時間 m1.put("username", getUsername()); m1.put("onlineFlag", onlineFlag); send(getServerAddress(), 2222, m1); System.exit(0); } }); ActionListener al = new ActionListener() { public void actionPerformed(ActionEvent ae) { String server = getServerAddress(); if (server == null || server.length() == 0) { JOptionPane.showMessageDialog(ChatClient.this, "請輸入服務器地址!"); return; } if (ae.getSource() == online) { // 用戶點擊了上線按鈕 if (!onlineFlag) { onlineFlag = true; Map<String, Object> m1 = new HashMap<String, Object>(); m1.put("time", new Date()); // 上線時間 m1.put("username", getUsername()); m1.put("onlineFlag", onlineFlag); online.setEnabled(false); offline.setEnabled(true); send.setEnabled(true); address.setEnabled(false); username.setEnabled(false); send(getServerAddress(), 2222, m1); } } else { // 用戶點擊了下線按鈕 if (onlineFlag) { onlineFlag = false; Map<String, Object> m1 = new HashMap<String, Object>(); m1.put("time", new Date()); // 上線時間 m1.put("username", getUsername()); m1.put("onlineFlag", onlineFlag); online.setEnabled(true); offline.setEnabled(false); send.setEnabled(false); address.setEnabled(true); username.setEnabled(true); send(getServerAddress(), 2222, m1); } } } }; ActionListener al2 = new ActionListener() { public void actionPerformed(ActionEvent ae) { Map<String, Object> m1 = new HashMap<String, Object>(); m1.put("time", new Date()); m1.put("username", getUsername()); m1.put("input", getInput()); send(getServerAddress(), 2222, m1); } }; send.addActionListener(al2); online.addActionListener(al); offline.addActionListener(al); // setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(720, 400); Dimension d = Toolkit.getDefaultToolkit().getScreenSize(); setLocation((d.width - 720) / 2, (d.height - 400) / 2); setVisible(true); new Thread() { public void run() { try { // 在2221端口接收服務器發送過來的聊天內容,包括上線,下線消息。 DatagramSocket rSocket = new DatagramSocket(2220); while (true) { byte[] buffer = new byte[1024 * 16]; DatagramPacket recvPacket = new DatagramPacket(buffer, buffer.length); rSocket.receive(recvPacket); byte[] data = recvPacket.getData(); byte[] recvData = new byte[recvPacket.getLength()]; System.arraycopy(buffer, 0, recvData, 0, recvData.length); //將從服務器接收的數據存入Map並顯示 Map<String, Object> map = convertByteArrayToMap(recvData); if (map.containsKey("onlineFlag")) { // 上線或者下線 boolean b = (boolean) map.get("onlineFlag"); if (b) { // 上線 map.put("input", "上線"); } else { // 下線 map.put("input", "下線"); } } Date time = (Date) map.get("time"); String s = convertDateToFormatString(time); String user = (String) map.get("username"); user = "[" + user + "]"; contents.append(s + user + ": " + (String) map.get("input") + "\r\n"); } } catch (Exception e) { e.printStackTrace(); } } }.start(); } public String getServerAddress() { String s = address.getText(); //trim刪除多余空格 s = s.trim(); return s; } public String getUsername() { String s = username.getText(); s = s.trim(); if (s == null || s.length() == 0) { s = "匿名"; } return s; } public String getInput() { String s = input.getText(); s = s.trim(); if (s == null || s.length() == 0) { s = "不想說什么"; } return s; } public static String convertDateToFormatString(Date d) { return new java.text.SimpleDateFormat("(yyyy-MM-dd HH:mm:ss)").format(d); } public static void send(String ip, int port, Map<String, Object> map) { try { byte[] data = convertMapToByteArray(map); DatagramSocket socket = new DatagramSocket(); DatagramPacket packet = new DatagramPacket(data, data.length); packet.setSocketAddress(new InetSocketAddress(ip, port)); socket.send(packet); } catch (Exception e) { e.printStackTrace(); } } public static byte[] convertMapToByteArray(Map<String, Object> map) { try { //完成此代碼塊,要求將map集合中的數據轉換成字節數組 ByteArrayOutputStream b = new ByteArrayOutputStream(); ObjectOutputStream ois = new ObjectOutputStream(b); ois.writeObject(map); byte[] temp = b.toByteArray(); return temp; } catch (Exception e) { e.printStackTrace(); } return null; } public static Map<String, Object> convertByteArrayToMap(byte[] data) { try { ByteArrayInputStream bais = new ByteArrayInputStream(data); ObjectInputStream ois = new ObjectInputStream(bais); Map<String, Object> result = (Map<String, Object>) ois.readObject(); return result; } catch (Exception e) { e.printStackTrace(); } return null; } public static void main(String[] args) { new ChatClient(); } }
服務端代碼:
import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.util.*; import java.util.concurrent.LinkedBlockingQueue; public class ChatServer { LinkedBlockingQueue<Map<String, Object>> queue = new LinkedBlockingQueue<Map<String, Object>>(); Set<String> onlineIPs = new HashSet<String>(); // 當前在線IP。 public ChatServer() { try { new Thread() { public void run() { while (true) { try { //補齊程序,要求從接收隊列中取出數據,遍歷在線IP集合,向所有在線IP發送數據 if( !queue.isEmpty() ){ if( !onlineIPs.isEmpty() ){ for(String ip : onlineIPs){ //下面的端口號可以自定義增加,初始轉發至2220端口 多增加的端口可以實現本機內的廣播 ChatClient.send(ip, 2221, queue.peek() ); ChatClient.send(ip, 2220, queue.remove() ); } } } //end } catch (Exception e) { e.printStackTrace(); } } } }.start(); //接受端口2222 DatagramSocket serverSocket = new DatagramSocket(2222); System.out.println("Chat Server started."); while (true) { byte[] buffer = new byte[1024 * 16]; DatagramPacket recvPacket = new DatagramPacket(buffer, buffer.length); serverSocket.receive(recvPacket); InetSocketAddress remoteAddress = new InetSocketAddress(recvPacket.getAddress(), recvPacket.getPort()); System.out.println("接收到 " + remoteAddress.getHostString() + " 發送過來的數據包"); byte[] data = recvPacket.getData(); byte[] recvData = new byte[recvPacket.getLength()]; System.arraycopy(buffer, 0, recvData, 0, recvData.length); System.out.println(recvData.length); Map<String, Object> map = ChatClient.convertByteArrayToMap(recvData); System.out.print("map長度"+map.size()); if (map.containsKey("onlineFlag")) { // 上線或者下線 boolean b = (boolean) map.get("onlineFlag"); if (b) { // 上線 onlineIPs.add(remoteAddress.getHostString()); } else { // 下線 onlineIPs.remove(remoteAddress.getHostString()); } } queue.add(map); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { new ChatServer(); } }
