1、21课题内容和要求 .31.1 课题内容 .31.2 课题要求 .32设计分析 .32.1 思路分析 .32.2 概要设计 .32.3 程序流程图 .43.详细设计 .53.1 服务器端 53.2 客户端 .154.问题提出及改进 .234.1 问题的描述 .234.2 程序改进的思考 .235.课程设计总结 .236 指导老师意见 2431课题内容和要求1.1 课题内容基于 Socket 和多线程编程的聊天程序实现1.2 课题要求网络聊天程序设计非常复杂,允许多个人同时聊天更加需要多线程技术的支持,请实现一个简单的多线程网络聊天程序模拟。2设计分析2.1 思路分析1. 在网络越来越发达的今天
2、,人们对网络的依赖越来越强,网络聊天已经成了许多人生活中必不可少的一部分,基于这样的需求,出现了许多网络聊天通信工具,像 QQ,MSN 等等,但是人们已经不再满足于单一的两个人之间的聊天,而是对多人同时聊天产生了兴趣,于是出现了网络聊天室,不同地方的人可以在那个虚拟的聊天室里面热烈聊天。基于这样的想法,我们用 JAVA 设计一个多人同时聊天的小程序,用 Socket 编程实现网络通讯,面向连接的,采用多线程的实现技术。2. 实现一个简单的聊天室应用软件,要求能够显示聊天室中所有人的发言及人员进入和退出聊天室的相关信息。可以使用 TCP 实现 C/S 模式的聊天室,普通用户均使用客户端程序登录到
3、服务器,然后获得相关服务。也可以使用 UDP 广播或组播实现 P2P 模式的聊天室。并在设计报告中分析两种方案的特点和区别。3.在程序中,可以设置加入连接的最大数目,通过更改 IP 地址和端口号,成为不同的客户端,与服务器端连接,进行多用户聊天。4. 为方便用户交互,我们采用图形化的用户界面。实现了好友添加,消息收发、显示等基本功能。42.2 概要设计该网络聊天程序大致分为三个主要部分:客户端、服务器端和用户图形界面。各个部分的初步设计思想、流程及存储结构如下:1. 程序整体框架:主程序监听一端口,等待客户接入;同时构造一个线程类,准备接管会话。当一个 Socket 会话产生后,将这个会话交给
4、线程处理,然后主程序继续2. 客户端,使用 Socket 对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭 Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个端口。3. 服务器端,使用 ServerSocket 监听指定的端口,端口可以随意指定(由于1024 以下的端口通常属于保留端口,在一些操作系统中不可以随意使用,所以建议使用大于 1024 的端口) ,等待客户连接请求,客户连接后,会话产生;在完成会话后,关闭连接。4. 用户图形界面方便程序与用户的交互,多个用户参加,完成会话功能,具体的设计要方便用户的使用,直观清晰,简洁明了,友好
5、美观。5.存储结构下面列出主要存储结构或变量:存储结构、变量、对象 类型 说明post InetAddress 标识 IP 地址Port int 标识端口Server ServerThread 服务器端连接数Client Socket 客户端连接数Client(String ip,int p,Face chat)public Client 类成员函数Public void run() Void Client、Server类成员函数Server(int port,Face chat)public Server 类成员函数Face() Public Face 类成员函数52.3 程序流程图否是Se
6、ndMsg3.详细设计代码分服务器端、客户端、和用户类三部分,分别如下:3.1 服务器端服务器端主要是使用 ServerSocket 类,相当于服务器 Socket,用来监听试图进入的连接,当新的连接建立后,该类为他们实例化一个 Socket 对象,同时得到输入输出流,调用相应方法完成会话。public class Server / 主方法,程序执行入口public static void main(String args) new Server();创建 CServerSocket 类的对象将 sockets 与本地 IP 和相应的端口绑定Listen(),监听来自客户端的连接创建 CCli
7、entSocket 类的对象Connect(),将套接字与服务器相连接收客户端的连接请求是否有建立连接?为客户端建立连接否不为客户端建立连接,显示错误信息ReceiveMsg/()SendMsg(),在套接字上收发信息ReceiveMsg/()SendMsg(),在套接字上收发信息是否有连接到服务器?显示错误信息服务器 客户机6/ 执行消息发送public void send() if (!isStart) JOptionPane.showMessageDialog(frame, “服务器还未启动,不能发送消息!“, “错误“,JOptionPane.ERROR_MESSAGE);return
8、;if (clients.size() = 0) JOptionPane.showMessageDialog(frame, “没有用户在线,不能发送消息!“, “错误“,JOptionPane.ERROR_MESSAGE);return;String message = txt_message.getText().trim();if (message = null | message.equals(“) JOptionPane.showMessageDialog(frame, “消息不能为空!“, “错误“,JOptionPane.ERROR_MESSAGE);return;sendServe
9、rMessage(message);/ 群发服务器消息contentArea.append(“服务器说:“ + txt_message.getText() + “rn“);txt_message.setText(null);/ 构造放法public Server() frame = new JFrame(“服务器“);/ 更改JFrame的图标:/frame.setIconImage(Toolkit.getDefaultToolkit().createImage(Client.class.getResource(“qq.png“);/frame.setIconImage(Toolkit.get
10、DefaultToolkit().createImage(Server.class.getResource(“qq.png“);contentArea = new JTextArea();contentArea.setEditable(false);contentArea.setForeground(Color.blue);txt_message = new JTextField();txt_max = new JTextField(“30“);txt_port = new JTextField(“6666“);btn_start = new JButton(“启动“);7btn_stop =
11、 new JButton(“停止“);btn_send = new JButton(“发送“);btn_stop.setEnabled(false);listModel = new DefaultListModel();userList = new JList(listModel);southPanel = new JPanel(new BorderLayout();southPanel.setBorder(new TitledBorder(“写消息“);southPanel.add(txt_message, “Center“);southPanel.add(btn_send, “East“)
12、;leftPanel = new JScrollPane(userList);leftPanel.setBorder(new TitledBorder(“在线用户“);rightPanel = new JScrollPane(contentArea);rightPanel.setBorder(new TitledBorder(“消息显示区“);centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel,rightPanel);centerSplit.setDividerLocation(100);northPanel
13、= new JPanel();northPanel.setLayout(new GridLayout(1, 6);northPanel.add(new JLabel(“人数上限“);northPanel.add(txt_max);northPanel.add(new JLabel(“端口“);northPanel.add(txt_port);northPanel.add(btn_start);northPanel.add(btn_stop);northPanel.setBorder(new TitledBorder(“配置信息“);frame.setLayout(new BorderLayou
14、t();frame.add(northPanel, “North“);frame.add(centerSplit, “Center“);frame.add(southPanel, “South“);frame.setSize(600, 400);/frame.setSize(Toolkit.getDefaultToolkit().getScreenSize();/设置全屏int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;int screen_height = Toolkit.getDefaultToolki
15、t().getScreenSize().height;frame.setLocation(screen_width - frame.getWidth() / 2,(screen_height - frame.getHeight() / 2);8frame.setVisible(true);/ 关闭窗口时事件frame.addWindowListener(new WindowAdapter() public void windowClosing(WindowEvent e) if (isStart) closeServer();/ 关闭服务器System.exit(0);/ 退出程序);/ 文本
16、框按回车键时事件txt_message.addActionListener(new ActionListener() public void actionPerformed(ActionEvent e) send(););/ 单击发送按钮时事件btn_send.addActionListener(new ActionListener() public void actionPerformed(ActionEvent arg0) send(););/ 单击启动服务器按钮时事件btn_start.addActionListener(new ActionListener() public void
17、actionPerformed(ActionEvent e) if (isStart) JOptionPane.showMessageDialog(frame, “服务器已处于启动状态,不要重复启动!“,“错误“, JOptionPane.ERROR_MESSAGE);return;int max;int port;try try max = Integer.parseInt(txt_max.getText(); catch (Exception e1) throw new Exception(“人数上限为正整数!“);if (max ();serverSocket = new ServerS
18、ocket(port);serverThread = new ServerThread(serverSocket, max);serverThread.start();isStart = true; catch (BindException e) isStart = false;throw new BindException(“端口号已被占用,请换一个!“); catch (Exception e1) e1.printStackTrace();isStart = false;throw new BindException(“启动服务器异常!“);/ 关闭服务器SuppressWarnings(
19、“deprecation“)public void closeServer() try if (serverThread != null)serverThread.stop();/ 停止服务器线程for (int i = clients.size() - 1; i = 0; i-) / 给所有在线用户发送关闭命令clients.get(i).getWriter().println(“CLOSE“);clients.get(i).getWriter().flush();/ 释放资源clients.get(i).stop();/ 停止此条为客户端服务的线程clients.get(i).reader
20、.close();11clients.get(i).writer.close();clients.get(i).socket.close();clients.remove(i);if (serverSocket != null) serverSocket.close();/ 关闭服务器端连接listModel.removeAllElements();/ 清空用户列表isStart = false; catch (IOException e) e.printStackTrace();isStart = true;/ 群发服务器消息public void sendServerMessage(Str
21、ing message) for (int i = clients.size() - 1; i = 0; i-) clients.get(i).getWriter().println(“服务器:“ + message + “(多人发送)“);clients.get(i).getWriter().flush();/ 服务器线程class ServerThread extends Thread private ServerSocket serverSocket;private int max;/ 人数上限/ 服务器线程的构造方法public ServerThread(ServerSocket se
22、rverSocket, int max) this.serverSocket = serverSocket;this.max = max;public void run() while (true) / 不停的等待客户端的链接try Socket socket = serverSocket.accept();if (clients.size() = max) / 如果已达人数上限BufferedReader r = new BufferedReader(new InputStreamReader(socket.getInputStream();PrintWriter w = new Print
23、Writer(socket12.getOutputStream();/ 接收客户端的基本用户信息String inf = r.readLine();StringTokenizer st = new StringTokenizer(inf, “);User user = new User(st.nextToken(), st.nextToken();/ 反馈连接成功信息w.println(“MAX服务器:对不起,“ + user.getName()+ user.getIp() + “,服务器在线人数已达上限,请稍后尝试连接!“);w.flush();/ 释放资源r.close();w.close
24、();socket.close();continue;ClientThread client = new ClientThread(socket);client.start();/ 开启对此客户端服务的线程clients.add(client);listModel.addElement(client.getUser().getName();/ 更新在线列表contentArea.append(client.getUser().getName()+ client.getUser().getIp() + “上线!rn“); catch (IOException e) e.printStackTra
25、ce();/ 为一个客户端服务的线程class ClientThread extends Thread private Socket socket;private BufferedReader reader;private PrintWriter writer;private User user;public BufferedReader getReader() return reader;public PrintWriter getWriter() 13return writer;public User getUser() return user;/ 客户端线程的构造方法public Cli
26、entThread(Socket socket) try this.socket = socket;reader = new BufferedReader(new InputStreamReader(socket.getInputStream();writer = new PrintWriter(socket.getOutputStream();/ 接收客户端的基本用户信息String inf = reader.readLine();StringTokenizer st = new StringTokenizer(inf, “);user = new User(st.nextToken(),
27、st.nextToken();/ 反馈连接成功信息writer.println(user.getName() + user.getIp() + “与服务器连接成功!“);writer.flush();/ 反馈当前在线用户信息if (clients.size() 0) String temp = “;for (int i = clients.size() - 1; i = 0; i-) temp += (clients.get(i).getUser().getName() + “/“ + clients.get(i).getUser().getIp()+ “;writer.println(“US
28、ERLIST“ + clients.size() + “ + temp);writer.flush();/ 向所有在线用户发送该用户上线命令for (int i = clients.size() - 1; i = 0; i-) clients.get(i).getWriter().println(“ADD“ + user.getName() + user.getIp();clients.get(i).getWriter().flush(); catch (IOException e) e.printStackTrace();14SuppressWarnings(“deprecation“)pu
29、blic void run() / 不断接收客户端的消息,进行处理。String message = null;while (true) try message = reader.readLine();/ 接收客户端消息if (message.equals(“CLOSE“)/ 下线命令contentArea.append(this.getUser().getName()+ this.getUser().getIp() + “下线!rn“);/ 断开连接释放资源reader.close();writer.close();socket.close();/ 向所有在线用户发送该用户的下线命令for
30、(int i = clients.size() - 1; i = 0; i-) clients.get(i).getWriter().println(“DELETE“ + user.getName();clients.get(i).getWriter().flush();listModel.removeElement(user.getName();/ 更新在线列表/ 删除此条客户端服务线程for (int i = clients.size() - 1; i = 0; i-) if (clients.get(i).getUser() = user) ClientThread temp = cli
31、ents.get(i);clients.remove(i);/ 删除此用户的服务线程temp.stop();/ 停止这条服务线程return; else dispatcherMessage(message);/ 转发消息 catch (IOException e) e.printStackTrace();15/ 转发消息public void dispatcherMessage(String message) StringTokenizer stringTokenizer = new StringTokenizer(message, “);String source = stringToken
32、izer.nextToken();String owner = stringTokenizer.nextToken();String content = stringTokenizer.nextToken();message = source + “说:“ + content;contentArea.append(message + “rn“);if (owner.equals(“ALL“) / 群发for (int i = clients.size() - 1; i = 0; i-) clients.get(i).getWriter().println(message + “(多人发送)“)
33、;clients.get(i).getWriter().flush();3.2客户端客户端,使用 Socket 对网络上某一个服务器的某一个端口发出连接请求,一旦连接成功,打开会话;会话完成后,关闭 Socket。客户端不需要指定打开的端口,通常临时的、动态的分配一个端口。public class Clientprivate Socket socket;private PrintWriter writer;private BufferedReader reader;private MessageThread messageThread;/ 负责接收消息的线程private Map onLine
34、Users = new HashMap();/ 所有在线用户/ 主方法,程序入口public static void main(String args) new Client();/ 执行发送public void send() if (!isConnected) JOptionPane.showMessageDialog(frame, “还没有连接服务器,无法发送消息!“, “错误“,16JOptionPane.ERROR_MESSAGE);return;String message = textField.getText().trim();if (message = null | mess
35、age.equals(“) JOptionPane.showMessageDialog(frame, “消息不能为空!“, “错误“,JOptionPane.ERROR_MESSAGE);return;sendMessage(frame.getTitle() + “ + “ALL“ + “ + message);textField.setText(null);/ 构造方法public Client() textArea = new JTextArea();textArea.setEditable(false);textArea.setForeground(Color.blue);textFie
36、ld = new JTextField();txt_port = new JTextField(“6666“);txt_hostIp = new JTextField(“127.0.0.1“);txt_name = new JTextField(“xiaoqiang“);btn_start = new JButton(“连接“);btn_stop = new JButton(“断开“);btn_send = new JButton(“发送“);listModel = new DefaultListModel();userList = new JList(listModel);northPane
37、l = new JPanel();northPanel.setLayout(new GridLayout(1, 7);northPanel.add(new JLabel(“端口“);northPanel.add(txt_port);northPanel.add(new JLabel(“服务器IP“);northPanel.add(txt_hostIp);northPanel.add(new JLabel(“姓名“);northPanel.add(txt_name);northPanel.add(btn_start);northPanel.add(btn_stop);northPanel.set
38、Border(new TitledBorder(“连接信息“);rightScroll = new JScrollPane(textArea);rightScroll.setBorder(new TitledBorder(“消息显示区“);leftScroll = new JScrollPane(userList);17leftScroll.setBorder(new TitledBorder(“在线用户“);southPanel = new JPanel(new BorderLayout();southPanel.add(textField, “Center“);southPanel.add
39、(btn_send, “East“);southPanel.setBorder(new TitledBorder(“写消息“);centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroll,rightScroll);centerSplit.setDividerLocation(100);frame = new JFrame(“客户机“);/ 更改JFrame的图标:/frame.setIconImage(Toolkit.getDefaultToolkit().createImage(Client.class.getRe
40、source(“qq.png“);frame.setLayout(new BorderLayout();frame.add(northPanel, “North“);frame.add(centerSplit, “Center“);frame.add(southPanel, “South“);frame.setSize(600, 400);int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;int screen_height = Toolkit.getDefaultToolkit().getScreenSiz
41、e().height;frame.setLocation(screen_width - frame.getWidth() / 2,(screen_height - frame.getHeight() / 2);frame.setVisible(true);/ 写消息的文本框中按回车键时事件textField.addActionListener(new ActionListener() public void actionPerformed(ActionEvent arg0) send(););/ 单击发送按钮时事件btn_send.addActionListener(new ActionLis
42、tener() public void actionPerformed(ActionEvent e) send(););/ 单击连接按钮时事件18btn_start.addActionListener(new ActionListener() public void actionPerformed(ActionEvent e) int port;if (isConnected) JOptionPane.showMessageDialog(frame, “已处于连接上状态,不要重复连接!“,“错误“, JOptionPane. ERROR_MESSAGE);return;try try port
43、 = Integer.parseInt(txt_port.getText().trim(); catch (NumberFormatException e2) throw new Exception(“端口号不符合要求!端口为整数!“);String hostIp = txt_hostIp.getText().trim();String name = txt_name.getText().trim();if (name.equals(“) | hostIp.equals(“) throw new Exception(“姓名、服务器IP不能为空!“);boolean flag = connect
44、Server(port, hostIp, name);if (flag = false) throw new Exception(“与服务器连接失败!“);frame.setTitle(name);JOptionPane.showMessageDialog(frame, “成功连接!“); catch (Exception exc) JOptionPane.showMessageDialog(frame, exc.getMessage(),“错误“, JOptionPane. ERROR_MESSAGE););/ 单击断开按钮时事件btn_stop.addActionListener(new
45、ActionListener() public void actionPerformed(ActionEvent e) if (!isConnected) JOptionPane.showMessageDialog(frame, “已处于断开状态,不要重复断开!“,“错误“, JOptionPane. ERROR_MESSAGE);return;19try boolean flag = closeConnection();/ 断开连接if (flag = false) throw new Exception(“断开连接发生异常!“);JOptionPane.showMessageDialog(
46、frame, “成功断开!“); catch (Exception exc) JOptionPane.showMessageDialog(frame, exc.getMessage(),“错误“, JOptionPane. ERROR_MESSAGE););/ 关闭窗口时事件frame.addWindowListener(new WindowAdapter() public void windowClosing(WindowEvent e) if (isConnected) closeConnection();/ 关闭连接System.exit(0);/ 退出程序);/* 连接服务器* * p
47、aram port* param hostIp* param name*/public boolean connectServer(int port, String hostIp, String name) / 连接服务器try socket = new Socket(hostIp, port);/ 根据端口号和服务器ip建立连接writer = new PrintWriter(socket.getOutputStream();reader = new BufferedReader(new InputStreamReader(socket.getInputStream();/ 发送客户端用户基
48、本信息(用户名和ip地址)sendMessage(name + “ + 20socket.getLocalAddress().toString();/ 开启接收消息的线程messageThread = new MessageThread(reader, textArea);messageThread.start();isConnected = true;/ 已经连接上了return true; catch (Exception e) textArea.append(“与端口号为:“ + port + “ IP地址为:“ + hostIp+ “ 的服务器连接失败!“ + “rn“);isConnected = false;/ 未连接上return false;/* 发送消息* * param message*/public void sendMessage(String message) writer.println(message);writer.flush();/* 客户端主动关闭连接*/SuppressWarnings(“deprecation“)public synchronized boolean cl