Java聊天室课程设计
一、 需求分析
1.1开发背景
在信息化社会的今天,网络飞速发展,人们对网络的依赖越来越多,越来越离不开网络,由此而产生的聊天工具越来越多,类似MSN、QQ,网络聊天时一类的聊天系统的发展日新月异,因此产生了制作一个类似QQ的网络聊天工具的想法,且通过制作该程序还能更好的学习网络软件编程知识。
网络编程的目的就是指直接或间接地通过网络协议与其他计算机进行通讯。网编程中有两个主要的问题,一个是如何准确的定位网络上一台或多台主机,另一个就是找到主机后如何可靠高效的进行数据传输。在TCP/IP协议中IP层主要负责网络主机的定位,数据传输的路由,由IP地址可以唯一地确定Internet上的一台主机。而TCP层则提供面向应用的可靠的或非可靠的数据传输机制,这是网络编程的主要对象,一般不需要关心IP层是如何处理数据的。目前较为流行的网络编程模型是客户机/服务器(C/S)结构。即通信双方一方作为服务器等待客户提出请求并予以响应。客户则在需要服务时向服务器提出申请。服务器一般作为守护进程始终运行,监听网络端口,一旦有客户请求,就会启动一个服务进程来响应该客户,同时自己继续监听服务端口,使后来的客户也得到响应的服务。
1.2设计要求
本课程设计的目标是利用套接字socket()设计一个聊天程序,该程序基于C/S模式,客户机器向服务器发聊天请求,服务器应答并能显示客户机发过来的信息。
1.3设计目的
通过设计一个网络聊天程序,对套接字、数据报通讯、URL、与URLConnectiom的相关知识有详细的了解和充分的认识。能将相关的只是运用到相关的实践中去。
1.4功能实现
聊天室共分为客户端和服务端两部分,服务器程序主要负责侦听客户端发来的消息,客户端需要登录到相应的服务器才可以实现正常的聊天功能。
服务器的主要功能有
1) 在特定端口上进行侦听,等待客户连接
2) 用户可以配置服务器的真挺端口
3) 向已经连接服务器的客户发送系统消息
4) 统计在线人数
5) 当停止服务时,断开所有用户的连接
客户端的主要功能
1) 连接到已经开启聊天服务的服务端
2) 用户可以配置要连接服务器端的ip地址和端口号
3) 用户可以配置连接后显示的用户名
4) 当服务器开启时。用户可以随时登陆和注销
5) 用户可以向所有人或一个人发送消息
二、 总体设计
2.1设计思想
套接字对象在网络编程中扮演者重要的角色,可以用套接字技术编写一个聊天室,服务器为每个客户启动一个线程。在该线程中通过套接字和客户交流信息,当客户向服务器发送一条聊天信息“大家好”时,服务器要让所有的这些线程中的输入流写入信息大家好,这样所有的客户的套接字的输入流就都读取到了这一条信息。如果把信息“你好”发送给特定用户,服务器就让特定线程中的输出流写入信息“你好”,那么只有特定客户的套机字的输入流可以读取到这条信息。
在聊天室中需要对用户上线下线的状态进行修改,进而统计在线人数、查找某用户等。因而需要用到java链表来实现。由于Java语言不像c或c++一样可以利用线性表的链式存储结构,用节点和指针来表示,在Java中是没有指针的,但是可以通过使用对象的引用来实现链表。链表的结点个数称为链表的长度。因此在Java中可以定义两个类来实现链表的操作,分别为节点类和链表类。在本设计中对用户的存储就是利用链表来实现的。
2.2基本设计概念和处理流程
本系统运行用JAVA开发,采用C\S结构,系统分为客服端和服务端两大模块,使用Socket类建立套接字实现客服端和服务端得通讯。
处理流程
服务端 客户端
一个ServerSocket对象和一个Socket对象 一个Socketd对象
TCP Socket通信流程
2.3总体结构
2.4功能分配
客户端
服务端
2.5接口设计
2.5.1用户接口
提供一个用户操作界面:包括用户可以再界面中登录聊天室、输入消息、浏览聊天内容和聊天对象。
同时提供一个服务器操作界面,通过服务器操作界面可以修改服务器的配置,知道用户的当前状态,并可以给用户发送指定系统信息。
2.5.2内部接口
Socket(string hont ,int port );
客户端使用Socket类建立与服务器的套接字连接。
ServerSocket(int port);
建立接收客户的套接字的服务器套接字。
2.6主要模块
2.6.1聊天室服务器端模块
聊天室服务器端模块主要有以下几部分组成
1、 主框架类(ChatServer.java)
该文件包含名为ChatServer的public类,其主要功能是定义服务器的界面,添加事件侦听鱼事件处理。ChatServer类调用ServerListen类来实现服务端用户上线与下线的侦听,调用ServerReceive类来实现服务器端消息的转发。
2、 服务器用户上线与下线侦听类。
该类对用户上线与下线的侦听是通过调用用户链表类来实现的,当用户的上线与下线情况发生改变时,该类会对主类的界面进行相应的修改。
3、 服务器消息收发类
该类分别定义了向某用户及向所有人发送消息的方法,发送的消息会显示在主界面类的界面上。
4用户修改配置的类。
该类继承自JDialog。使用户对服务器端口进行修改配置的类。
5节点类
定义了链表中的用户
6链表类
该类通过构造函数构造用户链表,定义了添加用户、删除用户、返回用户数、根据用户名查找用户和各根据索引查找用户等方法。
7服务器帮助类、
2.6.2聊天室服务器端模块算法描述
服务器端的ChatServer类继承自JFrame并实现相应的事件监听接口,因此它定义了服务器的主框架,及各个按钮的事件监听。它分别调用ServerListen类来实现服务端用户上线与下线的侦听,调用ServerReceive类来实现服务器端消息的转发。同时服务器可以响应多个客户的请求,当一个客户发送请求时,服务器就为它增加一个线程,同时服务器利用UserLinkList类为客户端设置一个请求队列,如果服务器不能马上响应客户端的请求,就要把这个请求放到请求队列在中,,等服务器将当前的请求处理完,会自动到请求队列中按照先后顺序取出请求进行处理。
2.6.3聊天室客户端模块
客户端主要有以下几个文件,功能如下:
1客户端主框架类
该类主要定义客户端的界面,添加事件侦听与事件处理。该类定义了与服务器实现连接与断开连接的方法。当用户登录到指定的服务器上时,该类条用客户端实现消息收发的类实现消息的收发。同时该类定义了向所有用户发送消息的方法。
2客户端消息收发的类
该类实现了服务器与客户端消息的收发
3用户修改配置的类
该类继承自JDialog,是用户对要连接的服务器IP及侦听端口进行修改配置的类。
4帮助类
客户端用户程序的帮助类
2.6.4聊天室客户端模块主要算法描述
客户端ChatClient类继承了JFrame并实现相应的事件监听接口。它实现了客户端的主界面及相应按钮的事件侦听。该类调用ClientReceive类实现消息的收发。同时该类定义了向所有用户发送消息的方法。通过相应的输入输出流与服务器进行数据的传递与交流。
三、 主要代码实现
3.1服务器主要代码实现
1、Chatserver类:包含名为ChatServer的public类,其主要功能为定义服务器端的界面,添加事件侦听与事件处理。调用ServerListen类来实现服务端用户上线与下线的侦听,调用ServerReceive类来实现服务器端的消息的收发。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.net.*;
import java.io.*;
/*
* 聊天服务端的主框架类
*/
public class ChatServer extends JFrame implements ActionListener{
public static int port = 8888;//服务端的侦听端口
ServerSocket serverSocket;//服务端Socket
Image icon;//程序图标
JComboBox combobox;//选择发送消息的接受者
JTextArea messageShow;//服务端的信息显示
JScrollPane messageScrollPane;//信息显示的滚动条
JTextField showStatus;//显示用户连接状态
JLabel sendToLabel,messageLabel;
JTextField sysMessage;//服务端消息的发送
JButton sysMessageButton;//服务端消息的发送按钮
UserLinkList userLinkList;//用户链表
//建立菜单栏
JMenuBar jMenuBar = new JMenuBar();
//建立菜单组
JMenu serviceMenu = new JMenu ("服务(V)");
//建立菜单项
JMenuItem portItem = new JMenuItem ("端口设置(P)");
JMenuItem startItem = new JMenuItem ("启动服务(S)");
JMenuItem stopItem=new JMenuItem ("停止服务(T)");
JMenuItem exitItem=new JMenuItem ("退出(X)");
JMenu helpMenu=new JMenu ("帮助(H)");
JMenuItem helpItem=new JMenuItem ("帮助(H)");
//建立工具栏
JToolBar toolBar = new JToolBar();
//建立工具栏中的按钮组件
JButton portSet;//启动服务端侦听
JButton startServer;//启动服务端侦听
JButton stopServer;//关闭服务端侦听
JButton exitButton;//退出按钮
//框架的大小
Dimension faceSize = new Dimension(400, 600);
ServerListen listenThread;
JPanel downPanel ;
GridBagLayout girdBag;
GridBagConstraints girdBagCon;
/**
* 服务端构造函数
*/
public ChatServer(){
init();//初始化程序
//添加框架的关闭事件处理
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
//设置框架的大小
this.setSize(faceSize);
//设置运行时窗口的位置
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation( (int) (screenSize.width - faceSize.getWidth()) / 2,
(int) (screenSize.height - faceSize.getHeight()) / 2);
this.setResizable(false);
this.setTitle("聊天室服务端"); //设置标题
//程序图标
icon = getImage("icon.gif");
this.setIconImage(icon); //设置程序图标
show();
//为服务菜单栏设置热键'V'
serviceMenu.setMnemonic('V');
//为端口设置快捷键为ctrl+p
portItem.setMnemonic ('P');
portItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_P,InputEvent.CTRL_MASK));
//为启动服务快捷键为ctrl+s
startItem.setMnemonic ('S');
startItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_S,InputEvent.CTRL_MASK));
//为端口设置快捷键为ctrl+T
stopItem.setMnemonic ('T');
stopItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_T,InputEvent.CTRL_MASK));
//为退出设置快捷键为ctrl+x
exitItem.setMnemonic ('X');
exitItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_X,InputEvent.CTRL_MASK));
//为帮助菜单栏设置热键'H'
helpMenu.setMnemonic('H');
//为帮助设置快捷键为ctrl+p
helpItem.setMnemonic ('H');
helpItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_H,InputEvent.CTRL_MASK));
}
/**
* 程序初始化函数
*/
public void init(){
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
//添加菜单栏
serviceMenu.add (portItem);
serviceMenu.add (startItem);
serviceMenu.add (stopItem);
serviceMenu.add (exitItem);
jMenuBar.add (serviceMenu);
helpMenu.add (helpItem);
jMenuBar.add (helpMenu);
setJMenuBar (jMenuBar);
//初始化按钮
portSet = new JButton("端口设置");
startServer = new JButton("启动服务");
stopServer = new JButton("停止服务" );
exitButton = new JButton("退出" );
//将按钮添加到工具栏
toolBar.add(portSet);
toolBar.addSeparator();//添加分隔栏
toolBar.add(startServer);
toolBar.add(stopServer);
toolBar.addSeparator();//添加分隔栏
toolBar.add(exitButton);
contentPane.add(toolBar,BorderLayout.NORTH);
//初始时,令停止服务按钮不可用
stopServer.setEnabled(false);
stopItem .setEnabled(false);
//为菜单栏添加事件监听
portItem.addActionListener(this);
startItem.addActionListener(this);
stopItem.addActionListener(this);
exitItem.addActionListener(this);
helpItem.addActionListener(this);
//添加按钮的事件侦听
portSet.addActionListener(this);
startServer.addActionListener(this);
stopServer.addActionListener(this);
exitButton.addActionListener(this);
combobox = new JComboBox();
combobox.insertItemAt("所有人",0);
combobox.setSelectedIndex(0);
messageShow = new JTextArea();
messageShow.setEditable(false);
//添加滚动条
messageScrollPane = new JScrollPane(messageShow,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
messageScrollPane.setPreferredSize(new Dimension(400,400));
messageScrollPane.revalidate();
showStatus = new JTextField(35);
showStatus.setEditable(false)
sysMessage = new JTextField(24);
sysMessage.setEnabled(false);
sysMessageButton = new JButton();
sysMessageButton.setText("发送");
//添加系统消息的事件侦听
sysMessage.addActionListener(this);
sysMessageButton.addActionListener(this);
sendToLabel = new JLabel("发送至:");
messageLabel = new JLabel("发送消息:");
downPanel = new JPanel();
girdBag = new GridBagLayout();
downPanel.setLayout(girdBag);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 0;
girdBagCon.gridwidth = 3;
girdBagCon.gridheight = 2;
girdBagCon.ipadx = 5;
girdBagCon.ipady = 5;
JLabel none = new JLabel(" ");
girdBag.setConstraints(none,girdBagCon);
downPanel.add(none);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 2;
girdBagCon.insets = new Insets(1,0,0,0);
girdBagCon.ipadx = 5;
girdBagCon.ipady = 5;
girdBag.setConstraints(sendToLabel,girdBagCon);
downPanel.add(sendToLabel);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx =1;
girdBagCon.gridy = 2;
girdBagCon.anchor = GridBagConstraints.LINE_START;
girdBag.setConstraints(combobox,girdBagCon);
downPanel.add(combobox);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 3;
girdBag.setConstraints(messageLabel,girdBagCon);
downPanel.add(messageLabel);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 1;
girdBagCon.gridy = 3;
girdBag.setConstraints(sysMessage,girdBagCon);
downPanel.add(sysMessage);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 2;
girdBagCon.gridy = 3;
girdBag.setConstraints(sysMessageButton,girdBagCon);
downPanel.add(sysMessageButton);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 4;
girdBagCon.gridwidth = 3;
girdBag.setConstraints(showStatus,girdBagCon);
downPanel.add(showStatus);
contentPane.add(messageScrollPane,BorderLayout.CENTER);
contentPane.add(downPanel,BorderLayout.SOUTH);
//关闭程序时的操作
this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
stopService();
System.exit(0);
}}}}
/**
* 事件处理
*/
public void actionPerformed(ActionEvent e) {
Object obj = e.getSource();
if (obj == startServer || obj == startItem) { //启动服务端
startService();
}
else if (obj == stopServer || obj == stopItem) { //停止服务端
int j=JOptionPane.showConfirmDialog(
this,"真的停止服务吗?","停止服务",
JOptionPane.YES_OPTION,JOptionPane.QUESTION_MESSAGE);
if (j == JOptionPane.YES_OPTION){
stopService();
}
}
else if (obj == portSet || obj == portItem) { //端口设置
//调出端口设置的对话框
PortConf portConf = new PortConf(this);
portConf.show();
}
else if (obj == exitButton || obj == exitItem) { //退出程序
int j=JOptionPane.showConfirmDialog(
this,"真的要退出吗?","退出",
JOptionPane.YES_OPTION,JOptionPane.QUESTION_MESSAGE);
if (j == JOptionPane.YES_OPTION){
stopService();
System.exit(0);
}
}
else if (obj == helpItem) { //菜单栏中的帮助
//调出帮助对话框
Help helpDialog = new Help(this);
helpDialog.show();
}
else if (obj == sysMessage || obj == sysMessageButton) { //发送系统消息
sendSystemMessage();
}
}
/**
* 启动服务端
*/
public void startService(){
try{
serverSocket = new ServerSocket(port,10);
messageShow.append("服务端已经启动,在"+port+"端口侦听...\n");
startServer.setEnabled(false);
startItem.setEnabled(false);
portSet.setEnabled(false);
portItem.setEnabled(false);
stopServer .setEnabled(true);
stopItem .setEnabled(true);
sysMessage.setEnabled(true);
}
catch (Exception e){
//System.out.println(e);
}
userLinkList = new UserLinkList();
listenThread = new ServerListen(serverSocket,combobox,
messageShow,showStatus,userLinkList);
listenThread.start();
}
/**
* 关闭服务端
*/
public void stopService(){
try{
//向所有人发送服务器关闭的消息
sendStopToAll();
listenThread.isStop = true;
serverSocket.close();
int count = userLinkList.getCount();
int i =0;
while( i < count){
Node node = userLinkList.findUser(i);
node.input .close();
node.output.close();
node.socket.close();
i ++;
}
stopServer .setEnabled(false);
stopItem .setEnabled(false);
startServer.setEnabled(true);
startItem.setEnabled(true);
portSet.setEnabled(true);
portItem.setEnabled(true);
sysMessage.setEnabled(false);
messageShow.append("服务端已经关闭\n");
combobox.removeAllItems();
combobox.addItem("所有人");
}
catch(Exception e){
//System.out.println(e);
}
}
/**
* 向所有人发送服务器关闭的消息
*/
public void sendStopToAll(){
int count = userLinkList.getCount();
int i = 0;
while(i < count){
Node node = userLinkList.findUser(i);
if(node == null) {
i ++;
continue;
}
try{
node.output.writeObject("服务关闭");
node.output.flush();
}
catch (Exception e){
//System.out.println("$$$"+e);
}
i++;
}
}
/**
* 向所有人发送消息
*/
public void sendMsgToAll(String msg){
int count = userLinkList.getCount();//用户总数
int i = 0;
while(i < count){
Node node = userLinkList.findUser(i);
if(node == null) {
i ++;
continue;
}
try{
node.output.writeObject("系统信息");
node.output.flush();
node.output.writeObject(msg);
node.output.flush();
}
catch (Exception e){
//System.out.println("@@@"+e);
}
i++;
}
sysMessage.setText("");
}
/**
* 向客户端用户发送消息
*/
public void sendSystemMessage(){
String toSomebody = combobox.getSelectedItem().toString();
String message = sysMessage.getText() + "\n";
messageShow.append(message);
//向所有人发送消息
if(toSomebody.equalsIgnoreCase("所有人")){
sendMsgToAll(message);
}
else{
//向某个用户发送消息
Node node = userLinkList.findUser(toSomebody);
try{
node.output.writeObject("系统信息");
node.output.flush();
node.output.writeObject(message);
node.output.flush();
}
catch(Exception e){
//System.out.println("!!!"+e);
}
sysMessage.setText("");//将发送消息栏的消息清空
}
/**
* 通过给定的文件名获得图像
*/
Image getImage(String filename) {
URLClassLoader urlLoader = (URLClassLoader)this.getClass().
getClassLoader();
URL url = null;
Image image = null;
url = urlLoader.findResource(filename);
image = Toolkit.getDefaultToolkit().getImage(url);
MediaTracker mediatracker = new MediaTracker(this);
try {
mediatracker.addImage(image, 0);
mediatracker.waitForID(0);
}
catch (InterruptedException _ex) {
image = null;
}
if (mediatracker.isErrorID(0)) {
image = null;
}
return image;
}
public static void main(String[] args) {
ChatServer app = new ChatServer();
}
}
(2)ServerListen.java:该类实现服务端用户上线与下线的侦听。该类对用户上线下线的侦听是通过调用用户链表类(UserLinkList)来实现的,当用户上线与下线情况发生变化时,该类会对主类的界面进行相应的修改。
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
import java.io.*;
import java.net.*;
/*
* 服务端的侦听类
*/
public class ServerListen extends Thread {
ServerSocket server;
JComboBox combobox;
JTextArea textarea;
JTextField textfield;
UserLinkList userLinkList;//用户链表
Node client;
ServerReceive recvThread;
public boolean isStop;
/*
* 聊天服务端的用户上线于下线侦听类
*/
public ServerListen(ServerSocket server,JComboBox combobox,
JTextArea textarea,JTextField textfield,UserLinkList userLinkList){
this.server = server;
this.combobox = combobox;
this.textarea = textarea;
this.textfield = textfield;
this.userLinkList = userLinkList;
isStop = false;
}
public void run(){
while(!isStop && !server.isClosed()){
try{
client = new Node();
client.socket = server.accept();
client.output = new ObjectOutputStream(client.socket.getOutputStream());
client.output.flush();
client.input = new ObjectInputStream(client.socket.getInputStream());
client.username = (String)client.input.readObject();
//显示提示信息
combobox.addItem(client.username);
userLinkList.addUser(client);
textarea.append("用户 " + client.username + " 上线" + "\n");
textfield.setText("在线用户" + userLinkList.getCount() + "人\n");
recvThread = new ServerReceive(textarea,textfield,
combobox,client,userLinkList);
recvThread.start();
}
catch(Exception e){
}
}
}
}
(3)ServerReceive.java:该类是实现服务器端的消息的收发的类。该类分别定义了向某用户及所有人发送消息的方法,发送的消息会显示在主界面类的界面上。
import javax.swing.*;
import java.io.*;
import java.net.*;
/*
* 服务器收发消息的类
*/
public class ServerReceive extends Thread {
JTextArea textarea;
JTextField textfield;
JComboBox combobox;
Node client;
UserLinkList userLinkList;//用户链表
public boolean isStop;
public ServerReceive(JTextArea textarea,JTextField textfield,
JComboBox combobox,Node client,UserLinkList userLinkList){
this.textarea = textarea;
this.textfield = textfield;
this.client = client;
this.userLinkList = userLinkList;
this.combobox = combobox;
isStop = false;
}
public void run(){
//向所有人发送用户的列表
sendUserList();
while(!isStop && !client.socket.isClosed()){
try{
String type = (String)client.input.readObject();
if(type.equalsIgnoreCase("聊天信息")){
String toSomebody = (String)client.input.readObject();
String status = (String)client.input.readObject();
String action = (String)client.input.readObject();
String message = (String)client.input.readObject();
String msg = client.username
+" "+ action
+ "对 "
+ toSomebody
+ " 说 : "
+ message
+ "\n";
if(status.equalsIgnoreCase("悄悄话")){
msg = " [悄悄话] " + msg;
}
textarea.append(msg);
if(toSomebody.equalsIgnoreCase("所有人")){
sendToAll(msg);//向所有人发送消息
}
else{
try{
client.output.writeObject("聊天信息");
client.output.flush();
client.output.writeObject(msg);
client.output.flush();
}
catch (Exception e){
//System.out.println("###"+e);
}
Node node = userLinkList.findUser(toSomebody);
if(node != null){
node.output.writeObject("聊天信息");
node.output.flush();
node.output.writeObject(msg);
node.output.flush();
}
}
}
else if(type.equalsIgnoreCase("用户下线")){
Node node = userLinkList.findUser(client.username);
userLinkList.delUser(node);
String msg = "用户 " + client.username + " 下线\n";
int count = userLinkList.getCount();
combobox.removeAllItems();
combobox.addItem("所有人");
int i = 0;
while(i < count){
node = userLinkList.findUser(i);
if(node == null) {
i ++;
continue;
}
combobox.addItem(node.username);
i++;
}
combobox.setSelectedIndex(0);
textarea.append(msg);
textfield.setText("在线用户" + userLinkList.getCount() + "人\n");
sendToAll(msg);//向所有人发送消息
sendUserList();//重新发送用户列表,刷新
break;
}
}
catch (Exception e){
//System.out.println(e);
}
}
}
/*
* 向所有人发送消息
*/
public void sendToAll(String msg){
int count = userLinkList.getCount();
int i = 0;
while(i < count){
Node node = userLinkList.findUser(i);
if(node == null) {
i ++;
continue;
}
try{
node.output.writeObject("聊天信息");
node.output.flush();
node.output.writeObject(msg);
node.output.flush();
}
catch (Exception e){
//System.out.println(e);
}
i++;
}
}
/*
* 向所有人发送用户的列表
*/
public void sendUserList(){
String userlist = "";
int count = userLinkList.getCount();
int i = 0;
while(i < count){
Node node = userLinkList.findUser(i);
if(node == null) {
i ++;
continue;
}
userlist += node.username;
userlist += '\n';
i++;
}
i = 0;
while(i < count){
Node node = userLinkList.findUser(i);
if(node == null) {
i ++;
continue;
}
try{
node.output.writeObject("用户列表");
node.output.flush();
node.output.writeObject(userlist);
node.output.flush();
}
catch (Exception e){
//System.out.println(e);
}
i++;
}
}
}
(4)Node.java:用户链表的节点类,定义了链表中的用户。该类与前面所讲的链表节点Node类的功能相当。
import java.net.*;
import java.io.*;
/**
* 用户链表的结点类
*/
public class Node {
String username = null;
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
Node next = null;
}
(6)UserLinkList.java:用户链表节点的具体实现类。该类通过构造函数构造用户链表,定义了添加用户,删除用户、返回用户数、根据用户名查找用户、根据索引查找用户这5个方法。
/**
* 用户链表
*/
public class UserLinkList {
Node root;
Node pointer;
int count;
/**
* 构造用户链表
*/
public UserLinkList(){
root = new Node();
root.next = null;
pointer = null;
count = 0;
}
/**
* 添加用户
*/
public void addUser(Node n){
pointer = root;
while(pointer.next != null){
pointer = pointer.next;
}
pointer.next = n;
n.next = null;
count++;
}
/**
* 删除用户
*/
public void delUser(Node n){
pointer = root;
while(pointer.next != null){if(pointer.next == n){
pointer.next = n.next;
count --;
break;
}
pointer = pointer.next;
}
}
/**
* 返回用户数
*/
public int getCount(){
return count;
}
/**
* 根据用户名查找用户
*/
public Node findUser(String username){
if(count == 0) return null;
pointer = root;
while(pointer.next != null){
pointer = pointer.next;
if(pointer.username.equalsIgnoreCase(username)){
return pointer;
}
}
return null;
}
/**
* 根据索引查找用户
*/
public Node findUser(int index){
if(count == 0) {
return null;
}
if(index < 0) {
return null;
}
pointer = root;
int i = 0;
while(i < index + 1){
if(pointer.next != null){
pointer = pointer.next;
}
else{
return null;
}
i++;
}
return pointer;
}
}
3.2客户端代码实现
(1)ChatClient.java:包含名为ChatClient的public类,其主要功能为定义客户端的界面,添加事件侦听与事件处理。该类定义了Connect()与DisConnect()方法实现与服务器的连接与断开连接。当登录到指定的服务器时,调用ClientReceive类实现消息收发,同时该类还定义了SendMessage()方法来向其他用户发送带有表情的消息或者悄悄话。
//package Client;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.io.*;
import java.net.*;
/*
* 聊天客户端的主框架类
*/
public class ChatClient extends JFrame implements ActionListener{
private static final long serialVersionUID = 1L;
String ip = "127.0.0.1";//连接到服务端的ip地址
int port = 8888;//连接到服务端的端口号
String userName = "匆匆过客";//用户名
int type = 0;//0表示未连接,1表示已连接
JComboBox combobox;//选择发送消息的接受者
JTextArea messageShow;//客户端的信息显示
JScrollPane messageScrollPane;//信息显示的滚动条
JLabel express,sendToLabel,messageLabel ;
JTextField clientMessage;//客户端消息的发送
JCheckBox checkbox;//悄悄话
JComboBox actionlist;//表情选择
JButton clientMessageButton;//发送消息
JTextField showStatus;//显示用户连接状态
Socket socket;
ObjectOutputStream output;//网络套接字输出流
ObjectInputStream input;//网络套接字输入流
ClientReceive recvThread;
//建立菜单栏
JMenuBar jMenuBar = new JMenuBar();
//建立菜单组
JMenu operateMenu = new JMenu ("操作(O)");
//建立菜单项
JMenuItem loginItem = new JMenuItem ("用户登录(I)",new ImageIcon("face/98.gif"));
JMenuItem logoffItem = new JMenuItem ("用户注销(L)",new ImageIcon("face/icon.gif"));
JMenuItem exitItem=new JMenuItem ("退出(X)",new ImageIcon("face/smile.gif"));
JMenu conMenu=new JMenu ("设置(C)");
JMenuItem userItem=new JMenuItem ("用户设置(U)",new ImageIcon("face/messenger_big.gif"));
JMenuItem connectItem=new JMenuItem ("连接设置(C)",new ImageIcon("face/Uabrand.gif"));
JMenu helpMenu=new JMenu ("帮助(H)");
JMenuItem helpItem=new JMenuItem ("帮助(H)",new ImageIcon("face/HelpCenter.gif"));
//建立工具栏
JToolBar toolBar = new JToolBar();
//建立工具栏中的按钮组件
JButton loginButton;//用户登录
JButton logoffButton;//用户注销
JButton userButton;//用户信息的设置
JButton connectButton;//连接设置
JButton exitButton;//退出按钮
//框架的大小
Dimension faceSize = new Dimension(400, 600);
JPanel downPanel ;
GridBagLayout girdBag;
GridBagConstraints girdBagCon;
public ChatClient(){
init();//初始化程序
//添加框架的关闭事件处理
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.pack();
//设置框架的大小
this.setSize(faceSize);
this.setVisible(true);
setIconImage(getToolkit().getImage("face/love.gif"));
//设置运行时窗口的位置
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
this.setLocation( (int) (screenSize.width - faceSize.getWidth()) / 2,
(int) (screenSize.height - faceSize.getHeight()) / 2);
this.setResizable(false);
this.setTitle("聊天室客户端"); //设置标题
//为操作菜单栏设置热键'V'
operateMenu.setMnemonic('O');
//为用户登录设置快捷键为ctrl+i
loginItem.setMnemonic ('I');
loginItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_I,InputEvent.CTRL_MASK));
//为用户注销快捷键为ctrl+l
logoffItem.setMnemonic ('L');
logoffItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_L,InputEvent.CTRL_MASK));
//为退出快捷键为ctrl+x
exitItem.setMnemonic ('X');
exitItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_X,InputEvent.CTRL_MASK));
//为设置菜单栏设置热键'C'
conMenu.setMnemonic('C');
//为用户设置设置快捷键为ctrl+u
userItem.setMnemonic ('U');
userItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_U,InputEvent.CTRL_MASK));
//为连接设置设置快捷键为ctrl+c
connectItem.setMnemonic ('C');
connectItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_C,InputEvent.CTRL_MASK));
//为帮助菜单栏设置热键'H'
helpMenu.setMnemonic('H');
//为帮助设置快捷键为ctrl+p
helpItem.setMnemonic ('H');
helpItem.setAccelerator (KeyStroke.getKeyStroke (KeyEvent.VK_H,InputEvent.CTRL_MASK));
}
/**
* 程序初始化函数
*/
public void init(){
Container contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
//添加菜单栏
operateMenu.add (loginItem);
operateMenu.add (logoffItem);
operateMenu.addSeparator();
operateMenu.add (exitItem);
jMenuBar.add (operateMenu);
conMenu.add (userItem);
conMenu.addSeparator();
conMenu.add (connectItem);
jMenuBar.add (conMenu);
helpMenu.add (helpItem);
jMenuBar.add (helpMenu);
setJMenuBar (jMenuBar);
//初始化按钮
loginButton = new JButton("登录");
logoffButton = new JButton("注销");
userButton = new JButton("用户设置" );
connectButton = new JButton("连接设置" );
exitButton = new JButton("退出" );
//当鼠标放上显示信息
loginButton.setToolTipText("连接到指定的服务器");
logoffButton.setToolTipText("与服务器断开连接");
userButton.setToolTipText("设置用户信息");
connectButton.setToolTipText("设置所要连接到的服务器信息");
//将按钮添加到工具栏
toolBar.add(userButton);
toolBar.add(connectButton);
toolBar.addSeparator();//添加分隔栏
toolBar.add(loginButton);
toolBar.add(logoffButton);
toolBar.addSeparator();//添加分隔栏
toolBar.add(exitButton);
contentPane.add(toolBar,BorderLayout.NORTH);
checkbox = new JCheckBox("悄悄话");
checkbox.setSelected(false);
actionlist = new JComboBox();
actionlist.addItem("微笑地");
actionlist.addItem("高兴地");
actionlist.addItem("轻轻地");
actionlist.addItem("生气地");
actionlist.setSelectedIndex(0);
//初始时
loginButton.setEnabled(true);
logoffButton.setEnabled(false);
//为菜单栏添加事件监听
loginItem.addActionListener(this);
logoffItem.addActionListener(this);
exitItem.addActionListener(this);
userItem.addActionListener(this);
connectItem.addActionListener(this);
helpItem.addActionListener(this);
//添加按钮的事件侦听
loginButton.addActionListener(this);
logoffButton.addActionListener(this);
userButton.addActionListener(this);
connectButton.addActionListener(this);
exitButton.addActionListener(this);
combobox = new JComboBox();
combobox.insertItemAt("所有人",0);
combobox.setSelectedIndex(0);
messageShow = new JTextArea();
messageShow.setEditable(false);
//添加滚动条
messageScrollPane = new JScrollPane(messageShow,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
messageScrollPane.setPreferredSize(new Dimension(400,400));
messageScrollPane.revalidate();
clientMessage = new JTextField(23);
clientMessage.setEnabled(false);
clientMessageButton = new JButton();
clientMessageButton.setText("发送");
//添加系统消息的事件侦听
clientMessage.addActionListener(this);
clientMessageButton.addActionListener(this);
sendToLabel = new JLabel("发送至:");
express = new JLabel(" 表情: ");
messageLabel = new JLabel("发送消息:");
downPanel = new JPanel();
girdBag = new GridBagLayout();
downPanel.setLayout(girdBag);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 0;
girdBagCon.gridwidth = 5;
girdBagCon.gridheight = 2;
girdBagCon.ipadx = 5;
girdBagCon.ipady = 5;
JLabel none = new JLabel(" ");
girdBag.setConstraints(none,girdBagCon);
downPanel.add(none);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 2;
girdBagCon.insets = new Insets(1,0,0,0);
//girdBagCon.ipadx = 5;
//girdBagCon.ipady = 5;
girdBag.setConstraints(sendToLabel,girdBagCon);
downPanel.add(sendToLabel);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx =1;
girdBagCon.gridy = 2;
girdBagCon.anchor = GridBagConstraints.LINE_START;
girdBag.setConstraints(combobox,girdBagCon);
downPanel.add(combobox);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx =2;
girdBagCon.gridy = 2;
girdBagCon.anchor = GridBagConstraints.LINE_END;
girdBag.setConstraints(express,girdBagCon);
downPanel.add(express);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 3;
girdBagCon.gridy = 2;
girdBagCon.anchor = GridBagConstraints.LINE_START;
//girdBagCon.insets = new Insets(1,0,0,0);
//girdBagCon.ipadx = 5;
//girdBagCon.ipady = 5;
girdBag.setConstraints(actionlist,girdBagCon);
downPanel.add(actionlist);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 4;
girdBagCon.gridy = 2;
girdBagCon.insets = new Insets(1,0,0,0);
//girdBagCon.ipadx = 5;
//girdBagCon.ipady = 5;
girdBag.setConstraints(checkbox,girdBagCon);
downPanel.add(checkbox);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 3;
girdBag.setConstraints(messageLabel,girdBagCon);
downPanel.add(messageLabel);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 1;
girdBagCon.gridy = 3;
girdBagCon.gridwidth = 3;
girdBagCon.gridheight = 1;
girdBag.setConstraints(clientMessage,girdBagCon);
downPanel.add(clientMessage);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 4;
girdBagCon.gridy = 3;
girdBag.setConstraints(clientMessageButton,girdBagCon);
downPanel.add(clientMessageButton);
showStatus = new JTextField(35);
showStatus.setEditable(false);
girdBagCon = new GridBagConstraints();
girdBagCon.gridx = 0;
girdBagCon.gridy = 5;
girdBagCon.gridwidth = 5;
girdBag.setConstraints(showStatus,girdBagCon);
downPanel.add(showStatus);
contentPane.add(messageScrollPane,BorderLayout.CENTER);
contentPane.add(downPanel,BorderLayout.SOUTH);
//关闭程序时的操作
this.addWindowListener(
new WindowAdapter(){
public void windowClosing(WindowEvent e){
if(type == 1){
DisConnect();
}
System.exit(0);
}
});
}
/**
* 事件处理
*/
public void actionPerformed(ActionEvent e) {
Object obj = e.getSource();
if (obj == userItem || obj == userButton) { //用户信息设置
//调出用户信息设置对话框
UserConf userConf = new UserConf(this,userName);
userConf.setVisible(true);
userName = userConf.userInputName;
}
else if (obj == connectItem || obj == connectButton) { //连接服务端设置
//调出连接设置对话框
ConnectConf conConf = new ConnectConf(this,ip,port);
conConf.setVisible(true);
ip = conConf.userInputIp;
port = conConf.userInputPort;
}
else if (obj == loginItem || obj == loginButton) { //登录
Connect();
}
else if (obj == logoffItem || obj == logoffButton) { //注销
DisConnect();
showStatus.setText("");
}
else if (obj == clientMessage || obj == clientMessageButton) { //发送消息
SendMessage();
clientMessage.setText("");
}
else if (obj == exitButton || obj == exitItem) { //退出
int j=JOptionPane.showConfirmDialog(
this,"真的要退出吗?","退出",
JOptionPane.YES_OPTION,JOptionPane.QUESTION_MESSAGE);
if (j == JOptionPane.YES_OPTION){
if(type == 1){
DisConnect();
}
System.exit(0);
}
}
else if (obj == helpItem) { //菜单栏中的帮助
//调出帮助对话框
Help helpDialog = new Help(this);
helpDialog.setVisible(true);
}
}
public void Connect(){
try{
socket = new Socket(ip,port);
}
catch (Exception e){
JOptionPane.showConfirmDialog(
this,"不能连接到指定的服务器。\n请确认连接设置是否正确。","提示",
JOptionPane.DEFAULT_OPTION,JOptionPane.WARNING_MESSAGE);
return;
}
try{
output = new ObjectOutputStream(socket.getOutputStream());
output.flush();
input = new ObjectInputStream(socket.getInputStream() );
output.writeObject(userName);
output.flush();
recvThread = new ClientReceive(socket,output,input,combobox,messageShow,showStatus);
recvThread.start();
loginButton.setEnabled(false);
loginItem.setEnabled(false);
userButton.setEnabled(false);
userItem.setEnabled(false);
connectButton.setEnabled(false);
connectItem.setEnabled(false);
logoffButton.setEnabled(true);
logoffItem.setEnabled(true);
clientMessage.setEnabled(true);
messageShow.append("连接服务器 "+ip+":"+port+" 成功...\n");
type = 1;//标志位设为已连接
}
catch (Exception e){
System.out.println(e);
return;
}
}
public void DisConnect(){
loginButton.setEnabled(true);
loginItem.setEnabled(true);
userButton.setEnabled(true);
userItem.setEnabled(true);
connectButton.setEnabled(true);
connectItem.setEnabled(true);
logoffButton.setEnabled(false);
logoffItem.setEnabled(false);
clientMessage.setEnabled(false);
if(socket.isClosed()){
return ;
}
try{
output.writeObject("用户下线");
output.flush();
input.close();
output.close();
socket.close();
messageShow.append("已经与服务器断开连接...\n");
type = 0;//标志位设为未连接
}
catch (Exception e){
//
}
}
public void SendMessage(){
String toSomebody = combobox.getSelectedItem().toString();
String status = "";
if(checkbox.isSelected()){
status = "悄悄话";
}
Icon face=new ImageIcon("face/smile.gif");
String action = actionlist.getSelectedItem().toString();
String message = clientMessage.getText();
if(socket.isClosed()){
return ;
}
try{
output.writeObject("聊天信息");
output.flush();
output.writeObject(toSomebody);
output.flush();
output.writeObject(action);
output.flush();
output.writeObject(action);
output.flush();
output.writeObject(message);
output.flush();
}
catch (Exception e){
//
}
}
public static void main(String[] args) {
new ChatClient();
}
}
(2)ClientReceive.java:该类是实现服务器端与客户端消息收发的类。
import javax.swing.*;
import java.io.*;
import java.net.*;
/*
* 聊天客户端消息收发类
*/
public class ClientReceive extends Thread {
private JComboBox combobox;
private JTextArea textarea;
Socket socket;
ObjectOutputStream output;
ObjectInputStream input;
JTextField showStatus;
public ClientReceive(Socket socket,ObjectOutputStream output,
ObjectInputStream input,JComboBox combobox,JTextArea textarea,JTextField showStatus){
this.socket = socket;
this.output = output;
this.input = input;
this.combobox = combobox;
this.textarea = textarea;
this.showStatus = showStatus;
}
public void run(){
while(!socket.isClosed()){
try{
String type = (String)input.readObject();
if(type.equalsIgnoreCase("系统信息")){
String sysmsg = (String)input.readObject();
textarea.append("系统信息: "+sysmsg);
}
else if(type.equalsIgnoreCase("服务关闭")){
output.close();
input.close();
socket.close();
textarea.append("服务器已关闭!\n");
break;
}
else if(type.equalsIgnoreCase("聊天信息")){
String message = (String)input.readObject();
textarea.append(message);
}
else if(type.equalsIgnoreCase("用户列表")){
String userlist = (String)input.readObject();
String usernames[] = userlist.split("\n");
combobox.removeAllItems();
int i =0;
combobox.addItem("所有人");
while(i < usernames.length){
combobox.addItem(usernames[i]);
i ++;
}
combobox.setSelectedIndex(0);
showStatus.setText("在线用户 " + usernames.length + " 人");
}
}
catch (Exception e ){
System.out.println(e);
}
}
}
}
四、 测试分析
4.1服务器测试
服务器启动
服务器端口设置
服务器与客户端通信
4.2客户端测试
客户端启动
客户端相互通信
客户端用户配置
五、 课程设计总结
通过本次课程设计是我对网络通信的知识有了更深的了解。加深了对TCP/UDP协议具体连接过程的理解。同时对套接字、数据报通讯、URL、与URLConnectiom的相关知识有了充分的认识。并将这些知识运用到具体的案例中去。本次课程设计不仅运用到套接字的知识,同时运用到java中的GUI编程,在设计框架中运用到各种组件与布局,通过服务器和客户端主框架的设计,对GUI编程中的各种组件和布局有了更清晰地了解。将书本上所学的知识成功运用到实践中去。通过本次课程设计使自己对在Java中所学的Swing组件,面板容器,事件处理,线程的创建、同步,输入输出处理,内部类,异常处理,和网络通信的知识有了一个复习和运用。培养了自己的编程能力,将学习和实践结合起来。
在Java的学习过程中,往往程序自己看得懂,但是需把所学知识运用到实践中去时,往往会遇到这样那样的问题,本次课程设计极大了锻炼了自己的动手能力,同时也使自己明白了只有动手做才会将课本上的知识变为自己的。只有自己参与实践,才能发现问题,解决问题,在解决问题的过程中提升自己的能力。希望自己以后能更多的将理论结合实践,再动手的过程中提高自己的编程能力和软件设计能力。
六、 参考文献