简单的聊天室程序源码

Server端

package day05.chat;

import java.io.BufferedReader;
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.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 服务端应用程序
 * */
public class Server {
	/**
	 * 运行在服务端的Socket
	 * ServerSocket可以用来接收客户端Socket的链接
	 * */
	private ServerSocket server;
	/**
	 * 客户端的昵称
	 * */
	private String nickName;
	/**
	 * 线程池,用来控制和管理与客户端交互的线程
	 * */
	private ExecutorService threadPool;
	/**
	 * 该集合用来保存当前服务器中所有客户端的输出流
	 * 用来实现广播
	 * */
	private List allOut;
	/**
	 * 构造方法,通常用于做初始化操作
	 * */
		
	public Server(){
		try{
			//初始化用来存放所有客户端输出流的集合
			allOut = new ArrayList();
			
			//初始化ServerSocket
			/**
			 * 向系统申请服务端口8088
			 * 这样将来外界通过网络发送过来的数据
			 * 若请求的是8088端口,那么系统就会将这些数据交给我们的程序去处理
			 * 服务端的端口应该是固定的,都则客户端将无法得知服务端的端口从而导致链接失败
			 * */
			server = new ServerSocket(8088);
			
			//初始化线程池
			threadPool = Executors.newFixedThreadPool(50);
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
	 *下面三个方法:
	 *1.向集合中添加输出流
	 *2.从集合中删除输出流
	 *3.遍历集合
	 *	这三个操作既要做到分别同步,又要做到三者互斥,这是因为多线程需要对这个集合做这三个操作,
	 *对于线程安全的集合而言,自身是可以做到add,remove互斥,并且保证方法同步,
	 *但是无法做到与遍历进行互斥,所以我们需要自己来维护三个操作的互斥关系
	 * /
	
	/**
	 * 将给定的输出流存入共享集合allOut
	 * */
	public synchronized void addOut(PrintWriter pw){
		//将给定的流存入集合
		allOut.add(pw);
	}
	
	/**
	 * 将给定的输出流从共享集合中删除
	 * */
	public synchronized void removeOut(PrintWriter pw){
		//将给定的流从集合中删除
		allOut.remove(pw);
	}
	
	/**
	 * 将给定的消息发送给每一个客户端
	 * */
	public synchronized void sendMessageToAllClient(String message){
		//遍历集合,将给定的字符串发送给每一个输出流,从而达到广播消息的效果
		for(PrintWriter pw:allOut){
			pw.println(message);
		}
	}
	
	/**
	 * 服务端开始服务的方法
	 * */
	public void start(){
		try{
			while(true){
			/**
			 * Socket accept()
			 * ServerSocker的accept方法是一个阻塞方法,调用该方法
			 * 的目的是监听8088端口,直到一个客户端连接上,这时会返回
			 * 用于与该客户端交互的Socket
			 * */
			System.out.println("等待客户端链接...");
			Socket socket = server.accept();
			
			/**
			 * 可以通过Socket获取远端客户端的信息,例如:IP地址以及端口号
			 * */
			InetAddress address = socket.getInetAddress();
			//获取远端计算机ip
			String host = address.getHostAddress();
			//获取远端计算机的端口号
			int port = socket.getPort();
			
			System.out.println("客户端["+host+":"+port+"]链接了...");
			/**
			 * 当客户端链接后,我们启动一个线程,并将该客户端的Socket传入到线程中,使得
			 * 让该线程来完成于当前客户端的交互工作,这样我们就可以再次调用accept方法等待
			 * 其他客户端的链接了
			 * */
			GetClientHandler handler = new GetClientHandler(socket);
//			Thread t = new Thread(handler);
//			t.start();
			/**
			 * 将任务交给线程池
			 * */
			threadPool.execute(handler);
			}
			/**
			 * 服务端这里,通过链接上的客户端的Socket获取输入流
			 * 来接收该客户端发送过来的数据
			 * */
//			InputStream in = socket.getInputStream();
//			InputStreamReader isr = new InputStreamReader(in,"UTF-8");
//			BufferedReader br = new BufferedReader(isr);
//			/**
//			 * 读取客户端发送过来的一行字符串
//			 * */
//			String message = null;
//		
//				if((message = br.readLine())!=null){
//					System.out.println("客户端说:"+message);
//				}
			
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args){
		Server server = new Server();
		server.start();
	}
	
	class GetClientHandler implements Runnable{
		/**
		 * 该线程用于交互的客户端的Socket
		 * */
		private Socket socket;
		public GetClientHandler(Socket socket){
			this.socket = socket;
		}
		
		public void run(){
			PrintWriter pw = null ;
			try{
				/**
				 * 通过Socket获取输出流,用于将消息发送给客户端
				 * */
				OutputStream out = socket.getOutputStream();
				OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
				pw = new PrintWriter(osw,true);
				/**
				 * 将当前线程交互的客户端的输出流存入共享集合,以便可以广播消息给当前客户端
				 * */
				addOut(pw);
				/**
				 * 输出当前在线人数
				 * 使用存放所有输出流的集合的size就可以作为人数的依据
				 * */
				System.out.println("当前在线人数:"+allOut.size());
								
				/**
				 * 通过Socket获取输入流
				 * 然后将输入流转换为缓冲字符输入流
				 * 循环读取该客户端发送的消息
				 * */
				InputStream in = socket.getInputStream();
				InputStreamReader isr = new InputStreamReader(in, "UTF-8");
				BufferedReader br = new BufferedReader(isr);
				
				/**
				 * 首先读取一个字符串,这个字符串是客户端发送过来的昵称
				 * */
				nickName = br.readLine();
				
				String message = null;
				/**
				 * 这里判断读取的内容是不是null是出于对linux与windows底层实现细节的差异,
				 * 当客户端与服务器断开链接后,若客户端是Linux用户,那么服务端的br.readLine()方法会
				 * 返回null,返回null对于缓冲流而言也是表示再没有消息可以读取了,所以就应当停止读取
				 * 工作了,但是windows的用户端断开链接,则br.readLine()方法会直接抛出异常.
				 * */
				while((message=br.readLine()) != null){
//					System.out.println("客户端说:"+message);
//					pw.println(message);
					/**
					 * 当该客户端发送一条消息过来后,我们将该消息转发给所有客户端,达到广播的效果
					 * */
					sendMessageToAllClient(nickName+": "+message);
				}
				
			}catch(Exception e){
			//	e.printStackTrace();
			}finally{
				try{
					/**
					 * 当客户端与服务端断开链接后,先将该客户端的输出流从共享集合中删除停止对该客户端广播消息
					 * */
					removeOut(pw);
					System.out.println("一个客户端下线了.");
					
					/**
					 * 当客户端与服务端断开链接后,
					 * 服务端这边也应当将Socket关闭,以释放资源.
					 * */
					if(socket!=null){
						socket.close();
					}
				}catch(Exception e2){
					e2.printStackTrace();
				}
			}
		}
	}
}

Client端

package day05.chat;

import java.io.BufferedReader;
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;

/**
 * 客户端应用程序
 * */
public class Client {
	/**
	 * 在客户端运行的Socket,用于链接服务端的ServerSocket
	 * */
	private Socket socket;
	
	/**
	 * 构造方法,用于初始化客户端
	 * */
	public Client(){
		try{
			/**
			 * Socket(String host, int port)
			 * 创建Socket使用给定的地址以及端口号并发起链接
			 * */
			socket  = new Socket("192.168.57.82",8088);
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	/**
	 * 客户端开始工作的方法
	 * */
	public void start(){
		try{
			/**
			 * 启动客户端中用来读取服务端发送消息的线程
			 * */
			GetServerMessageHandler handler = new GetServerMessageHandler();
			Thread t = new Thread(handler);
			t.start();
		
			/**
			 * 在客户端,若我们希望向服务端发送数据,
			 * 我们就通过客户端这边的Socket获取输出流,
			 * 然后向输出流写出数据就可以了
			 * */
			OutputStream out = socket.getOutputStream();
			OutputStreamWriter osw = new OutputStreamWriter(out,"UTF-8");
			PrintWriter pw = new PrintWriter(osw,true);
			
			//创建一个Scanner用于获取用户输入
			Scanner scan = new Scanner(System.in);
			
			//提示用户输入昵称
			String nickName = null;
			while(true){
				System.out.println("请输入昵称:");
				nickName = scan.nextLine();
				if(nickName.trim().length()<=0){
					System.out.println("至少输入一个字符");
				}else{
					//进行正则表达式验证
					break;
				}
			}
			
			//将昵称发送至服务器
			pw.println(nickName);
			//输出一个欢迎语
			System.out.println("欢迎你!"+nickName+",开始聊天吧!");
			/**
			 * 将字符串输入到服务端
			 * */
			while(true){
				pw.println(scan.nextLine());
			}
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	public static void main(String[] args){
		Client client = new Client();
		client.start();
	}
	
	/**
	 * 该线程的作用是用于接受服务端发送过来的信息,
	 * 并输出到客户端的控制台上
	 * */
	class GetServerMessageHandler 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 e2){
				e2.printStackTrace();
			}
		}
	}
}


你可能感兴趣的:(编程源代码)