引言:这个程序是自从学习java以来写的第一个比较大的图形界面程序,花费了大约一周的时间,作为期末的课程设计,也算是基本上完成了任务,当然由于作者的编程能力有限,代码中难免存在BUG,不太简练,今天搬到博客上来,也算是对自己学习历程的一段记录吧,当然,如果我的这篇博客有幸被诸君看到,欢迎提出意见和建议,这篇博客仅仅作为我的一个记录吧,如果可能(估计也没时间了),将在后续版本中不断完善。所有源代码移步:http://download.youkuaiyun.com/detail/chengshijian2015/9724315
1设计要求
设计并实现一个基于TCP协议或者UDP协议的文字聊天软件,具体实现可以实现成类似于腾讯QQ式的,用户端通过登录服务器找到彼此,用户端通信时采用点对点,不用通过服务器转发,通信双方都可以主动发起连接或主动通信。
要求:
1、采用Windows应用程序风格,功能完整,显示直观,色彩协调,界面友好,操作简单。
2、设置在线用户列表,显示在线用户,并可以选择在线列表中的用户进行聊天。
3、用户端与服务器通信采用TCP协议;用户端与用户端通信采用UDP协议。
4、 一个用户端可以同时和多个其他用户端聊天。
2设计原理和技术
2.1 多线程
很多应用程序都存在对并发性的需求。例如,一个图形界面的应用程序希望在处理长时间任务的同时能快速响应用户的鼠标和键盘事件,一个在因特网上运行的服务程序希望能同时处理大量的客户请求。多线程(multithreading)为处理程序中的并发性提供了强有力的工具。
本程序主要通过两种方法创建线程:实现runnable接口,并重写里面的run()方法和直接继承Thread类,并重写里面的run(),实现多线程,其中实现Runnable接口时,亦主要是通过两种方法创建线程:直接创建类实现Runnable接口和直接在程序中创建一个继承式的匿名内部类,创建一个线程。
值得注意的是,在JVM环境中,用户程序以线程的形式来运行。当所有的非守护线程结束时,程序也就停止了。
2.2 图形用户界面
Java语言图形用户界面(Graphics User Interface,GUI)是实现用户与程序之间交互的一个控制面板,包含菜单、组件(或控件)。通过这种直观的图形界面接收用户的输入,显示程序运行结果。Java GUI从最初的抽象窗口工具包AWT(Abstract Window ToolKit),到后来的Swing组件,现在还有MyEclipse的SWT(Standard widget Toolkit)及构件基于SWT的JFace,满足不同时期、不同应用市场的需求。
2.3 网络编程
网络应用是Java语言取得成功的领域之一。Java提供了java.net包,其中包括一系列网络类,支持Java程序直接发送与接收TCP或UDP数据报。程序员使用这些类可轻松实现基于TCP和UDP的编程。
UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的特点,所以通常音频、视频和普通数据在传输中使用UDP较多,因为它们偶尔丢失一两个数据包,也不会对接收结果产生太大影响,所以本程序客户端与客户端的之间使用UDP传输。
TCP协议能为应用程序提供可靠的通信连接使一台计算机发出的字节流无差错的发送到其他电脑,对于可靠性要求高的数据通信往往使用TCP通信,所以本程序客户端与服务器端之间使用TCP传输。
3总体设计
本程序是一个模仿当前最流行的聊天软件QQ而设计的简单聊天软件。本程序采用面向对象程序分析设计方法,采用Java语言实现,设计了一个服务器端,和一个客户端,其中服务器与客户端之间采用TCP协议通信,客户端与客户端之间采用UDP协议通信,利用了Java的多线程技术编写而成。
程序主要采用14个Java类实现,共放在6个不同的包中。
程序中包和类的组织结构如图3-1所示:
图3-1 包和类的组织结构
————————————–所有的源代码————————————–
3详细设计及实现
3.1服务器端
3.1.1服务器主类ChatRoomServerMain
package server;
public class ChatRoomServerMain
{
/**
* @author CSJ
*/
public static void main(String[] args)
{
new ChatRoomServerFrame();
}
}
3.1.2服务器框架类ChatRoomServerFrame
package server;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import stream.ServerClientConnectionStream;
import user.UserInformation;
public class ChatRoomServerFrame extends JFrame
{
/**
* @author CSJ
*/
private static final long serialVersionUID = 1L;
private Map<String, ServerClientConnectionStream> userMap;
private Set<UserInformation> userSet;
private ServerSocket serverSocket;
private JButton start;
private JLabel welcome;
private JTextArea hintInfo;
private JTextArea onlineUserInfo;
private JTextArea startInfoTextArea;
public ChatRoomServerFrame()
{
super();
userMap = new HashMap<String, ServerClientConnectionStream>();
userSet = new HashSet<UserInformation>();
welcome = new JLabel("服务器端");
hintInfo = new JTextArea("当前状态:服务器未启动!\n");
startInfoTextArea = new JTextArea();
startInfoTextArea.setEditable(false);
hintInfo.setEditable(false);
onlineUserInfo = new JTextArea();
onlineUserInfo.setEditable(false);
createFrame();
addEventHandler();
}
private void createFrame()
{
start = new JButton("启动服务器");
JTabbedPane jTabbedPane = new JTabbedPane(JTabbedPane.TOP);
JScrollPane hintPanel = new JScrollPane(hintInfo);
JScrollPane onlineUserInfoPanel = new JScrollPane(onlineUserInfo);
JScrollPane startInfoScrollPanel = new JScrollPane(startInfoTextArea);
JPanel welcomePanel = new JPanel();
welcomePanel.add(welcome);
JPanel southPanel = new JPanel();
southPanel.add(start);
jTabbedPane.add("状态", hintPanel);
jTabbedPane.add("运行详情", startInfoScrollPanel);
jTabbedPane.add("在线用户详情", onlineUserInfoPanel);
setSize(400, 600);
setVisible(true);
add(welcomePanel, BorderLayout.NORTH);
add(jTabbedPane, BorderLayout.CENTER);
add(southPanel, BorderLayout.SOUTH);
Toolkit toolkit = Toolkit.getDefaultToolkit();
Dimension screenSize = toolkit.getScreenSize();
int height = getHeight();
int width = getWidth();
int screenWidth = screenSize.width / 2;
int screenHeight = screenSize.height / 2;
setLocation(screenWidth - width / 2, screenHeight - height / 2);
}
public void addEventHandler()
{
start.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent arg0)
{
try
{
serverSocket = new ServerSocket(9000);
new Thread(new Runnable()
{
@Override
public void run()
{
startServer();
}
}).start();
JOptionPane.showMessageDialog(null, "服务器已启动!");
} catch (IOException e)
{
JOptionPane.showMessageDialog(null, "服务器已启动!");
e.printStackTrace();
}
}
});
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e)
{
int t = JOptionPane.showConfirmDialog(null, "确认要退出服务器吗?", "确认退出", JOptionPane.OK_CANCEL_OPTION);
if (t == JOptionPane.OK_OPTION)
{
System.exit(0);
}
}
});
}
private void startServer()
{
while (true)
{
try
{
System.out.println("开始监听客户端连接...");
startInfoTextArea.append("开始监听客户端连接...\n");
printServerInfo();
Socket socket = serverSocket.accept();
startInfoTextArea.append("已从客户端IP:" + socket.getInetAddress().getHostAddress() + ",端口号(TCP):"
+ socket.getPort() + "接收到数据...\n");
new Thread(new ChatRoomServerThread(new ServerClientConnectionStream(socket), userSet, userMap,
hintInfo, startInfoTextArea, onlineUserInfo)).start();
} catch (IOException e)
{
e.printStackTrace();
}
} // while
}
private void printServerInfo()
{
hintInfo.setText("当前状态:开始监听客户端连接...");
try
{
hintInfo.append("\n服务器名称:" + InetAddress.getLocalHost().getHostName());
hintInfo.append("\n服务器IP地址:" + InetAddress.getLocalHost().getHostAddress());
hintInfo.append("\n监听端口:9000");
hintInfo.append("\n当前在线人数:" + userMap.size());
} catch (UnknownHostException e)
{
e.printStackTrace();
}
}
}
3.1.3服务器线程类ChatRoomServerThread
package server;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Set;
import javax.swing.JTextArea;
import stream.ServerClientConnectionStream;
import user.UserInformation;
public class ChatRoomServerThread implements Runnable
{
/**
* @author CSJ
*/
private ServerClientConnectionStream userCS;
private UserInformation userInfo;
private Set<UserInformation> userSet;
private Map<String, ServerClientConnectionStream> userMap;
private StringBuffer userList;
private JTextArea hintInfo;
private JTextArea startInfoTextArea;
private JTextArea onlineUserInfo;
public ChatRoomServerThread(ServerClientConnectionStream userCS, Set<UserInformation> userSet,
Map<String, ServerClientConnectionStream> userMap, JTextArea hintInfo, JTextArea startInfoTextArea,
JTextArea onlineUserInfo)
{
super();
this.userCS = userCS;
this.userSet = userSet;
this.userMap = userMap;
this.hintInfo = hintInfo;
this.startInfoTextArea = startInfoTextArea;
this.onlineUserInfo = onlineUserInfo;
userList = new StringBuffer();
userInfo = new UserInformation();
}
@Override
public void run()
{
while (true)
{
String message = userCS.read();
if (message.indexOf("%LOGIN%") == 0)
{
userInfo.setInfo(message);
// 标志 IP 端口号 姓名 账号 密码
if (userMap.containsKey(userInfo.getAccount()))
{
userCS.send("LOGIN_FIAL");
} else if (userInfo.getName().equals(""))
{
userCS.send("NAME_IS_NULL");
} else
{
userMap.put(userInfo.getAccount(), userCS);
userSet.add(userInfo);
userCS.send("LOGIN_SUCESSFULLY");
startInfoTextArea.append("客户端账号:" + userInfo.getAccount() + "登陆服务器登陆成功!\n");
userList.append(userInfo.getAccount() + "&LOGIN:");
sendCurrentUserList();
}
} else if (message.indexOf("%EXIT%") == 0)
{
startInfoTextArea.append("服务器收到客户端账号:" + userInfo.getAccount() + "的退出请求...\n");
userMap.remove(userInfo.getAccount());
userSet.remove(userInfo);
userList.append(userInfo.getAccount() + "&EXIT:");
sendCurrentUserList();
startInfoTextArea.append("已向客户端账号:" + userInfo.getAccount() + "发送了用户列表消息!\n");
break;
}
}
if (userCS != null)
{
userCS.close();
}
}
public void sendCurrentUserList()
{
System.out.println("正在向客户端发送用户列表消息...");
onlineUserInfo.setText("");
for (UserInformation userInfo : userSet)
{
onlineUserInfo.append("账号:" + userInfo.getAccount() + ",密码:" + userInfo.getPassword() + ",昵称:"
+ userInfo.getName() + ",IP地址:" + userInfo.getIP() + ",端口号(UDP):" + userInfo.getPort() + "\n");
userList.append(userInfo.getIP() + "-" + userInfo.getPort() + "-" + userInfo.getAccount() + "-"
+ userInfo.getName() + "-" + userInfo.getUserPortraitNum() + ",");
}
for (String string : userMap.keySet())
{
userMap.get(string).send("%USER_LIST%:" + userList);
startInfoTextArea.append("服务器向" + string + "发送的用户列表为:%USER_LIST%:" + userList + "\n");
}
startInfoTextArea.append("已向客户端账号:" + userInfo.getAccount() + "发送了用户列表消息!\n");
userList.delete(0, userList.length());
printServerInfo();
System.out.println("已更新了服务器状态信息!");
startInfoTextArea.append("已更新了服务器状态信息!\n");
}
private void printServerInfo()
{
hintInfo.setText("当前状态:开始监听客户端连接...");
try
{
hintInfo.append("\n服务器名称:" + InetAddress.getLocalHost().getHostName());
hintInfo.append("\n服务器IP地址:" + InetAddress.getLocalHost().getHostAddress());
hintInfo.append("\n监听端口:9000");
hintInfo.append("\n当前在线人数:" + userMap.size());
} catch (UnknownHostException e)
{
e.printStackTrace();
}
}
}
3.1.4服务器到客户端连接流类ServerClientConnectionStream
package stream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class ServerClientConnectionStream
{
/**
* @author CSJ
*/
private Socket socket;
private BufferedReader reader;
private PrintWriter writer;
public ServerClientConnectionStream(Socket socket)
{
super();
this.socket = socket;
getStream();
}
private void getStream()
{
try
{
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new PrintWriter(socket.getOutputStream());
} catch (IOException e)
{
e.printStackTrace();
}
}
public void close()
{
if (socket != null)
{
try
{
socket.close();
} catch (IOException e)
{
e.printStackTrace();
}
}
}
public void send(String message)
{
writer.println(message);
writer.flush();
}
public String read()
{
try
{
return reader.readLine();
} catch (IOEx