网上考试系统总结

这个基于TCP协议的网上考试系统采用   C/S结构,是对学习j2se的一个检验,基本涉及到j2se的所有方面:多线程,IOGUI,网络编程,JDBC等等。

开发平台:eclipse3.1

数据库:Oracle 9i

一、该系统主要解决的问题

该系统主要处理了三个方面的问题:

1、客户端和服务器的间连接

2、服务器和数据库间的连接

3、界面的设计和控制

下面分别做简要说明:

1、客户端和服务器的间连接:

客户端和服务器的间连接,发送对象可以说是该系统的核心内容

具体步骤:

1)服务器端建立一个ServerSocket,然后调用accept()方法等待客户连接。

2)客户端建立一个Socket并发送自己的登陆信息(用户名,密码),请求与服务器连接。

3)服务器接收到用户登陆请求,如果通过验证则为该用户建立一个新的线程与客户端进行专线连接

4)刚才建立的两个Socket在服务器建立的新的线程上对话,服务器根据用户发送对象的

flag解析出要求服务的类型,并进行相应的处理,直到用户退出,释放该连接。

5)服务器等待新的连接请求。

将客户端和服务器之间要发送的所有消息封装成类,以对象的方式传递:

登陆时客户端向服务器发送消息封装成的类  LoginMsg

登陆后服务器向客户端发送反馈消息封装成的类 LoginEcho

获取试题时客户端向服务器发送消息封装成的类 GetTextMsg

退出时客户端向服务器发送消息封装成的类 ExitMsg

修改密码时客户端向服务器发送消息封装成的类 PasswordChangeMsg

修改密码后服务器向客户端发送反馈消息封装成的类 ChangeEcho

为了使该类的对象能在网络上传输,以上所有的类均实现Serializable接口,同时客户端向服务器发送消息封装成的类都继承了Msg类,都包含成员变量flag,这样在服务器端可以根据接收到的对象的flag值判断消息的类型,进行相应的处理,这里用到了面向对象的多态性。

2、服务器和数据库间的连接

用户、试题等信息都存在数据库中,客户端发送请求后服务器要连接数据库,为了实现代码复用、提高效率(因为服务器所有对数据库的操作只需一个连接),把建立连接的代码写入ConnectionManager的静态代码段,并用静态方法getConnection()返回获得的连接。在该类中还用私有的构造方法实现了单例模式,不允许其它类创建该类的实例对象。

核心代码:

Class.forName("oracle.jdbc.driver.OracleDriver");

String url="jdbc:oracle:oci8:@neuqsoft";

String user="scott";

String password="tiger";

conn= DriverManager.getConnection(url,user,password);

3、界面的设计和控制

 

服务器界面:

    

客户端界面:

 

客户端和服务器的主界面用swing中新增加的BoxLayout布局管理器,该布局管理器允许多个组件全部垂直摆放或全部水平摆放,通过嵌套组合使用多个BoxLayout布局管理器,可以实现类似GridBagLayout的功能,使界面布局合理、美观实用。另外通过使用DefaultFont类的静态方法initGlobalFontSetting统一设置字体风格。通过控制按钮是否可用严格控制流程,比如没有启动服务器前不能使用服务器的其它功能,当答到最后一题时,下一题按钮不可用,提交按钮变为交卷等等。

二、功能

服务器端实现的功能:         

1:管理员登录,启动服务器,并设置考试时间,考试人数;

2:查询考试动态信息,包括服务器信息,用户信息,网络信息;

    3:接收用户呼叫;

    4:验证用户登陆;

    5:用户开始考试,读取考试时间,读取试题;

    6:接收用户答案进行判断,将答案存到数据库,将成绩返回到客户端;

    7:对数据库信息进行维护(查询数据库中表的信息)

    8:对学生信息进行管理(包含查询某考生考试成绩)

9 对试题进行维护(增加删除试题)

10:考试结束关闭服务器。

客户端实现的功能:

1:用户登录,不能重复登陆,达到最大连接数不能登陆,密码错误三次不能登陆;

2:根据身份证号和旧密码修改密码;

    3:选择试题科目试题类型进行考试;

    4:发送前预览所做答案信息,并能进行修改;

5:考试完成可以查看成绩并退出考场;

6:考试过程显示考生信息,考试信息,答题信息,时间信息。

三、开发中遇到的问题及解决方案

1、启动服务器后原来界面不正常

问题描述:在ExamClient(负责界面)中点击“启动服务器”后新建了ClientNet类的对象,在这个类中新建了serversocketsocket,这时候原来的界面不正常了。

问题原因:在界面上点启动服务器后new了一个类,在这个类中新建了serversocketsocket,当前这个线程用于监听用户请求,而这个线程正是刚才处理界面的线程,所以界面无法刷新了,所以界面就不正常了。更深入的探讨:为什么点击后出现一个界面的情况就没有出现这个问题?因为:程序在产生一个Frame时自动产生一个awt线程,所以不存在上述问题。

解决方案:启动服务器后启动一个线程new一个类,这样原来那个线程就可以负责刷新界面了。

2、服务器和客户端间传对象时出现:java.io.EOFException java.io.StreamCorruptedException: invalid stream header

问题原因:服务器和客户端的

ObjectInputStream  ois = new ObjectInputStream(s.getInputStream());

ObjectOutputStream  ois = new ObjectInputStream(s.getInputStream());

必须一一对应!

一个ObjectOutputStream的构造和一个ObjectInputStream的构造必须一一对应.ObjectOutputStream的构造函数会向输出流中写入一个标识头,ObjectInputStream会首先读入这个标识头.因此,多次以追加方式向一个文件中写入object,该文件将会包含多个标识头.所以用ObjectInputStreamdeserialize这个ObjectOutputStream,将产生StreamCorruptedException.

解决方案:

Server:

while(start)

{

              ois = new ObjectInputStream(s.getInputStream());

              oos = new ObjectOutputStream(s.getOutputStream());

}

//登陆成功后启动的线程

class ExamThread extends Thread

{

public void run()

{

while(flag)

{

ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());

 

Msg msg = (Msg)ois.readObject();

int read = msg.flag;

 

if(read==Msg.GETTEST)

{}

if(read==Msg.EXIT)

{}

if(read==Msg.CHANGEPASSWORD)

{}

}

}

Client:

//发送,接收登陆的消息

public void send(LoginMsg login)

{

              oos = new ObjectOutputStream(s.getOutputStream());

              ois = new ObjectInputStream(s.getInputStream());

}

//发送,接收获取试题的消息

public void send(GetTextMsg getTextMsg)

{

              oos = new ObjectOutputStream(s.getOutputStream());

              ois = new ObjectInputStream(s.getInputStream());

}

//发送修改密码请求向服务器

public void send(PasswordChangeMsg passwordChangeMsg)

{

              oos = new ObjectOutputStream(s.getOutputStream());

              ois = new ObjectInputStream(s.getInputStream());

}

//发送退出消息封装的对象

public void send(ExitMsg exitMsg)

{

              oos = new ObjectOutputStream(s.getOutputStream());

              ois = new ObjectInputStream(s.getInputStream());

}

另外:

DataInputStream ois = new DataInputStream(s.getInputStream());

ObjectInputStream ois = new ObjectInputStream(s.getInputStream());

不能同时出现!

3、关于网络多线程问题:服务器通过用户请求后,建立一个新的线程与客户端进行专线连接,怎样使这个线程得到socket和用户的信息。

解决方案:在线程构造函数中传递socket,解决网络多线程问题,同时传递给线程该用户的信息,方便线程对用户的控制。

新建线程的构造函数:

public ExamThread(Socket s,User user)

{

              this.socket = s;

              this.user = user;

}

4、关于用socket传对象的问题:

被传的对象必须implements JAVA.io.Serializable接口,然后用ObjectInputStream ObjectOutputStream就可以传递对象了。

实现 java.io.Serializable 接口的类是可序列化的。没有实现此接口的类将不能使它们的任一状态被序列化或逆序列化。序列化类的所有子类本身都是可序列化的。这个序列化接口没有任何方法和域,仅用于标识序列化的语意。

5、关闭确认时按否依然关闭

没有写jFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

默认是dispose,所以看不见了,其实还存在

6、用JOptionPaneshowInputDialog方法输入考试时间和人数时选撤消出现异常

问题原因:JOptionPane.showInputDialog方法出来的对话框如果按取消的话, 返回值是null,不输入而确定的话,返回值是一个空字符串.

这句按取消会异常,

 String stu_number = JOptionPane.showInputDialog(ExamServer.this,"请输入参加考试的人数:").trim();

解决方案:先通过返回值看看点击的是不是“取消”,然后再决定是否进行下一步动作。

String stu_number = JOptionPane.showInputDialog(ExamServer.this,"请输入参加考试的人数:");

if(null == stu_number)

{

//用户取消了。

}

else

{

stu_number = stu_number.trim();

//进行下一步动作。

}

7、如果一个用户登陆二次后另外一个用户登陆会提示三次错误

解决方案:把验证放在本地即可互不影响,注意count要为static!因为一个用户每按一次呼叫服务器就new一个clientnetcount清零,永远都到不了三!

8、用户登陆只可以是admin,其它用户名和表都不可以

问题原因:JDBC默认normal用户登陆,而我用pl/sql登陆时选的是sysdba,二种登陆方式看到的表和记录完全不同,admin可用是因为以前在normal下插入过该记录。

9、关于多次连接数据库的问题

解决方案:把建立连接的代码写入ConnectionManager的静态代码段,并用静态方法getConnection()返回获得的连接。在该类中还用私有的构造方法实现了单例模式,不允许其它类创建该类的实例对象。

10if(loginMsg.equals("登陆成功"))写成了:if(loginMsg= ="登陆成功")

终于体会到equal==的区别了!

11、关于test_array[][]存放数据的约定:

test_array[0][0]存放考试题目个数,test_array[0][1]存放考试时间,

test_array[i][0]存放第i题题目,test_array[i][1]存放第i题答案

12、关于使用swing 界面默认字体为粗体的问题

解决方案:通过使用DefaultFont类的静态方法initGlobalFontSetting统一设置字体风格。

13、提交和交卷用一个done的问题

具体代码和解决方案见ExamClient393

类似问题还出现在ServerManager116行:

exist = false;   //将用户已经存在信息置为false,否则一次为true后用户将永远不能登陆

14BorderLayout布局中怎样让中间的空隙不显示

答案:用FlowLayout()可以解决这个问题,不过界面太窄,依然不太好看

15Server_EditExam下面if开始出现异常                    if(L_NOField.getText().trim().equals("")||answerField.getText().trim().equals("")||text.getText().trim().equals(""))

问题原因:

//TextArea text ;

//因为没有注释掉这句,在界面上定义和增加都是指这个text,不过取text.getText()的时候却//是指46行定义的text,因为46行只有一句TextArea text ;没有赋值,所有出现异常!

详细问题见代码处

16、服务器判断是否达到最大连接数的时候:

if((userCount++)>es.stu_count)

{

    loginMsg = new String("已达最大连接数请稍后");

userCount--;

//没有登陆成功人数却加一,所以要减掉,一个简单的数学逻辑导致用户退出后其他用户//依然无法登陆

}

其实更好的方法或者说问题的根源是不应该在这里给userCount++,毕竟还没有登陆成功,

所以如果在登陆成功后加一更符合逻辑。

四、版本

该系统分二个版本,2.0版对1.0版本的改进:

1、版本1.0中用DataInputStreamDataOutputStream传递intString进行了改进,2.0中改为使用ObjectInputStream ObjectOutputStream  直接传递对象,这样增强了程序的可读性,提高了运行的效率,并且更符合面向对象的特征。

2、版本1.0中用户获取试题时服务器用DataOutputStreamwriteUTF方法传给客户端一个试题,按下一题时传给下一个,2.0中改为直接传给客户端一个二维数组,并且在数组中包含试题个数和考试时间的信息,使考试过程的处理更加简洁。

3、版本2.0中解决了1.0中多次连接数据库、设置考试时间和人数如果按撤消会异常、允许重复登陆等问题。

五、总结

用了十几天的时间做完了这个共3000行系统,算是对我学习java三个月的一个总结, 过程中遇到很多问题,也学到很多东西。这个系统可以算是学java以来从模仿到创作的一个转折点。

       最后感谢所有帮助过我的人,是他们带给我每一点进步。

2007-4-12

苏强

 

你可能感兴趣的:(Java)