使用说明

这个代码是模拟一个简单的QQ,微信群聊,首先运行服务端代码,在运行客户端,每运行一次客户端,都会创建一个用户在聊天室中模拟发言。(注:服务端要一直处于开启状态,不能关闭)

客户端代码

import java.io.BufferedReader;
import java.io.BufferedWriter;
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.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class Client {
	/*
	  * 在整个客户端类中都会使用到Socket对象,所以生命成属性。
	 * Socket内部封装了TCP协议,可以完成安全可靠的数据传输。
	 * 
	  * 客户端向服务端发送数据时,需要使用输出流进行操作。
	 */
	private Socket socket;
	/**
	 * 客户端无参构造器
	 * 目的:用于创建客户端对象时,对socket属性进行初始化
	 * 
	 */
	public Client() {
		try {
			System.out.println("开始连接");
			/*
			 * Socket实质是用于描述IP地址和端口号
			  * 当创建Socket对象时需要调用两个参数的构造器方法
			  * 参数一:需要指定IP(服务端的IP地址)
			  * 参数二:需要指定端口号(服务端的端口号)
			 */
			socket = new Socket("localhost", 8899);
			System.out.println("连接服务端成功");
		} catch (UnknownHostException e) {		
			e.printStackTrace();
		} catch (IOException e) {			
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	  * 向服务端写出数据的方法
	 */
	public void start() {
		/*
		  * 如果需要发送消息,要使用socket对象获取输出流
		  * getOutputStream()方法的返回值要求返回一个OutputStream
		 */
		try {
			//创建接受服务端的消息线程,并添加任务序列
			ServerHandler serverHandler = new ServerHandler();
			Thread thread = new Thread(serverHandler);
			thread.start();
			OutputStream os = socket.getOutputStream();
			//创建字符转换流对象
			OutputStreamWriter osw = new OutputStreamWriter(os, "utf-8");
			//创建缓冲字符输出流对象
			BufferedWriter bw = new BufferedWriter(osw);
			//创建具备自动行数新功能的字符输出流
			PrintWriter pw = new PrintWriter(bw, true);
			//通过控制台接受客户端消息	
			Scanner scanner = new Scanner(System.in);
			while (true) {				
				String message = scanner.nextLine();
				pw.println(message);
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		//创建客户端对象
		Client client = new Client();
		client.start();
	}
	/**
	 * 用于处理服务端发送过来的数据的读取操作
	 */
	class ServerHandler implements Runnable {
		@Override
		public void run() {
			try {
				InputStream is = socket.getInputStream();
				//创建字符转换流对象
				InputStreamReader isr = new InputStreamReader(is,"utf-8");
				//创建缓冲字符输入流对象
				BufferedReader br = new BufferedReader(isr);
				//读取数据,按行进行读取
				String message;
				while ((message = br.readLine()) != null) {
					System.err.println(message);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			
		}
	}
}

服务端代码

import java.io.BufferedReader;
import java.io.BufferedWriter;
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.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
public class Server {
	/*
	  * 声明服务端ServerSocket对象
	 */
	private ServerSocket serverSocket;
	
	/*
	 * 定义一个输出流数组,保存的是指向各个客户端的输出流对象
	 */
	private PrintWriter[] allout = {};
	
	/**
	 * 服务端无参构造器
	 * 目的:用于创建服务端对象时,对serverSocket属性进行初始化
	 * 
	 */
	public Server() {
		try {
			System.out.println("服务端正在启动");
			serverSocket = new ServerSocket(8899);
			System.out.println("服务端启动完毕");
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	/**
	  * 服务端接收客户端消息的方法
	 */
	public void start() {
		/*
		 * Socket accept()方法是一个阻塞方法,
		  * 循环等待客户端连接,如果没有客户端连接就一直在这等着。
		 */
		try {
			while (true) {
				System.out.println("等待接收客户端连接");
				Socket socket = serverSocket.accept();
				System.out.println("一个客户端已经连接");
				
				/*
				 * 创建任务序列对象
				 * 创建线程对象并添加任务序列
				 * 开启线程
				 * 目的:等待客户端连接以后,可以开启一条线程,
				 * 在新的线程中用于处理客户端连接后的一些列操作
				 */
				ClientHandler clientHandler = new ClientHandler(socket);
				Thread thread = new Thread(clientHandler);
				thread.start();
			}
		} catch (IOException e) {			
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		//创建服务端对象
		Server server = new Server();
		server.start();
	}
	/**
	 * 用于处理客户端请求的内部类,
	 * 主要的职责是充当线程的任务序列,
	 * 在任务序列中,主要做获取输入流对象,循环判断,
	 * 及其信息输出的操作。
	 * @author Administrator
	 *
	 */
	class ClientHandler implements Runnable{
		//客户端发送过来的Socket对象
		Socket socket;
		//客户端的地址信息
		String host;
		
		public ClientHandler(Socket socket) {
			this.socket = socket;
			this.host = socket.getInetAddress().getHostAddress();
		}
		@Override
		public void run() {
			//字符输出流局部变量
			PrintWriter pw = null;
			try {
				/*
				 * 如果需要从服务端打印客户端传输的信息,
				 * 需要从Socket中获取输入流
				 */
				InputStream is = socket.getInputStream();
				//创建字符转换流对象
				InputStreamReader isr = new InputStreamReader(is,"utf-8");
				//创建缓冲字符输入流对象
				BufferedReader br = new BufferedReader(isr);
				
				/*
				 * 获取Socket中的输出流对象,用于给客户端写出数据
				 */
				OutputStream os = socket.getOutputStream();
				OutputStreamWriter osw = new OutputStreamWriter(os,"utf-8");
				BufferedWriter bw = new BufferedWriter(osw);
				pw = new PrintWriter(bw,true);
	
				//将输出流对象添加到allout输出流数组中
				synchronized (allout) {
					//对原数进行扩容
					allout = Arrays.copyOf(allout, allout.length+1);
					//对数组中的最后一个元素进行赋值
					allout[allout.length-1] = pw;
				}
				//在控制台中打印当前在线人数
				System.out.println(host+"上线了,当前在线人数为:"+allout.length);
				//读取数据,按行进行读取
				String message;
				while ((message = br.readLine()) != null) {
					System.err.println(host+"说:"+message);
					//读取消息以后就向客户端写出
					synchronized (allout) {
						//遍历输出流数组,向所有的客户端输出数据
						for (int i = 0; i < allout.length; i++) {
							allout[i].println(host+"说了:"+message);
						}
					}
				}
			}  catch (Exception e) {	
				/*
				 * 如果停掉客户端会发生异常,可以在当前的catch块中
				 * 进行处理删除发送给当前已经停止掉的客户端的输出流对象
				 */
				synchronized (allout) {
					for (int i = 0; i < allout.length; i++) {
						if (allout[i] == pw) {
							/*
							 * 删除的原则是找到需要移除的输出流对象,
							 * 然后将输出流数组中最后一个输出流对象
							 * 赋值给当前删除的输出流对象的下标位置。
							 * 最后缩容数组
							 */
							allout[i] = allout[allout.length-1];
							allout = Arrays.copyOf(allout, allout.length-1);
							//结束循环
							break;
						}
					}
				}
			} finally {
				System.out.println(host+"下线了,当前在线人数:"+allout.length);
				try {
					//关闭Socket资源
					socket.close();
				} catch (IOException e) {
				}
			}
		}
	}
}