Java聊天室

1.基本架构

2.服务端

3.客户端

4.运行效果


一,基本架构

1.客户端和服务端通过 socket套接字来通信

2.客户端只有一类socket套接字,专门用于处理与服务端的通信的

3.服务端有两类socket套接字,一个是专门用于监听有没有客户端的连接请求,另一个是专门用于处理与客户端的通信。前者和后者独立运行,用多线程实现。

4.当服务端监听到有客户端的连接请求,服务端就会新建一个新线程,专门用于服务这个客户端。

5.服务端维护一个客户端列表,用于记录连接进来的客户端。当服务器收到一个客户端发来的信息,就会遍历客户端列表,把信息发给所有客户端,以实现群聊的功能。




二,服务端

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.net.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

public class ChatServer {
	ServerSocket ss = null;	//服务器的的套接字,用于监听来自客户端的连接请求
	boolean started = false;
	
	List<Client> clients = new ArrayList<Client>();//维护一个客户端链表,用于记录已经连接上服务端的客户端们
	/*主程序*/
	public static void main(String[] args) {
		new ChatServer().start();	//调用本类的start()方法。因为服务端的操作在 start()方法中。
	}
	
	/*服务端的操作*/
	public void start(){
		try {
			ss = new ServerSocket(8888); //指定服务端套接字的端口号为8888(可以任意指定,但必须大于1024)
			started = true;

		} catch (BindException e) { //若捕捉到此异常,表明指定的端口号已经被占用了
			System.out.println("端口使用中..请重新启动程序");
			System.exit(0);		//程序退出
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		try{
			while(started){
				Socket s = ss.accept();	//创建一个用于管理客户端连接的客户端套接字。每当有新的客户端连接,服务端就新建一个套接字给此客户端
				Client c = new Client(s);//每当一个客户端连上服务端,就新建一个Client类,指代此客户端,并传入客户端套接字
				
				new Thread(c).start();	//新建一个子线程,并启动它
				clients.add(c);		//把Client对象加入到客户端列表中
				}
			} catch(IOException e){
				e.printStackTrace();
			} finally{
				try {
					ss.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
	
	}

	/*Client类实现Runnable接口,为一个线程类。*/
	class Client implements Runnable {
		Socket s = null;				//客户端套接字的引用
		DataInputStream dis = null;		//输入流的引用
		DataOutputStream dos = null;	//输出流的引用
		boolean bConnected = false;		//服务端是否已经获取客户端的输入流,输出流
	
		public Client(Socket s) {	//Client类的带参构造函数
			this.s = s;	//Client类中的客户端套接字引用指向 传入的客户端套接字
			try {
				dis = new DataInputStream(s.getInputStream());	//getInoutStream()用于获取客户端套接字自带的输入流
				dos = new DataOutputStream(s.getOutputStream());//getOutoutStream()用于获取客户端套接字自带的输出流
				bConnected = true;	//成功获取客户端的输入流,输出流后,把bConnected设置为true
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		/*用于向客户端发送信息*/
		public void send(String str){
			try {
				dos.writeUTF(str);//向输入流发送信息 
			} catch (IOException e) { //当此Client对象出现异常,把此Client从客户端列表clients中删除
				clients.remove(s);
			}
		}
		/*Client线程的操作*/
		public void run() {
			String str = null;
			try{
				while(bConnected){	//当成功获取客户端的 输入流,输出流后。
					str = dis.readUTF();	//从输入流读取来自客户端的信息
					//System.out.println(str);
					for(int i=0;i<clients.size();i++){	//从一个客户端中获取信息后,发此信息发送给已经连接上服务端的其他客户端
						Client c = clients.get(i);		//用列表序号来获取对应的客户端
						c.send(str);					//向以序号遍历的客户端发送信息
					}
				}
			} catch (EOFException e){	//由于客户端关闭了的时候,服务端不能从流中读取信息,就会报此异常。表示客户端关闭了
				System.out.println("Clinet closed!");
			} catch (IOException e){
				e.printStackTrace();
			}
			finally{
				try {
					if(s!=null) s.close();	//关闭客户端套接字
					if(dos!=null) dos.close();//关闭此客户端的输出流
					if(dis!=null) dis.close();//关闭此客户端的输入流
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}
}


3.客户端

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

public class ChatClient extends Frame {	//继承Frame类,用于给ChatClient类创建界面

	Socket s = null;	//用于连接服务端的套接字
	DataInputStream dis = null;	 //用于获取来自服务端的信息的输入流
	DataOutputStream dos = null; //用于向服务端发送信息的输出流
	boolean bConnected = false;	// 连接服务端的标志位
	
	TextField tfTxt = new TextField();	//创建一个文本框(放在界面中)
	TextArea taContent = new TextArea();//创建一个消息框(放在界面中)

	public static void main(String[] args) {	//主函数
		new ChatClient().lauchFrame();			//lauchFrame()用于设置界面布局

	}
	/*设置界面布局*/
	public void lauchFrame() {	
		this.setLocation(400, 300);	//界面在电脑屏幕中的显示位置
		this.setSize(300, 300);	//界面大小(长,宽)
		add(tfTxt, BorderLayout.SOUTH);	//把已创建的文本框加入到界面中的南方(即下方)
		add(taContent, BorderLayout.NORTH);	//把已创建的消息框加入到界面中的北方(即上方)
		this.addWindowListener(new WindowAdapter() {	//加入window监听器到界面中
			public void windowClosing(WindowEvent e) {	//当鼠标点击右上角的'X'时,会自动调用windowClosing()
				disconnect();	//调用disconnect()函数,用于断开与服务端的连接
				System.exit(0);	//程序退出
			}
		});
		pack();	//将界面上的控件位置自适应放好
		tfTxt.addActionListener(new TFListener()); //加入Action监听器到文本框中
		setVisible(true);	//使界面显示出来
		connect();			//调用connect(),用于连接服务端
		new Thread(new RecvThread()).start();	//创建一个新线程,用于接收来自服务端的信息
	}

	/*用于连接服务端*/
	public void connect() {	
		try {
			s = new Socket("127.0.0.1", 8888); //创建套接字对象,第一个参数为服务端所在的ip地址,后一个参数为服务端的端口号
			dis = new DataInputStream(s.getInputStream());	//创建输入流,用于接收服务端的信息
			dos = new DataOutputStream(s.getOutputStream());	//创建输出流,用于向服务端发送信息
			bConnected = true;	//设置 连接服务端的标志位 为真

			System.out.println("Client connected!");
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/*当要断开与服务端的连接时,作一些处理*/
	public void disconnect() {
		try {
			dos.close();	//断开输出流
			dis.close();	//断开输入流
			s.close();		//断开套接字
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	/*自定义Action监听器的内容*/
	private class TFListener implements ActionListener {
		public void actionPerformed(ActionEvent e) {//实现ActionListener接口中的actionPerformed()方法
			String str = tfTxt.getText().trim(); 	//当按下回车后,获取文本框中的信息。trim()用于消除文本信息前面和后尾的空格
			tfTxt.setText("");						//(发送信息后)把文本框置空。

			try {
				dos.writeUTF(str);	//往输出流写数据(用于发信息给服务端)
				dos.flush();		//强制把流缓冲区里的信息发出去。(否则系统会等到缓冲区满了以后才会发出去)
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
		}
	}

	/*自定义用于接收来自服务端的信息的子线程*/
	private class RecvThread implements Runnable{

		@Override
		public void run() {	//子线程工作的内容都写在 Runnable接口的 run()方法里
			try{
				while(bConnected){	//当bConnected 为true时,表示客户端连接服务端成功
					String str = dis.readUTF();	//从输入流中获取来自服务端的信息
					taContent.setText(taContent.getText()+str+'\n');	//把来自服务端的信息加上消息框以前的信息一起在消息框中打印出来
					//System.out.println(str);
				}
			} catch(IOException e){	//当出现IO异常时,很可能是因为客户端程序被用户关闭了,此时打印“client close”
				System.out.println("client close!");
			}
			
		}
		
	}
	
}


目录树:



4.运行结果:

先开服务端程序,再开多个客户端:


如图所示:

开了3个客户端,当从其中一个客户端中输入信息时,其他的客户端也会收到。

Java聊天室程序 需求分析 2.1 业务需求 1. 与聊天室成员一起聊天。 2. 可以与聊天室成员私聊。 3. 可以改变聊天内容风格。 4. 用户注册(含头像)、登录。 5. 服务器监控聊天内容。 6. 服务器过滤非法内容。 7. 服务器发送通知。 8. 服务器踢人。 9. 保存服务器日志。 10.保存用户聊天信息。 2.2 系统功能模块 2.2.1 服务器端 1.处理用户注册 2.处理用户登录 3.处理用户发送信息 4.处理用户得到信息 5.处理用户退出 2.2.2 客户端 1.用户注册界面及结果 2.用户登录界面及结果 3.用户发送信息界面及结果 4.用户得到信息界面及结果 5.用户退出界面及结果 2.3 性能需求 运行环境:Windows 9x、2000、xp、2003,Linux 必要环境:JDK 1.5 以上 硬件环境:CPU 400MHz以上,内存64MB以上 3.1.2 客户端结构 ChatClient.java 为客户端程序启动类,负责客户端的启动和退出。 Login.java 为客户端程序登录界面,负责用户帐号信息的验证与反馈。 Register.java 为客户端程序注册界面,负责用户帐号信息的注册验证与反馈。 ChatRoom.java 为客户端程序聊天室主界面,负责接收、发送聊天内容与服务器端的Connection.java 亲密合作。 Windowclose 为ChatRoom.java的内部类,负责监听聊天室界面的操作,当用户退出时返回给服务器信息。 Clock.java 为客户端程序的一个小程序,实现的一个石英钟功能。 3. 2 系统实现原理 当用户聊天时,将当前用户名、聊天对象、聊天内容、聊天语气和是否私聊进行封装,然后与服务器建立Socket连接,再用对象输出流包装Socket的输出流将聊天信息对象发送给服务器端 当用户发送聊天信息时,服务端将会收到客户端用Socket传输过来的聊天信息对象,然后将其强制转换为Chat对象,并将本次用户的聊天信息对象添加到聊天对象集Message,以供所有聊天用户访问。 接收用户的聊天信息是由多线程技术实现的,因为客户端必须时时关注更新服务器上是否有最新消息,在本程序设定的是3秒刷新服务器一次,如果间隔时间太短将会增加客户端与服务器端的通信负担,而间隔时间长就会让人感觉没有时效性,所以经过权衡后认为3秒最佳,因为每个用户都不可能在3秒内连续发送信息。 当每次用户接收到聊天信息后将会开始分析聊天信息然后将适合自己的信息人性化地显示在聊天信息界面上。 4.1.1 问题陈述 1.接受用户注册信息并保存在一个基于文件的对象型数据库。 2.能够允许注册过的用户登陆聊天界面并可以聊天。 3.能够接受私聊信息并发送给特定的用户。 4.服务器运行在自定义的端口上#1001。 5.服务器监控用户列表和用户聊天信息(私聊除外)。 6.服务器踢人,发送通知。 7.服务器保存日志。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值