搜索
您的当前位置:首页正文

用户界面设计

来源:知库网


用户界面设计

软件四班 郑伟090203092 张艳飞090203100 朱涛090203086

第1章 概述

网络聊天工具已经作为一种重要的信息交流工具,受到越来越多的网民的青睐。目前,出现了很多非常不错的聊天工具,其中应用比较广泛的有Netmeeting、腾讯QQ、MSN-Messager等等。我们自己可以通过一个小例子来了解聊天工具的基本工作原理。 关键词:junit4 C/S MySQL Socket Swing Thread dom4j

第2章 系统需要分析

该系统开发主要包括一个网络聊天服务器程序和一个网络聊天客户程序两个方面。前者通过Socket套接字建立服务器,服务器能读取、转发客户端发来信息,并能刷新用户列表。后者通过与服务器建立连接,来进行客户端与客户端的信息交流。其中用到了局域网通信机制的原理,通过直接继承Thread类来建立多线程。开发中利用了计算机网络编程的基本理论知识,如TCP/IP协议、客户端/服务器端模式(Client/Server模式)、网络编程的设计方法等。在网络编程中对信息的读取、发送,是利用流来实现信息的交换,其中介绍了对实现一个系统的信息流的分析,包含了一些基本的软件工程的方法。经过分析这些情况,该聊天工具采用Eclipse为基本开发环境和java语言进行编写,首先可在短时间内建立系统应用原型,然后,对初始原型系统进行不断修正和改进,直到形成可行系统。

第3章 系统概要设计

1、即时通讯原理

首先验证登陆,如果成功,则建立与服务端的socket连接,服务端新开启一个线程

1

专门为它服务,将打包好的Message发送给服务器端,服务器端根据Message里面的信息,再将信息转发给其他用户。一个标准的C/S模式。 2、Swing技术

Swing是一个用于开发Java应用程序用户界面的开发工具包。它以抽象窗口工具包

(AWT)为基础使跨平台应用程序可以使用任何可插拔的外观风格。用来实现客服端的界面设计。 3、socket技术

socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机 制,取后一种

意思。通常也称作\"套接字\",用于描述IP地址和端口,是一个通信链的句柄。在Internet上的主机一般运行了多个服务软件,同时提供几 种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原意那样,象一个多孔插座。一台主机 犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。 4、thread技术

线程是进程中某个单一顺序的控制流,也被称为轻量级进程。计算机科学术语是指运行中的程序的调度单位。进程是程序中一个单一的顺序控制流程,在单个程序中同时运行多个线程完成不同的工作,称为多线程。进程是一个可执行的程序,由私有虚拟地址空间,代码,数据和其他操作系统资源(如进程创建的文件,管道,同步对象等)组成。一个应用可以有一个或多个进程。一个进程可以有一个或多个线程,其中一个是主线程。 5.Junit技术

JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架(regression testing

framework)。Junit测试是程序员测试,即所谓白盒测试,因为程序员知道被测试的软件如何(How)完成功能和完成什么样(What)的功能。Junit是一套框架,继承TestCase类,就可以用Junit进行自动测试了。

第4章 系统详细设计

1)编写客户端界面

2

我们可以通过java swing中的类来实现上面的效果。 2)编写服务器端界面 3)编写聊天主界面

3

4)实现连接Mysql数据库以及插入数据和查找数据的方法 create database chat; use chat; create table t_chat( id int primary key auto_increment, name varchar(30) ); 建立数据库和建表语句 package com.dbc; import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.PreparedStatement; public class DatabaseConnection { private static String URL = \"jdbc:mysql://localhost:3306/chat\"; private static String DRIVER = \"org.gjt.mm.mysql.Driver\"; 4

private static String USER = \"root\"; private static String PASSWORD = \"haibing\"; private Connection conn = null; public DatabaseConnection() { try { Class.forName(DRIVER); } catch (ClassNotFoundException e) { e.printStackTrace(); } try { this.conn = DriverManager.getConnection(URL, USER, PASSWORD); } catch (SQLException e) { e.printStackTrace(); } } // 关闭Connection public void closeConnection(Connection conn) { if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } // 获得connection public Connection getConnection() { return conn; } // 查找用户 public boolean findUserByName(String name) { String sql = \"select *from t_chat where name=?\"; try { PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, name); pstmt.execute(); ResultSet rs = pstmt.executeQuery(); if (rs.next()) { return true; } } catch (SQLException e) { 5

e.printStackTrace(); } return false; } // 添加用户信息 public boolean insertUser(String username) { boolean result = false; String sql = \"insert into t_chat(name) values (?)\"; try { PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); pstmt.execute(); } catch (SQLException e) { e.printStackTrace(); } return result; } } 5)给客户端的登陆按钮增加监听器 /* * 给登陆按钮增加监听器 */ jButton1.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Client.this.login(e); // 登陆方法 } }); 6)给客户端重置按钮增加监听器 /* * 重置按钮增加监听器 */ jButton2.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { username.setText(\"\"); // 将输入字段内容清空 hostAddress.setText(\"\"); 6

port.setText(\"\"); } }); 7)给客户端注册按钮增加监听器 jButton3.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Register r = new Register(\"用户注册\"); } }); 8)编写客户端线程(非主线程) package com.client; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.List; import javax.swing.JOptionPane; import com.chat.ChatClient; import com.util.CharacterTool; import com.util.XMLUtil; public class ClientConnection extends Thread { private Socket socket; private int port; private String username; private Client client; private String hostAddress; private InputStream is; private OutputStream os; private ChatClient chatClient; public ClientConnection(Client client, String hostAddress, int port, String username) { this.client = client; this.hostAddress = hostAddress; this.port = port; 7

this.username = username; this.connectToServer(); login(); } // 连接到服务器 private void connectToServer() { try { this.socket = new Socket(this.hostAddress, this.port); is = socket.getInputStream(); os = socket.getOutputStream(); } catch (Exception e) { JOptionPane.showMessageDialog(client, \"服务器没有启动,请启动\警告\ JOptionPane.WARNING_MESSAGE); e.printStackTrace(); } } public boolean login() { try { String xml = XMLUtil.constructLoginXML(this.username); os.write(xml.getBytes()); // 向服务器端发送用户的登录信息(其中包含了用户名) byte[] buf = new byte[5000]; int length = is.read(buf); // 读取服务器端的响应结果,判断用户是否登录成功 String loginResultXML = new String(buf, 0, length); String loginResult = XMLUtil.extractUsername(loginResultXML); if (\"success\".equals(loginResult)) { this.chatClient = new ChatClient(this); this.client.setVisible(false); return true; } } catch (Exception e) { e.printStackTrace(); } return false; 8

} public Socket getSocket() { return socket; } // 发送消息 public void sendMessage(String message, String type) { try { int t = Integer.parseInt(type); String xml = null; // 客户端向服务器端发送聊天数据 if (CharacterTool.CLIENT_MESSAGE == t) { xml = XMLUtil.constructMessageXML(this.username, message); } // 客户端服务器端发送关闭窗口的数据 else if (CharacterTool.CLOSE_CLIENT_WINDOW == t) { xml = XMLUtil.constructCloseClientWindowXML(username); } this.os.write(xml.getBytes()); } catch (Exception e) { e.printStackTrace(); } } // 完成了所有读的工作 @Override public void run() { try { while (true) { byte[] buf = new byte[5000]; int length = is.read(buf); String xml = new String(buf, 0, length); int type = Integer.parseInt(XMLUtil.extractType(xml)); if (type == CharacterTool.LOGIN) { } if (type == CharacterTool.USER_LIST) { List list = XMLUtil.extractUserList(xml); String users = \"\"; for (String user : list) { users += user + \"\\n\"; } chatClient.getjTextArea2().setText(users); } 9

// 服务器端发送来的信息 else if (type == CharacterTool.SERVER_MESSAGE) { String content = XMLUtil.extractContent(xml); this.chatClient.getJTextArea1().append(content + \"\\n\"); } else if (type == CharacterTool.CLOSE_SERVER_WINDOW) { JOptionPane.showMessageDialog(this.chatClient, \"服务器端关闭,程序退出!\\"警告\JOptionPane.WARNING_MESSAGE); System.exit(0); // 客户端退出 } // 服务器端确认关闭客户端窗口 else if (type == CharacterTool.CLOSE_CLIENT_WINDOW_CONFIRMATION) { try { this.getSocket().close(); } catch (Exception e) { e.printStackTrace(); } finally { System.exit(0); } } } } catch (Exception e) { e.printStackTrace(); } } } 9)编写服务器端线程(非主线程).这个线程用来处理用户连接的线程 package com.server; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; import javax.swing.JOptionPane; import com.util.XMLUtil; public class ServerConnection extends Thread { private ServerSocket serverSocket; 10

private Server server; public ServerConnection(Server server, int port) { try { this.server = server; this.serverSocket = new ServerSocket(port); this.server.getJLabel2().setText(\"运行\"); this.server.getJButton().setEnabled(false); } catch (Exception ex) { ex.printStackTrace(); JOptionPane.showMessageDialog(this.server, \"端口号被占用!\警告\ JOptionPane.ERROR_MESSAGE); System.exit(0); } } @Override public void run() { while (true) { try { Socket socket = this.serverSocket.accept(); InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); byte[] buf = new byte[5000]; int length = is.read(buf); // 客户端发来的连接信息(包括了用户名) String loginXML = new String(buf, 0, length); System.out.println(\"loginxml\"+ loginXML); // 从客户端登录数据中提取出用户名信息(username) String username = XMLUtil.extractUsername(loginXML); System.out.println(\"username=\" + username); String loginResult = \"success\"; String xml = XMLUtil.constructLoginXML(loginResult); os.write(xml.getBytes()); //创建新的线程。用于处理用户的聊天数据。每一个连接上的用户都会对应一个该线程 11

ServerMessageThread serverMessageThread ServerMessageThread(server,socket); //将用户名及与之对应的线程对应放到Map中 this.server.getMap().put(username, serverMessageThread); //更新用户列表 serverMessageThread.updateUserList(); serverMessageThread.start(); } catch (Exception ex) { ex.printStackTrace(); } } } }

= new 第5章 系统实现

10)服务器端用来处理消息的线程 package com.server; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.util.Collection; import java.util.Map; import java.util.Set; import com.util.CharacterTool; import com.util.XMLUtil; public class ServerMessageThread extends Thread{ private Server server; private OutputStream os ; private InputStream is; public ServerMessageThread(Server server,Socket socket) { try { this.server = server; this.is = socket.getInputStream(); 12

this.os = socket.getOutputStream(); } catch (Exception e) { e.printStackTrace(); } } //更新用户列表 客户端和服务器端的列表都要更新 public void updateUserList(){ //获得用户名集合 Set users = this.server.getMap().keySet(); String xml = XMLUtil.constructUserList(users); String str = \"\"; for(String user: users){ str += user + \"\\n\"; } this.server.getjTextArea().setText(str); //获得所有用户线程 Collection cols = this.server.getMap().values(); for(ServerMessageThread smt:cols){ smt.sendMessage(xml); } } //向客户端发送用户列表信息 public void sendMessage(String message){ try { os.write(message.getBytes()); } catch (Exception e) { e.printStackTrace(); } } @Override public void run() { while(true){ try { byte[] buf = new byte[5000]; int length = is.read(buf); String xml = new String(buf,0,length); int type = Integer.parseInt(XMLUtil.extractType(xml)); //聊天数据 if(CharacterTool.CLIENT_MESSAGE == type){ 13

String username = XMLUtil.extractUsername(xml); String content = XMLUtil.extractContent(xml); //构造向所有客户端发送信息 String message = username + \"说:\" + content; //向所有客户端发送的聊天数据 String messageXML = XMLUtil.constructServerMessageXML(message); Map map = this.server.getMap(); Collection cols = map.values(); for(ServerMessageThread smt:cols){ //将xml聊天数据发送给每一个客户端 smt.sendMessage(messageXML); } }else if(type == CharacterTool.CLOSE_CLIENT_WINDOW){ String username = XMLUtil.extractUsername(xml); //获得待删除用户所对应的线程对象 ServerMessageThread smt = this.server.getMap().get(username); //构造向客户端确认关闭的XML信息 String confirmationXML = XMLUtil.constructCloseClientWindowConfirmationXML(); smt.sendMessage(confirmationXML); //从用户列表的Map中将该用户去除 this.server.getMap().remove(username); //更新在线用户列表 this.updateUserList(); this.is.close(); this.os.close(); break; //结束当前线程 } } catch (Exception e) { } } } } 11)增加输入信息的验证逻辑 package com.util; public class CharacterUtil { /* * 判断给定的字符串是否为空,为空返回true.否则返回false */ 14

public static boolean isEmpty(String str){ if(\"\".equals(str)){ return true; }else{ return false; } } /* * 字符串是不是一个数字.是的话,返回true,否则返回false */ public static boolean isNumber(String str){ for(int i=0;i 65535){ return false; } return true; } } 12)编写xml信息的解析类。由于代码量太大,这里不在贴出 13)将一些标号设计成常量,这样更好的遵循java设计 package com.util; public class CharacterTool { public static final int LOGIN = 1; public static final int USER_LIST = 4; public static final int SERVER_MESSAGE = 3; public static final int CLIENT_MESSAGE = 2; public static final int CLOSE_CLIENT_WINDOW = 5; public static final int CLOSE_SERVER_WINDOW = 6; public static final int CLOSE_CLIENT_WINDOW_CONFIRMATION = 7; } 15

14)用户注册逻辑 package com.register; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JTextField; import com.dbc.DatabaseConnection; import com.util.CharacterUtil; public class Register extends JFrame { private JButton jbutton; private JTextField jtextField; private JPanel jpanel; private JLabel jlabel; private boolean result = true; private DatabaseConnection dbc = new DatabaseConnection(); public Register(String name) { super(name); initComponents(); } // 初始化控件 public void initComponents() { jbutton = new JButton(); jtextField = new JTextField(15); jpanel = new JPanel(); jlabel = new JLabel(); jlabel.setText(\"用户名:\"); jbutton.setText(\"提交\"); jpanel.setBorder(javax.swing.BorderFactory.createTitledBorder(\"用户注册\")); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 设置关闭 this.setAlwaysOnTop(true); this.setVisible(true); 16

jpanel.add(jlabel); jpanel.add(jtextField); jpanel.add(jbutton); this.getContentPane().add(jpanel); this.setSize(250, 130); this.setVisible(true); this.setResizable(false); this.setLocation(300, 300); jbutton.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { String str = jtextField.getText(); validate(str); } }); } private void validate(String name) { if (CharacterUtil.isEmpty(name)) { JOptionPane.showMessageDialog(this, \"用户名不能为空\", \"警告\", JOptionPane.WARNING_MESSAGE); return; } if (dbc.findUserByName(name)) { JOptionPane.showMessageDialog(this, \"注册的用户名已经存在,请重新输入\", \"警告\", JOptionPane.WARNING_MESSAGE); return; } dbc.insertUser(name); JOptionPane.showMessageDialog(this, \"恭喜,注册成功。可以登陆!\", \"警告\", JOptionPane.OK_OPTION); this.setVisible(false); } public static void main(String[] args) { Register r = new Register(\"注册\"); } } 17

16)使用junit来进行单元测试 package com.util; import java.io.StringReader; import junit.framework.Assert; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import org.junit.Before; import org.junit.Test; public class XMLUtilTest { private SAXReader saxReader; @Before public void setUp(){ saxReader = new SAXReader(); } @Test public void testConstructLoginXML(){ try { String xml = XMLUtil.constructLoginXML(\"lisi\"); Document document = saxReader.read(new StringReader(xml)); Element root = document.getRootElement(); String rootName = root.getName(); Element typeElement = root.element(\"type\"); Element userElement = root.element(\"user\"); Assert.assertEquals(\"message\ Assert.assertEquals(\"1\ Assert.assertEquals(\"lisi\ } catch (Exception e) { e.printStackTrace(); Assert.fail(); } } }

18

第6章 系统测试

1)测试用户注册

2)测试聊天功能

19

第7章 总结

本系统采用C/S模式,聊天室页面简洁本系统可以实现通讯系统最基本的功能是双方能够互相收发信息。

这次设计用到了,Swing, Socket,Struts2,JDBC,Thread技术,采用MySQL作为数据库,MyEclipse作为开发工具。

重要的技术点解析

Socket程序的工作过程:

1、建立Socket连接:在通信开始之前由通信双方确认身份,建立一条专用的虚拟连接通道。

2、数据通信:利用虚拟连接通道传送数据信息进行通信。 3、关闭:通信结束时,再将所建的虚拟连接拆除。 具体如下: 服务器

20

服务器端应当建立一个ServerSocket,并且不断进行侦听是否有客户端连接或者断开连接(包括判断没有响应的连接超时)。服务器端应当是一个信息发送中心,所有客户端的信息都传到服务器端,由服务器端根据要求分发信息。 聊天系统工作原理图

21

22

发送消息活动图

23

开始填写用户信息存入数据库显示账号结束

用户注册流程图

24

因篇幅问题不能全部显示,请点此查看更多更全内容

Top