当前位置: 首页 > news >正文

网络聊天室(Java)

摘要

本文阐述了基于Linux环境,Java语言实现的基本聊天室功能,涉及Linux下的Java 语言的Socket编程。以及Java语言的多线程编程。

关键字
Linux Java Thread Socket Tcp
简介
开发背景
操作系统》和《计算机网络》的学习,使我能够有机会选择“基于Linux的网络聊天室的实现”这个课题项目,使自己课堂所学的理论能够联系实际,并且能够学习自己没有涉及过网络方面以及面向对象的基本思想,而且在编写聊天程序的过程中,也涉及到多线程的程序设计问题,这个概念在《操作系统》中已经学到,但是对于真正的语言应用却是第一次。Java语言的多线程编程提供了很好的基础类,可以很容易实现多线程的调用。以及Java语言的跨平台,使我有机会在Linux下编写程序,当然对于Linux下的Java编程和Windows下的Java编程,没有多少的不同,这些都是Java语言没有平台特有的特征带来的。
系统开发环境
Linux正以自由的精神席卷全球网络操作系统市场,而Java凭借其开放、先进的架构正迅速占领着高端软件领域。将这二者结合,便可通过Linux低廉的成本实现Java高级应用,在自由、高效的环境下充分发挥出Java的优势。因此,无论从成本还是性能上考虑,二者的结合都可谓是相得益彰。
例如,现在热门的服务器端脚本JSP的推荐实现就是Linux上的Tomcat,而与Jboss结合更是极佳的EJB平台。但是,Linux之所以未能在桌面应用等领域迅速普及,软件安装和设置复杂是一个重要原因。要在Linux下实现Java编程,其普通的环境设置可能令习惯了Windows的用户望而却步。其实,很多问题只需要简单的设置就能解决。
而对于本次课程项目的开发也正是两者的结合,尽管结合没有发挥各自的精髓,但是也能体会和感受到Java+Linux 的魅力。
技术概要
网络通信基本原理

<!--[if !supportLists]-->Ø <!--[endif]-->TCP(Transmission Control Protocol)基础

数据传输协议允许创建和维护与远程计算机的连接。连接两台计算机就可彼此进行数据传输。如果创建客户应用程序,就必须知道服务器计算机名或者 IP 地址(RemoteHost 属性),还要知道进行 侦听 的端口(RemotePort 属性),然后调用 Connect 方法。如果创建服务器应用程序,就应设置一个收听端口(LocalPort 属性)并调用 Listen 方法。当客户计算机需要连接时就会发生 ConnectionRequest 事件。为了完成连接,可调用 ConnectionRequest 事件内的 Accept 方法。建立连接后,任何一方计算机都可以收发数据。为了发送数据,可调用 SendData 方法。当接收数据时会发生 DataArrival 事件。调用 DataArrival 事件内的 GetData 方法就可获取数据。

<!--[if !supportLists]-->Ø <!--[endif]-->UDP(User Datagram Protocol)基础

用户数据文报协议 (UDP) 是一个无连接协议。跟 TCP 的操作不同,计算机并不建立连接。另外 UDP 应用程序可以是客户机,也可以是服务器。
为了传输数据,首先要设置客户计算机的 LocalPort 属性。然后,服务器计算机只需将
RemoteHost 设置为客户计算机的 Internet 地址,并将 RemotePort 属性设置为跟客户计算机的 LocalPort 属性相同的端口,并调用 SendData 方法来着手发送信息。于是,客户计算机使用DataArrival 事件内的 GetData 方法来获取已发送的信息。

<!--[if !supportLists]-->Ø <!--[endif]-->Socket(Java)

套接字方式通信 (socket-based communication) 通过指派套接字实现程序自己的通信。套接字(Socket) 是一种抽象,为服务器和客户之间的通信提供方便。Java处理套接字通信的方式很像处理I/O操作,这样,程序对套接字进行读写就像读写文件一样容易。

Java支持流套接字(steam socket)和数据报套接字(datagram socket)。流套接字使用TCP协议(Transmission Control Protocol, 传输控制协议)进行数据的传输,而数据报套接字使用UDP协议(User Datagram Protocol, 用户数据报协议)。因为TCP能够探测丢失的数据传输并重新提交它们,因此传输的数据不会丢失,是可靠的。相比之下,UDP协议不能保证无损失传输。所以,采用TCP协议通信可以保证数据的正确传输。

<!--[if !supportLists]-->Ø <!--[endif]-->客户/服务器模式

网络聊天室涉及的一个服务器端和N个客户端。客户向服务器发送请求,服务器对请求作出响应。客户尝试与服务器建立连接,服务器可以接受连接也可以拒绝连接。一旦连接建立起来,客户和服务器就可以通过套节字进行通信。
客户开始工作时,服务器必须正在运行,等待客户的连接请求。创建服务器和客户所需要的语句如图1-1所示。

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-1 服务器创建一个服务器套接字,与用户的连接一旦建立,就用客户套接字也客户保持连接
要建立服务器,需要创建一个服务器套接字,并把它附加到一个端口上,服务器通过这个端口监听连接请求。端口标识套接字上的TCP服务。编号在0到1023之间的端口用来为特权进程服务。
下面的语句创建一个服务器套接字server:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="1" width="55"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="38" width="550"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->

ServerSocket server = new ServerSocket(port);1

<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

<!--[if !supportFootnotes]-->[1]<!--[endif]--> 创建了一个服务器套接字之后,服务器就能够使用下面的语句监听连接请求:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="3" width="55"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="38" width="550"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->

Socket connectToClient = server.accept();

<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

这条语句一直等待,直到客户连接到服务器套接字。客户发送下面的语句与服务器建立连接:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="6" width="55"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="39" width="550"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->

Socket connectToServer = new Socket(ServerName, port);

<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

该语句用来打开一个套接字,以便客户程序能够与服务器进行通信。 ServerName是服务气的Internet主机名或IP地址。
服务器接受连接之后,就用与处理 I/O数据流相同的方式建立服务器与客户之间的通信。
要获得输入数据流和输出数据流,可以使用套接字对象中的 getInputStream()和getOutputstream()方法:

<!--[if gte vml 1]> <table cellpadding="0" cellspacing="0"> <tbody><tr> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="60" width="550"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->

InputStream isFromServer = connectToServer.getInputStream();

OutputStream osToServer= connectToServer.getOutputSteam();
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

InputStream 和 OutputSteam是用户读写字节。可以使用DataInputStream、DataOutputSteam、BufferReader 和 PrintWriter包装InputStream和OutputSteam,读取double、int、String之类的数据值。可以使用readLine方法读入一行数据,使用println向端口写入一行。

多线程技术编程基本原理
一个线程 (Thread) 是指程序中完成一个任务的有始有终的执行流。 Java 语言支持多个线程同时运行。如下图 1-2

<!--[if gte vml 1]> <tr> <td><![endif]> <div> <p align=center style='text-align:center'><span style='mso-ascii-"Times New Roman"; mso-hansi-"Times New Roman"'>任务</span>1</p> </div> <![if !mso]></td> </tr> </table> <![endif]> <tr> <td><![endif]> <div> <p align=center style='text-align:center'><span style='mso-ascii-"Times New Roman"; mso-hansi-"Times New Roman"'>任务</span>2</p> <div>&nbsp;</div> </div> <![if !mso]></td> </tr> </table> <![endif]> <tr> <td><![endif]> <div> <p align=center style='text-align:center'><span style='mso-ascii-"Times New Roman"; mso-hansi-"Times New Roman"'>任务</span>3</p> <div>&nbsp;</div> </div> <![if !mso]></td> </tr> </table> <![endif]> <![endif]--><!--[if !vml]-->



<!--[endif]-->

图1-2 多个线程分享一个CPU
多线程可以使程序反应更快、交互性更强,并提高执行效率。 Java 对多线程程序设计提供更好的支持,包括内在地支持创建线程和锁定资源以避免冲突,解决了资源的共享冲突问题。
当程序运行时, Java 解释器为 main 方法开始一个线程。此时可以创建另外的线程。每一个线程都是一个对象,它的类实现 Runnable 接口或者扩展实现了 Runnable 接口的类。也可以通过扩展 Thread 类来实现 Runnable 接口来创建线程。 Thread 事实实现了 Runnable 接口。

<!--[if !supportLists]-->Ø <!--[endif]-->线程有五种状态:新建、就绪、运行、阻塞、结束。

如图 1-3 所示:

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-3一个线程处于其中的一个状态
<!--[if !supportLists]--> à <!--[endif]--> 新创建一个线程的时候,它进入“新建状态”。调用start方法启动线程后,它进入“就绪状态”。就绪态可以通过调用run方法实现到“运行状态”的转移。
<!--[if !supportLists]--> à <!--[endif]--> 如果给定的CPU时间用完,或者调用yield()方法可以使线程处于“就绪状态”
<!--[if !supportLists]--> à <!--[endif]--> 当线程执行结束,然后其自然应该进入“结束状态”。
<!--[if !supportLists]--> à <!--[endif]--> 当线程因为调用sleep()等方法,将进入“阻塞状态”。此状态还可以重新进入“就绪状态”,接着重新得到运行。

<!--[if !supportLists]-->Ø <!--[endif]-->Thread类包括以下的几种控制方法:

public void run()方法,用来执行线程。用户线程类中必须覆盖该方法。
public void start()方法,它引起对run方法的调用。
public void stop()方法,结束一个线程
public void suspend()方法,挂起一个线程<!--[if !supportFootnotes]-->[2]<!--[endif]-->
public void resume()方法,唤醒一个线程<!--[if !supportFootnotes]-->[3]<!--[endif]-->

public static void sleep(long millis) throws InterruptedException 方法,可以将在运行的线程置为休眠状态,休眠时间为指定的毫秒。

public void interrupt()方法,中断正在运行的线程。
public static boolean isInterrupted()方法,测试线程是否被中断。
public boolean isAlive()方法,检查线程是不是处于运行态。
public void setPriority(int p)方法,设置方法的优先级。从1~10
public final void wait() throws InterruptedException 方法,将该线程置为暂停状态,等待另外一个线程的通知。
public final void notify()方法,唤醒一个等待的线程。
Linux下Java编程
本次课题项目采用的环境是:

<!--[if !supportLists]-->¨ <!--[endif]-->Linux Platform - J2SE(TM) and NetBeans(TM) IDE Bundle NB 4.1 / J2SE 5.0 Update 4<!--[if !supportFootnotes]-->[4]<!--[endif]-->

<!--[if !supportLists]-->¨ <!--[endif]-->Rat Hat Linux 9.0

Linux采用J2SENetBeans可以很容易的开发面向Linux的应用程序,可以移植到windows平台下运行。其中NetBean Sun公司开发的免费Java图形用户界面编辑器。(如图1-4)可以很轻松的实现界面的设计,它将控件以Swing awt JavaBean分类放置。

集成 Tomcat 5.0 加入对 XML,Structs 的支持。这其中感触最深的地方是,不能修改自动生成的组件初始代码。假如要用 ButtonGroup 就得自己去另写一个函数来初始化。感觉这样做,因为不能随意修改代码,能避免随意修改所导致的错误。但是,很多时候,我们真的要修改那部分代码反倒是件很麻烦的事了。
对于 window 平台有个比较不当的地方就是内存消耗太大。硬盘频繁访问。在 Linux 环境可以明显感觉到速度快了不少!

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-4 windows 平台下的NetBeans运行界面<!--[if !supportFootnotes]-->[5]<!--[endif]-->
基于Linux的网络聊天室的具体实现
服务器端和客户端体系结构
根据通信的基本原理,不难分析服务器端与客户端的通信实现,以下是客户端和服务器端的交互流程,如图 1-5

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-5 客户端和服务器端的基本流程
流程图的简要描述
Server Client 端通信主要是服务器端创建多个线程,生成多个 Socket 对不同的用户进行通信。服务器端和客户端通过消息命令字的方式进行消息确认。方式是在消息头加入“命令字”。
自定义命令字含义如下:
[MESSAGE] :表示接下来的一句话是消息
[NAME]: 表示接下来的一句话是名字
[FIRSTNAME] :用于程序逻辑控制,表示第一个
[SYSTEM]: 系统消息
[Server exit!] 服务器退出
[WHISPERMESSAGE] :私聊控制字
[QUIT] 表示客户端退出聊天室
客户端:
客户端由两个类实现,一个是主类 ClientJFrame 另外一个是用于播放声音的 PlaySound 类。一下是客户端的类关系图,图 1-6

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-6 Client端两个类,ClientJFrame中创建PlaySound实例来播放声音

<!--[if !supportLists]-->Ø <!--[endif]--><!--[if gte vml 1]> <table cellpadding="0" cellspacing="0"> <tbody><tr> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="404" width="582"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->
public void startConnect()
{
try
{

sock = new Socket(ipAddress, DEFAULT_PORT); //新建一个socket

if(sock!=null) //连接成功

{

processMsg();

}

isFromServer=new BufferedReader(); //新建一个接收变量

osToServer = new PrintWriter(); //新建一个输出变量

osToServer.println("[NAME]" + name); //发名字

osToServer.flush(); //刷新数据缓冲
}

catch(IOException ex)

{
}

readThread=new Thread(this); //通过Runnable实现

readThread.start();

}
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]--> Client端通过方法startConnect(),尝试连接服务器端,其函数原型如下:

<!--[if !supportLists]-->Ø <!--[endif]-->通过SendInformation()方法实现数据的发送,实现函数原型如下:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="3" width="23"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="259" width="582"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->
public void sendInformation()
{
if(私聊)
{

osToServer.println("[WHISPERMESSAGE]" + message);//发送私聊信息

osToServer.flush();
}
else
{

osToServer.println("[MESSAGE]" + message); //发送群聊信息

osToServer.flush();

}
}
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

通过调用prinln()方法可以向端口写一句消息。

<!--[if !supportLists]-->Ø <!--[endif]-->当程序退出,或者服务器退出,线程应该结束运行。客户端,通过重载Thread的run方法实现,函数的原型如下:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="2" width="23"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="397" width="582"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->
public void exit()
{
try
{
osToServer.println("[QUIT]"); //向服务器发送退出命令字

osToServer.flush();

}

catch(Exception exc){}

try
{
sock.close(); //关闭socket

isFromServer.close(); //输入缓冲关闭

osToServer.close(); //输出缓冲关闭

}
catch(IOException ioe){}
finally //不管异常是否发生都将执行
{

System.exit(0);

}
}
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

<!--[if !supportLists]-->à <!--[endif]-->在退出应用程序之前,向服务器端发送[QUIT]命令字,实现聊天室的更新。

<!--[if !supportLists]-->à <!--[endif]-->客户端退出要将socket关闭,要将输入和输出的数据流关闭。

<!--[if !supportLists]-->à <!--[endif]-->最后将执行finally块程序,最后调用System的exit的函数退出应用程序。

服务器端
服务器端主要创建了 5 个类,其中 ServerJFrame 是主类, CommunicateThread 用于连接客户端, BroadcastThread 用与对消息的广播, WhisperThread 用于处理悄悄话, BroadcastName 用户广播当前在线的用户。图 1-7 显示了类中的方法和类的属性。

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-7 Server端的类视图

<!--[if !supportLists]-->Ø <!--[endif]-->Server相对与客户端更加的复杂,要主动监听客户端发送的连接请求,创建不同的线程,来应答客户的请求。创建的线程,接受客户发送的数据的处理。

Server创建了连接线程后,还必须创建广播线程,将每一个客户发送的消息广播出去,到每一个客户端。对于广播线程和数据接受和处理线程之间的资源共享问题,我采用了,有序运行的方法来消除死锁。即在用户发送来数据时,开启广播线程,对刚才的数据进行广播,在信息广播结束后,关闭广播线程。这样一前一后,就可以保证数据广播的正确性。
在server还需要处理的一件事,就是如何将私聊信息发送给指定的客户端。我采用的方式是,“用户名查找发送法”,可以比较快而准确的发送数据<!--[if !supportFootnotes]-->[6]<!--[endif]-->,为了和群聊消息区分,我采用WhisperThread类单独给予处理。这样可以清晰的区分。
在客户端,还需要处理的一件事情就是,管理当前的在线用户。我使用一个堆栈<!--[if !supportFootnotes]-->[7]<!--[endif]-->来管理用户,当用户来到时,就将用户名压入堆栈。当用户退出时,将用户的名字从中去除<!--[if !supportFootnotes]-->[8]<!--[endif]-->。

<!--[if !supportLists]-->Ø <!--[endif]-->服务端使用serverListen()函数开始监听端口,其函数原型如下:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="13" width="31"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="169" width="582"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->
private void serverListen()
{

chatAcceptThread = new Thread(this); //创建一个监听线程

chatAcceptThread.start();

broadcastThread = new BroadcastThread(this); //创建广播线程

broadcastThread.start();

}
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

<!--[if !supportLists]-->Ø <!--[endif]-->ServerJFrame是通过Runnable接口来创建的线程。其run函数实现客户端的接受并创建一个新的线程:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="5" width="31"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="398" width="582"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->
public void run()
{

clients = new java.util.Vector(); //分配一个栈,用于存储用户线程

clientsInfor = new java.util.Vector(); //用于存储用户名
try
{

serverSock=new ServerSocket(DEFAULT_PORT); //新建一个Socket

}

catch(IOException e){}

try
{

while(true)

{

Socket clientSock = serverSock.accept();

CommunicateThread ct = new CommunicateThread(); //实例化通信类

boolean addSucOrNot = clients.add(ct); //将当前的进程压入堆栈

}
}

catch(IOException e){}

}
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->
在run函数中有一个while(true)程序块,表示这个进程将随应用进程一起存在,所以服务器端将不停的监听端口,当一个连接进入的时候,就通过CommunicateThread来处理当前连接的进程(客户端),然后,服务器主线程将监听下一个连接。

为了后面能够将数据广播出去,和实现私聊,必须要得到响应的线程,所以在向堆栈压入线程的时候,需要有一个变量(index)来指示线程, index 不会随着客户的退出而删除<!--[if !supportFootnotes]-->[9]<!--[endif]-->,而是逐次累加,那么当客户退出时,要将此进程在堆栈中的位置设置为[EMPTY],来表示一个客户端已经退出,此时,服务器端要结束和这个客户端连接的线程。


<!--[if !supportLists]-->Ø <!--[endif]-->那么当用户发来数据的时候,并且命令字为[MESSAGE]的时候,服务器需要将这条信息广播出去,这个由Broadcast来处理,其中的run函数原型如下:

<!--[if gte vml 1]> <table align="left" cellpadding="0" cellspacing="0"> <tbody><tr> <td height="6" width="31"><br></td> </tr> <tr> <td><br></td> <td style="border: 0.75pt solid black; background: white none repeat scroll 0%; vertical-align: top; -moz-background-clip: -moz-initial; -moz-background-origin: -moz-initial; -moz-background-inline-policy: -moz-initial;" bgcolor="white" height="789" width="582"><!--[endif]--><!--[if !mso]-->

<!--[endif]-->
public void run()
{
try
{
while(true)
{
//若消息堆栈为空,或者没有当前信息需要发送

boolean startBroadcast = chatFrame2.getBroadcastStart();

if (!startBroadcast)

{
try

Thread.sleep(500); // 线程睡眠500毫秒后重新检测

catch(InterruptedException ex){}

continue;

}

int lengthOfChatClients = chatClients.size(); //线程个数

for(int i=0; i < lengthOfChatClients; i++) //对每个线程进行操作

{

if(chatClients.get(i).equals("[EMPTY]")) //若是退出进程

continue;

comThread1 = (CommunicateThread)chatClients.get(i);

msgStack = comThread1.inforStack;

int lengthOfMsgStack = msgStack.size(); //对消息堆栈进行广播

for(int j=0; j<lengthOfMsgStack; j++)

{

string = (String)msgStack.get(j);

broadcastInfor = string;

broadcast("[MESSAGE]" + broadcastInfor);

boolean temp = msgStack.removeElement(string);

}
}
try
{

chatFrame2.stopBroadcast(); //停止广播

Thread.sleep(1000);

}

catch(InterruptedException ex) {}

}
}

catch(Exception e){}

}
<!--[if !mso]-->
<!--[endif]--><!--[if !mso & !vml]--><!--[endif]--><!--[if !vml]--> <!--[endif]-->

广播线程,主要是和接受消息线程达到同步,通过共享内存堆栈来实现数据的交互。然后广播线程需要解决的问题就是如何实现线程的数据广播,这是使用的是,调用创建的线程,然后调用CommunicateThread的通信进行数据的广播。其中名字的广播也是同样的一个道理。
执行效果演示
演示效果如下图所示 ( 1-8)

<!--[if gte vml 1]> <![endif]--><!--[if !vml]--><!--[endif]-->

图1-8是windows下的运行效果,充分说明在Linux下开发的应用程序的可移植性,在Windows下运行无阻

<!--[if !supportLists]-->Ø <!--[endif]-->程序可以实现公聊和私聊<!--[if !supportFootnotes]-->[10]<!--[endif]-->,公聊在服务器端将加入聊天记录,私聊则只是发给指定用户,服务器端不保留聊天信息。

<!--[if !supportLists]-->Ø <!--[endif]-->收到系统消息,和用户变化都会有声音提示。

<!--[if !supportLists]-->Ø <!--[endif]-->完全可以单机来调试信息,也试过在Linux下运行服务器端,在Windows下使用客户端进行访问,访问方式没有区别,通信也没有故障。

<!--[if !supportLists]-->Ø <!--[endif]-->当服务器退出时,或者说用户端失去服务器连接时,用户将需要重新连接,当然也可以实现超时退出的方式,这样可以实现重新连接。

<!--[if !supportLists]-->Ø <!--[endif]-->可扩展功能:系统可以选择需要发送的系统消息的对象,这样可以使系统消息发送更加灵活。

<!--[if !supportLists]-->Ø <!--[endif]-->用户可以通过右边的list得到当前的在线用户的状况

<!--[if !supportLists]-->Ø <!--[endif]-->用户可以通过左边的textArea得到当前群中用户所发送的消息的记录<!--[if !supportFootnotes]-->[11]<!--[endif]-->。

<!--[if !supportLists]-->Ø <!--[endif]-->当用户连接失败,可以选择重新登陆,重新登陆就不需要重新输入用户名。

<!--[if !supportLists]-->Ø <!--[endif]-->假如用户登陆时,没有指定连接地址,将默认为localhost地址<!--[if !supportFootnotes]-->[12]<!--[endif]-->。

<!--[if !supportLists]-->Ø <!--[endif]-->用户可以通过直接按Enter键发送消息<!--[if !supportFootnotes]-->[13]<!--[endif]-->。

总结
经过一个星期的编码,基本完成了课题任务。从中也学到了不少的东西,锻炼了自己的独立开发能力。其中,对 Java 语言也有了一定的了解,也被 Java 语言的强大类库所折服,以及 Java 环境提供的规范语言所欣喜。正因为有这样优秀的语言,和优秀的类库使得这次的任务能顺利的完成。
从中让我深有体会的是, Java 的多线程编程。让我真正有机会接触多线程的编程,而 Java 语言的强大也使得这样的一个过程,不是非常的艰难。 Java 多线程编程,一般采用继承 Thread 类或者采用 Runnable 接口来实现。
Windows 平台和 Linux 平台对于 Java 语言,不同的只是虚拟机,对于程序,对于编码没有区别,这也是能让我顺利完成 Linux 平台的应用程序的一个保证。
通过书写这篇文档,我也从中琢磨了许多的东西,如 Rose UML 等面向对象实现概念,通过尝试也学习了其中工具带来的方便。

相关文章:

  • react ssr方法
  • Palm之祭
  • 304详解 gzip压缩 虚拟主机
  • 看Borland IDE向何处去
  • jwt原理
  • 我的BCB情缘
  • koa session 签名 cookie等
  • 作了Redhat Linux的基础课测试,刚好达到及格的标准,还是要多加学习啊!
  • express精简
  • react hooks1
  • CC2e:《代码大全(第2版)》集萃
  • js 数组API之every、some用法
  • 整理一个ANT在J2EE项目中的应用,含预编译JSP和打包WAR/EAR文件!
  • Taro解决低版本内核 兼容ES6语法问题Object.entries is not a function
  • git add . 增加所有文件到缓存区 git diff 什么也没有 因为无法比较差异 如果git add 某一个文件 则可以查看变动文件
  • ES6指北【2】—— 箭头函数
  • [nginx文档翻译系列] 控制nginx
  • “Material Design”设计规范在 ComponentOne For WinForm 的全新尝试!
  • 【面试系列】之二:关于js原型
  • 230. Kth Smallest Element in a BST
  • Js实现点击查看全文(类似今日头条、知乎日报效果)
  • markdown编辑器简评
  • Python_网络编程
  • react 代码优化(一) ——事件处理
  • Swift 中的尾递归和蹦床
  • windows下如何用phpstorm同步测试服务器
  • 分布式事物理论与实践
  • 前端知识点整理(待续)
  • 浅析微信支付:申请退款、退款回调接口、查询退款
  • 《TCP IP 详解卷1:协议》阅读笔记 - 第六章
  • 选择阿里云数据库HBase版十大理由
  • ​ArcGIS Pro 如何批量删除字段
  • $NOIp2018$劝退记
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (12)目标检测_SSD基于pytorch搭建代码
  • (2.2w字)前端单元测试之Jest详解篇
  • (Python第六天)文件处理
  • (附源码)ssm户外用品商城 毕业设计 112346
  • (三分钟了解debug)SLAM研究方向-Debug总结
  • (四)鸿鹄云架构一服务注册中心
  • .[backups@airmail.cc].faust勒索病毒的最新威胁:如何恢复您的数据?
  • .net CHARTING图表控件下载地址
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .Net+SQL Server企业应用性能优化笔记4——精确查找瓶颈
  • .Net7 环境安装配置
  • .NET与java的MVC模式(2):struts2核心工作流程与原理
  • .NET中 MVC 工厂模式浅析
  • ??在JSP中,java和JavaScript如何交互?
  • @Autowired多个相同类型bean装配问题
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!
  • [2015][note]基于薄向列液晶层的可调谐THz fishnet超材料快速开关——
  • [2016.7 day.5] T2
  • [383] 赎金信 js
  • [AI]文心一言爆火的同时,ChatGPT带来了这么多的开源项目你了解吗
  • [Android]创建TabBar