java简单Socket聊天室程序的实现

服务器端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务端应用程序
 * @author huawangxin
 *
 * 2016年11月23日 上午1:44:47
 */
public class Server {
	// 运行在服务端的Socket
	private ServerSocket server;
	// 线程池,用于管理客户端连接的交互线程
	private ExecutorService threadPool;
	// 保存所有客户端输出流的集合
	private HashMap<String, PrintWriter> allout;

	/**
	 * 构造方法,用于初始化服务端
	 * 
	 * @throws IOException
	 */
	public Server() throws IOException {
		try {
			/*
			 * 创建ServerSocket时需要指定服务端口
			 */
			System.out.println("初始化服务端");
			server = new ServerSocket(8088);

			// 初始化线程池
			threadPool = Executors.newFixedThreadPool(50);

			// 初始化存放所有客户端输出流的集合
			allout = new HashMap<String, PrintWriter>();

			System.out.println("服务端初始化完毕");
		} catch (IOException e) {
			e.printStackTrace();
			throw e;
		}
	}

	/**
	 * 服务端开始工作的方法
	 */
	public void start() {
		try {
			/*
			 * ServerSocket的accept方法 用于监听8088端口,等待客户端的连接 该方法是一个阻塞方法,直到一个
			 * 客户端连接,否则该方法一直阻塞。 若一个客户端连接了,会返回该客户端的 Socket
			 */
			while (true) {
				System.out.println("等待客户端连接...");
				Socket socket = server.accept();
				/*
				 * 当一个客户端连接后,启动启动一个线程 ClientHandler,将该客户端的Socket 传入,使得该线程与该
				 * 客户端交互 这样我们能再次进入循环,接收下一个客户端的连接
				 */
				Runnable handler = new ClientHandler(socket);
				// Thread t = new Thread(handler) ;
				// t.start();
				threadPool.execute(handler);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * 将给定的输出流共享集合
	 * 
	 * @param p
	 */
	public synchronized void addout(String nickName, PrintWriter p) {
		allout.put(nickName, p);
	}

	/**
	 * 将给定的输出流从共享集合中删除
	 * 
	 * @param p
	 */
	public synchronized void removeOut(String nickName) {
		allout.remove(nickName);
	}

	/**
	 * 将给定的消息转发给客户端
	 * 
	 * @param message
	 */
	public synchronized void sendMessage(String message) {
		Set<Entry<String, PrintWriter>> set = allout.entrySet();
		for (Entry<String, PrintWriter> e : set) {
			e.getValue().println(message);
		}

	}

	public static void main(String[] args) {
		Server server;
		try {
			server = new Server();
			server.start();
		} catch (IOException e) {
			e.printStackTrace();
			System.out.println("服务端初始化失败");
		}

	}

	/*
	 * 服务器中的一个线程,用于与某个客户端 交互 使用线程的目的是使得服务端可以处理多客户端了。
	 */

	class ClientHandler implements Runnable {
		// 当前线程处理的客户端的Socket、
		private Socket socket;
		// 当前客户端的ip
		private String ip;
		// 当前客户端的昵称
		private String nickname;

		public ClientHandler(Socket socket) {
			this.socket = socket;
			InetAddress address = socket.getInetAddress();
			// 获取远端计算机的IP地址
			ip = address.getHostAddress();
			// address.getCanonicalHostName()
			// 获取客户端的端口号
			int port = socket.getPort();
			System.out.println(ip + ":" + port + " 客户端连接了");
			// 改为了使用昵称了,所以不在这里通知了
			// //通知其他用户,该用户上线了
			// sendMessage("["+ip+"]上线了");
		}

		/*
		 * 该线程将当前Socket中的输入流获取 用来循环读取客户端发送过来的消息 (non-Javadoc)
		 * 
		 * @see java.lang.Runnable#run()
		 */
		public void run() {
			/*
			 * 定义在try语句外的目的是,为了在finally中也可以引用到
			 */
			PrintWriter pw = null;
			try {
				/*
				 * 为了让服务端与客户端发送信息 我们需要通过socket 获取输出流。
				 */
				OutputStream out = socket.getOutputStream();
				OutputStreamWriter osw = new OutputStreamWriter(out, "UTF-8");
				pw = new PrintWriter(osw, true);

				/*
				 * 将客户端的输出流存入共享集合 以便是的客户端也能接收服务端转发的消息
				 */
				// addout(pw);
				// Scanner sc = new Scanner(System.in) ;
				//
				// System.out.println(sc.nextLine());
				/*
				 * 通过socket获取远端的地址信息 对于服务端而言,远端就是客户端了
				 */

				/*
				 * 通过刚刚连上的客户端的Socket获取 输入流,来读取客户端发送过来的信息
				 */
				InputStream in = socket.getInputStream();
				/*
				 * 将字节输入流包装为字符输出流,这样 可以指定编码集来读取每一个字符
				 */
				InputStreamReader isr = new InputStreamReader(in, "UTF-8");
				/*
				 * 将字符流转换为缓冲字符输入流 这样就可以以行为单位读取字符串了
				 */
				BufferedReader br = new BufferedReader(isr);

				/*
				 * 当创建好当前客户端的输入流后 读取的第一个字符串,应当是昵称
				 */
				nickname = br.readLine();
				addout(nickname, pw);
				// 通知所有客户端,当前用户上线了
				sendMessage("[" + nickname + "]上线了");

				String message = null;
				// 读取客户端发送过来的一行字符串
				/*
				 * 读取客户端发送过来的信息这里 windows与linux存在一定的差异: linux:当客户端与服务端断开连接后
				 * 我们通过输入流会读取到null 但这是合乎逻辑的,因为缓冲流的 readLine()方法若返回null就
				 * 表示无法通过该留再读取到信息。 参考之前服务文本文件的判断。
				 * 
				 * windows:当客户端与服务端断开连接后 readLine()方法会抛出异常。
				 */
				while ((message = br.readLine()) != null) {
					message = hexiestr(message);
					// System.out.println(
					// "客户端说:" + message);
					// pw.println("服务端说:"+message) ;
					/*
					 * 当读取客户端发送过来的一条消息后,将该消息转发给所有客户端
					 */
					// for (PrintWriter o : allout) {
					// o.println(message);
					// }
					String siliao = siliaostr(message);
					if (allout.containsKey(siliao)) {
						message = message.substring(message.indexOf(":") + 1);
						allout.get(siliao).println(nickname + "对你说:" + message);
					} else {
						sendMessage(nickname + "说:" + message);
					}
				}
			} catch (Exception e) {
				// 在windows中的客户顿,
				// 报错通常是因为客户端断开了连接
			} finally {
				/*
				 * 首先将该客户端的输出流从共享集合中删除
				 */
				removeOut(nickname);
				// 输出当前在线人数
				System.out.println("当前在线人数为:" + allout.size());
				// 通知其他用户,该用户下线了
				sendMessage("[" + nickname + "]下线了");
				/*
				 * 无论是Linux 用户还是windows用户,当予服务端断开连接后、、、 我们都应该在服务端也与客户端断开连接
				 */
				try {
					socket.close();
				} catch (IOException e) {
					e.printStackTrace();
					System.out.println("一个客户端下线了..");
				}
			}
		}
	}
	public String siliaostr(String str) {
		int fir = str.indexOf("\\") + 1;
		int las = str.indexOf(":");
		if (fir <= 0 || las <= 0) {
			return str;
		} else {
			String sub = str.substring(fir, las);
			return sub;
		}
	}
	public String hexiestr(String str) {
		String s = str.replaceAll("(sb|cao|ca)", "**");
		return s;
	}

}

客户端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

/**
 * 客户端应用程序
 * @author huawangxin
 *
 * 2016年11月23日 上午1:48:11
 */
public class Client {
	//Socket,用于连接服务端的ServerSocket
	private Socket socket;
	private Scanner scanner;
	/*
	 * 客户端构造方法,用于初始化客户端
	 */
	public Client() throws IOException{
		try {
			/*
			 * 创建Socket对象时,
			 * 就会尝试根据给定的地址与端口连接服务端。
			 * 所以,若该对象创建成功,说明与服务端连接正常。
			 */
			System.out.println("正在连接服务端。。");
			socket=new Socket("127.0.0.1",8088);
			System.out.println("成功连接服务端");
		}catch (IOException e) {
			throw e;
		}
	}
	/*
	 * 客户端启动方法
	 */
	public void start(){
		try {
			//创建并启动线程,来接收服务端的消息
			Runnable runn=new GetServerInfoHandler();
			Thread t=new Thread(runn);
			t.start();
			
			
			/*
			 * 可以通过Socket的getOutputStream()
			 * 方法获得一条输出流,用于将信息发送至服务端
			 */
			OutputStream out=socket.getOutputStream();
			/*
			 * 使用字符流来根据指定的编码集将字符串转换为字节后,
			 * 在通过out发送给服务端
			 */
			OutputStreamWriter osw=new OutputStreamWriter(out,"UTF-8");
			/*
			 * 将字符流包装为缓冲字符流,就可以按行为单位写出字符串
			 */
			PrintWriter pw=new PrintWriter(osw,true);
			/*
			 * 创建一个Scanner,用于接收用户输入的字符串
			 */
			scanner = new Scanner(System.in);
			//输出欢迎用语
			System.out.println("欢迎来到Linus聊天室");
			while(true){
			//首先输入昵称
			System.out.println("请输入昵称");
			String nickname=scanner.nextLine();
			if(nickname.trim().length()>0){
				pw.println(nickname);
				break;
				}
				System.out.println("昵称不能为空");
			}
			while(true){
				String str=scanner.nextLine();
				pw.println(str);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String [] args){
		try {
			Client client=new Client();
			client.start();
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println("客户端初始化失败");		
		}
	}
	/*
	 * 该线程的作用是循环接受服务端发送过来的信息,并输出待控制台
	 */
	class GetServerInfoHandler implements Runnable{
		public void run() {
			try {
				/*
				 * 通过Socket获取输入流
				 */
				InputStream in=socket.getInputStream();
				//将输入流转化为字符输入流,指定编码
				InputStreamReader isr=new InputStreamReader(in,"UTF-8");
				//将字符输入流转换为缓冲流
				BufferedReader br=new BufferedReader(isr);
				String message=null;
				//循环读取服务端发送的每一个字符串
				while((message=br.readLine())!=null){
					//将服务端发送的字符串输出到控制台
					System.out.println(message);
				}
			} catch (Exception e) {
				
			}
		}
	}
	
}


上述如果有不对或者补充的地方,请大家批评和指教,谢谢。

java聊天室程序源码 2 需求分析 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.服务器保存日志。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值