手把手教会你 | 多用户-服务器聊天室应用软件开发
01.项目分析
这个项目利用Socket、ServerSocket、DatainputStream、DataOutputStream、Thread以及GUI技术,开发一个简单聊天室应用程序。图1显示这个聊天室程序本机模拟运行后的典型结果。其中可以看到服务器接受两个用户以及显示两个用户的对话过程。下图是在本机操作系统窗口运行一个聊天用户的截图。
图1 聊天室两个用户对话截图
类的设计:
在下面的代码解释中将详细讨论主要类和方法的设计目的编写技术。这里将项目中的两大类:服务端类ChatServer以及用户端类ChatClient做一个概括性描述。
ChatServer——利用ServerSocket创建连接、监控用户端连接端口、利用API类Vector管理和协调多用户端的请求与对话操作,以及各种异常处理。
ChatClient——利用Socket创建对服务端ServerSocket的连接和异常处理、运行时得到用户名以及显示、利用GUI组件创建聊天室窗口、布局、显示聊天内容以及事件处理。利用线程执行对多聊天用户的协调处理和操作等。ChatClient包括如下内部类:
WindowExitHandler——执行按下Exit按钮时的关闭聊天室窗口操作。
TextActionHandler——执行将输入的聊天信息显示到每个用户聊天室窗口的功能。
ChatClientReceive——执行聊天用户线程之间聊天信息的协调性显示以及运行。
实战项目测试和模拟运行:
建议你首先在本机对这个实战项目进行模拟试运行。服务端程序可在Eclipse或者本机操作系统运行;然后打开两到三个本机操作系统窗口,按照图1下图运行用户端程序。
以下是用户端程序的主要代码部分:
class TextActionHandler implements ActionListener {//处理文本框聊天信息事件和发送信息到服务器
public void actionPerformed(ActionEvent e) {
try {
if (e.getSource() == sendText) { //如果是文本框触发事件
remoteOut.writeUTF(username + "->" + sendText.getText()); //发送信息
receivedText.append("\n" + username + "->" + sendText.getText());//显示到本机
sendText.setText(""); //清除文本框内容
}
}
catch(IOException x) {
System.out.println(x.getMessage() + " : connection to peer lost.");
}
}
}
}
class ChatClientReceive extends Thread { //处理用户接收聊天室信息线程
private ChatClient chat;
ChatClientReceive(ChatClient chat) { //构造方法
this.chat=chat;
}
public synchronized void run() { //应用协调覆盖run()方法
String s;
DataInputStream remoteIn=null;
try {
remoteIn= new DataInputStream(chat.sock.getInputStream()); //创建接收信息数据流
while(true) {
s = remoteIn.readUTF(); //按UTF方式读入信息
chat.receivedText.append("\n" + s); //添加到文本窗口
}
}
catch(IOException e) {
System.out.println(e.getMessage() + " : connection to peer lost.");
}
}
}
用户端主方法创建GUI窗口以及ChatClient对象如下:
//聊天室用户端主方法代码
public static void main(String args[]) {
JFrame frame= new JFrame("Connecting to Dear "+args[0]); //利用指令参数指定用户名
ChatClient chat=new ChatClient(frame,args[0],"localhost"); //创建用户
frame.add("Center",chat); //注册聊天室窗口
frame.setSize(350,600); //指定大小
frame.setResizable(false); //不可变更
frame.setVisible(true); //显示窗口
chat.client(); //调用client()方法
}
代码中利用指令行参数args[0]来指定用户名。所以必须在操作系统窗口利用java指令运行这个用户端程序。例如:
java ChatClient UserName //打入具体用户名,如 Emily
如下是聊天室服务器端程序的主要代码:
//
...
public class ChatServer { //聊天室服务器端程序
private static final int port = 1688; //指定端口
private boolean connected = true; //假设连接无误
private Vector<DataOutputStream> clients=new Vector<DataOutputStream>(); //创建用户队列
public static void main(String args[]) {
new ChatServer().server(); //创建无名服务器对象并调用方法server()
}
void server() { //自定义方法执行连接用户和聊天室操作
ServerSocket serverSock = null;
try {
InetAddress serverAddr=InetAddress.getByName(null); //地址初始化
System.out.println("Welcome to Chat Server. The server is running..."); //显示运行信息
System.out.println("Waiting for " + serverAddr.getHostName() + " on port "+ port);
serverSock=new ServerSocket(port, 50); //聊天室最大用户为50, 可以是任何整数
catch(IOException e) {
System.out.println(e.getMessage()+": Disconnected/Failed");
}
while(connected) {
try {
Socket socket=serverSock.accept(); //接受用户连接请求
System.out.println("Accept"+socket.getInetAddress()
getHostName()); //显示信息
//如下代码行创建输出流
DataOutputStream remoteOut= new DataOutputStream(socket.get
OutputStream());
clients.addElement(remoteOut); //将一个用户加入聊天室队列
new ServerHelper(socket,remoteOut,this).start();
//启动这个用户的线程
}
catch(IOException e) {
System.out.println(e.getMessage()+": Disconncted/Failed");
}
}
}
synchronized Vector getClients() { //自定义方法返回用户队列
return clients;
}
synchronized void removeFromClients(DataOutputStream remoteOut){ //删除推出聊天室用户
clients.removeElement(remoteOut);
}
}
虽然在这个代码中规定聊天室最大用户容量为50,这个数量可根据网络速度调整。代码中利用Vector对象来记录聊天室中的用户对象,以便向所有聊天室用户发送对话信息。每一个用户都是一个独立的线程,由其创建一个无名ServerHelper对象,并调用其start()方法来启动执行。因为在调用自定义方法getClients()和removeFromClients()时存在多线程协调问题,所以应用协调操作synchronized。
如下是自定义类ServerHelper的代码:
//聊天室服务器端用来协调处理用户聊天信息I/O的线程
class ServerHelper extends Thread {
private Socket sock;
private DataOutputStream remoteOut;
private ChatServer server;
private boolean connected = true;
private DataInputStream remoteIn;
//构造器对服务器和输入聊天室信息的用户初始化
ServerHelper(Socket sock,DataOutputStream remoteOut,ChatServer server) throws IOException {
this.sock=sock;
this.remoteOut=remoteOut;
this.server=server;
remoteIn=new DataInputStream(sock.getInputStream());
}
public synchronized void run() { //覆盖run()方法并协调运行
String s;
try {
while(connected) {
s = remoteIn.readUTF(); //读入一个用户输入信息
broadcast(s); //传播这个信息到所有聊天室用户
}
}
catch(IOException e) {
System.out.println(e.getMessage()+"connection failed");
}
}
private void broadcast(String s) { //自定义方法执行聊天室信息传播
Vector clients=server.getClients(); //得到聊天室所有用户
DataOutputStream dataOut=null; //声明输出流
for(Enumeration e=clients.elements(); e.hasMoreElements(); ) { //对每一个聊天室用户
dataOut=(DataOutputStream)(e.nextElement()); //得到用户输出流对象
if(!dataOut.equals(remoteOut)) { //如果不是删除用户
try {
dataOut.writeUTF(s); //输出这个聊天室信息
}
catch(IOException x) {
System.out.println(x.getMessage()+"Failed");
server.removeFromClients(dataOut);
}
}
}
}
}
可以看到,由于在Vector的队列中储存有所有聊天室用户输出流信息,因而容易实现对聊天室所有用户传播对话信息。当某个用户在聊天室输入任何对话后,这个信息作为参数,传入自定义方法broadcast()中,并利用循环和枚举技术,遍历用户队列,执行向所有用户传播这个对话信息的操作。由于方法run()有可能在同一时间被多个用户运行的情况,代码中利用synchronized来实现这个协调。
- 点赞
- 收藏
- 关注作者
评论(0)