8.14 Java聊天室

基本逻辑

  1. 服务器充当的角色:服务器应能接受客户端的连接;服务器本身一般不产生信息只转发信息。
    1.1 连接:服务器应提供IP和端口号;
    1.2 连接:允许多个客户端连接一个服务器,使用一个服务器转发信息。需要使用线程,为每个客户端构建一个线程。
    1.3 转发:对于每条流入服务器的信息,服务器只做接收和转发。
  2. 客户端角色:客户端可以连接服务器;可以实现发送信息和接收信息。
    2.1 连接:拥有服务器的IP的对应的端口号即可连接
    2.2 发送:可以实现向服务器发送信息,然后指定接受信息的客户端,使服务器转发信息给此客户端。发送是独立随机的,应为其建立线程。
    2.3 接受:接受服务器转发来的信息。接收是随机的,需要不断的监听接收服务器发送的信息,应为其建立线程。
  3. 难点:线程的使用。

单人自言自语版

特点
      一个客户端可连接到服务器端,客户端可以发送信息给服务器端,也可以接受服务器端发送的信息。但只能发送一条信息,接收一条信息,即发送与接受不独立。
      服务器端只能接收一个客户端的接入,然后将该客户端发送的信息在转发给该客户端。
代码
服务器端

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务器:可与一个客户端重复收发消息
 * @author dxt
 *
 */
public class MultiChat {
	
	public static void main(String[] args) throws IOException{
		//1.使用ServerSocket建立服务器端
		ServerSocket server = new ServerSocket(8888);	//指定端口
		
		//2.使用accept() 接收连入的客户, 程序会一直阻塞在此,直到有客户连入才会向下执行
		Socket client = server.accept();
		System.out.println("一个客户端已连入");
		
		//3. 获取接入客户的输入输出流
		DataInputStream dis = new DataInputStream(client.getInputStream());
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		//4. 循环转发消息
		boolean isEnd = false;
		while(!isEnd){
			//4.1 输入流获取 客户发送的信息
			String msg = dis.readUTF();
		
			//4.输出流 转发信息 到客户端
			dos.writeUTF(msg);
			dos.flush();
		}
		
		//5.释放资源
		dos.close();
		dis.close();
		client.close();
		server.close();
	}
}

客户端

import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客户端
 * 可与服务器重复收发消息
 * @author dxt
 *
 */
public class MultiClient {
	public static void main(String[] args) throws UnknownHostException, IOException{
		//1. 建立客户端Socket, 指明IP和端口
		Socket client = new Socket("localhost", 8888);
		
		//2. 接收控制台信息
		BufferedReader console = new BufferedReader(new InputStreamReader(System.in));

		//3. 获取client的输入输出流
		DataOutputStream dos = new DataOutputStream(client.getOutputStream());
		DataInputStream dis = new DataInputStream(client.getInputStream());
		
		//4.循环接收发送消息
		boolean isEnd = false;
		while(!isEnd){
			//接收控制台输入的信息
			String msg = console.readLine();
			
			//4.1  发送信息
			dos.writeUTF(msg);
			dos.flush();
		
			//4.2  接受信息
			msg = dis.readUTF();
			System.out.println("接收信息:" + msg);
		}
		
		
		//4.释放资源
		dis.close();
		dos.close();
		client.close();
	}
}

多人自言自语版

特点
      一个客户端可连接到服务器端,可以独立进行发送和接收信息。
      服务器可接受多个客户端的连接,但只能将客户端发送的的消息再转发给它自己。
      注意:服务器是如何处理多个客户端的连接的;客户端是如何实现发送与接收信息独立的。
      注意:存在很多bug。
代码
服务器端:

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/**
 * 服务器:
 * 目标:实现 多个客户可以收发信息
 * 可实现多人自言自语
 * 目标:封装
 * @author dxt
 *
 */
public class MultiChat {
	
	public static void main(String[] args) throws IOException{
		//1.使用ServerSocket建立服务器端
		ServerSocket server = new ServerSocket(8888);	//指定端口
		
		//2.while(): 不断接收客户端
		while(true){
			Socket client = server.accept();
			System.out.println("一个客户端建立了连接");
			
			//为连入的客户建立线程,不断的接受发送信息
			new Thread(new Channel(client)).start();
		}
	}
	/*
	 * 一个客户代表一个Channel
	 */
	static class Channel implements Runnable{
		private DataOutputStream dos;
		private DataInputStream dis;
		private Socket client;
		private boolean isRunning;
		
		public Channel(){
		}
		public Channel(Socket client){
			this.client = client;
			try {
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
				isRunning = true;
			} catch (IOException e) {
				System.out.println("Client");
				release();	//直接释放
			}
		}
		//接收消息
		private String receive(){
			String msg = "";
			try {
				msg = dis.readUTF();
			} catch (IOException e) {
				System.out.println("receive");
				release();	//直接释放
			}
			return msg;
		}
		//发送消息
		private void send(String msg){
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				System.out.println("send");
				release();	//直接释放
			}
		}
		//释放资源
		private void release(){
			this.isRunning = false;
			Utils.close(dos, dis, client);	//释放资源
		}
		
		/**
		 * 实现run方法
		 * 在一个线程中要做什么?
		 * 接收消息->发送消息
		 */
		public void run(){
			while(isRunning){
				//接收信息
				String msg = receive();
				//如果信息不为空,则发送信息
				if(!msg.equals("")){
					send(msg);
				}	
			}
		}
	}
}

客户端:

import java.io.IOException;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客户端
 * 可与服务器重复收发消息
 * @author dxt
 *
 */
public class MultiClient {
	public static void main(String[] args) throws UnknownHostException, IOException{
		//1. 建立客户端Socket, 指明IP和端口
		Socket client = new Socket("localhost", 8888);
		
		//2. 不断发送消息
		//不停 这个属性有Send类中的isRunning来控制
		new Thread(new Send(client)).start();
		
		//3. 不断接收消息
		new Thread(new Receive(client)).start();
	}
}

Send类:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * 接收端 发送信息线程
 * 给客户端开一个不断接收信息的线程
 * @author dxt
 *
 */
public class Send implements Runnable {
	private Socket client;		//此发送线程为那个客户端服务
	private BufferedReader console;		//从控制台接受信息
	private DataOutputStream dos;	//客户端的 输出流(发送)
	private boolean isRunning;
	
	public Send(Socket client){
		super();
		this.client = client;
		this.console = new BufferedReader(new InputStreamReader(System.in));
		try {
			this.dos = new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			System.out.println("Send");
			this.release();	//释放资源
		}
		this.isRunning = true;
	}
	/**
	 * 资源释放
	 */
	private void release(){
		this.isRunning = false;
		Utils.close(dos, client);	
	}
	/**
	 * 获取控制台输入的信息
	 * @return
	 */
	private String getInfo(){
		String msg = null;
		try {
			msg = console.readLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return msg;
	}
	
	private void send(String msg){
		try {
			dos.writeUTF(msg);
			dos.flush();
		} catch (IOException e) {
			System.out.println("send");
			release();
		}
	}
	/**
	 * 线程执行操作
	 */
	public void run(){
		while(isRunning){
			//1. 从控制台获取信息
			String msg = this.getInfo();
			//2. 如果信息不为空,则发送
			if(!msg.equals("")){
				this.send(msg);
			}
			
		}
	}
}

Receive类

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 客户端的 接收消息线程
 * @author dxt
 *
 */
public class Receive implements Runnable{
	private Socket client;
	private DataInputStream dis;
	private boolean isRunning;
	
	public Receive(Socket client){
		this.client = client;
		try {
			dis = new DataInputStream(client.getInputStream());
			this.isRunning = true;
		} catch (IOException e) {
			System.out.println("Receive");
			release();
		}
	}
	/**
	 * 资源释放
	 */
	private void release(){
		this.isRunning = false;
		Utils.close(dis, client);
	}
	/**
	 * 客户端接收消息
	 */
	private String receive(){
		String msg = "";
		try {
			msg = dis.readUTF();
		} catch (IOException e) {
			System.out.println("receive");
			release();
		}
		return msg;
	}
	/**
	 * 线程不断接收信息
	 */
	public void run(){
		while(isRunning){
			String msg = receive();
			if(!msg.equals("")){
				System.out.println("receive:" + msg);
			}
		}
	}
}

Utils类:

import java.io.Closeable;

/**
 * 工具类
 * 用于释放资源
 * @author dxt
 *
 */
public class Utils {
	public static void close(Closeable... targets){
		for(Closeable target : targets){
			try{
				if(null != target){
					target.close();
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}

群聊版

特点
      能够实现一个客户端向所有连接到此服务器上的客户发送信息。
代码
服务器端:

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;


/**
 * 在线聊天室:服务器
 * 目标:加入容器实现群聊
 * @author dxt
 *
 */
public class Chat {
	//添加容器  CopyOnWriteArrayList容器是为了解决并发问题
	private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
	
	public static void main(String[] args) throws IOException{
		//1.使用ServerSocket建立服务器端
		ServerSocket server = new ServerSocket(8888);	//指定端口
				
		//2.while(): 不断接收客户端
		while(true){
			Socket client = server.accept();
			System.out.println("一个客户端建立了连接");
					
			//为连入的客户建立线程,不断的接受发送信息
			Channel c = new Channel(client);
			all.add(c);
			new Thread(c).start();
			
			//做个统计
			System.out.println(c.name + "加入了聊天室");
			System.out.println("共有 "+ all.size() + " 人在线。");
		}
	}
	
	/*
	 * 一个客户代表一个Channel
	 */
	static class Channel implements Runnable{
		private DataOutputStream dos;
		private DataInputStream dis;
		private Socket client;
		private boolean isRunning;
		private String name;
		
		public Channel(){
		}
		public Channel(Socket client){
			this.client = client;
			try {
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
				isRunning = true;
				name = receive();
			} catch (IOException e) {
				System.out.println("Client");
				release();	//直接释放
			}
		}
		//接收消息
		private String receive(){
			String msg = "";
			try {
				msg = dis.readUTF();
			} catch (IOException e) {
				System.out.println("receive");
				release();	//直接释放
			}
			return msg;
		}
		//自言自语:自己发送消息给自己
		private void send(String msg){
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				System.out.println("send");
				release();	//直接释放
			}
		}
		//群聊:发给其他所有人
		private void sendOthers(String msg){
			for(Channel other : all){
				if(other == this){
					continue;
				}
				other.send(this.name+ ": " + msg);	//发给其他人
			}
		}
		//释放资源
		private void release(){
			this.isRunning = false;
			Utils.close(dos, dis, client);	//释放资源
			//退出
			all.remove(this);
			System.out.println(this.name + "离开了群聊");
		}
		
		/**
		 * 实现run方法
		 * 在一个线程中要做什么?
		 * 接收消息->发送消息
		 */
		public void run(){
			while(isRunning){
				//接收信息
				String msg = receive();
				//如果信息不为空,则发送信息
				if(!msg.equals("")){
					sendOthers(msg);	//群聊模式
				}	
			}
		}
	}
}

客户端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import java.net.UnknownHostException;

/**
 * 客户端
 *  目标:加入容器实现群聊
 * @author dxt
 *
 */
public class Client {
	public static void main(String[] args) throws UnknownHostException, IOException{
		System.out.println("------client------");
		//1. 建立客户端Socket, 指明IP和端口
		Socket client = new Socket("localhost", 8888);
		
		//设置客户端姓名
		System.out.println("请输入姓名:");
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String name = br.readLine();
		
		//2. 不断发送消息
		//不停 这个属性有Send类中的isRunning来控制
		new Thread(new Send(client, name)).start();
				
		//3. 不断接收消息
		new Thread(new Receive(client)).start();
	}
}

Send类:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

/**
 * 接收端 发送信息线程
 * 给客户端开一个不断接收信息的线程
 * @author dxt
 *
 */
public class Send implements Runnable {
	private Socket client;		//此发送线程为那个客户端服务
	private BufferedReader console;		//从控制台接受信息
	private DataOutputStream dos;	//客户端的 输出流(发送)
	private boolean isRunning;
	private String name;
	
	public Send(Socket client, String name){
		super();
		this.client = client;
		this.console = new BufferedReader(new InputStreamReader(System.in));
		this.name = name;
		try {
			this.dos = new DataOutputStream(client.getOutputStream());
			//在初始化时 发送名称
			send(name);
		} catch (IOException e) {
			System.out.println("Send");
			this.release();	//释放资源
		}
		this.isRunning = true;
	}
	/**
	 * 资源释放
	 */
	private void release(){
		this.isRunning = false;
		Utils.close(dos, client);	
	}
	/**
	 * 获取控制台输入的信息
	 * @return
	 */
	private String getInfo(){
		String msg = null;
		try {
			msg = console.readLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return msg;
	}
	
	private void send(String msg){
		try {
			dos.writeUTF(msg);
			dos.flush();
		} catch (IOException e) {
			System.out.println("send");
			release();
		}
	}
	/**
	 * 线程执行操作
	 */
	public void run(){
		while(isRunning){
			//1. 从控制台获取信息
			String msg = this.getInfo();
			//2. 如果信息不为空,则发送
			if(!msg.equals("")){
				this.send(msg);
			}
			
		}
	}
}

Receive类:

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 客户端的 接收消息线程
 * @author dxt
 *
 */
public class Receive implements Runnable{
	private Socket client;
	private DataInputStream dis;
	private boolean isRunning;
	
	public Receive(Socket client){
		this.client = client;
		try {
			dis = new DataInputStream(client.getInputStream());
			this.isRunning = true;
		} catch (IOException e) {
			System.out.println("Receive");
			release();
		}
	}
	/**
	 * 资源释放
	 */
	private void release(){
		this.isRunning = false;
		Utils.close(dis, client);
	}
	/**
	 * 客户端接收消息
	 */
	private String receive(){
		String msg = "";
		try {
			msg = dis.readUTF();
		} catch (IOException e) {
			System.out.println("receive");
			release();
		}
		return msg;
	}
	/**
	 * 线程不断接收信息
	 */
	public void run(){
		while(isRunning){
			String msg = receive();
			if(!msg.equals("")){
				System.out.println(msg);
			}
		}
	}
}

Utils类:

import java.io.Closeable;

/**
 * 工具类
 * 用于释放资源
 * @author dxt
 *
 */
public class Utils {
	public static void close(Closeable... targets){
		for(Closeable target : targets){
			try{
				if(null != target){
					target.close();
				}
			}catch(Exception e){
				e.printStackTrace();
			}
		}
	}
}

私聊版

特点
      在群聊的基础上加入了私聊。如果以固定格式发送信息则会进行私聊,否则会是群聊。
      代码在群聊版的基础上只对服务器端做了修改。
代码

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.CopyOnWriteArrayList;


/**
 * 在线聊天室:服务器
 * 目标:实现私聊
 * @author dxt
 *
 */
public class Chat {
	//添加容器  CopyOnWriteArrayList容器是为了解决并发问题
	private static CopyOnWriteArrayList<Channel> all = new CopyOnWriteArrayList<Channel>();
	
	public static void main(String[] args) throws IOException{
		//1.使用ServerSocket建立服务器端
		ServerSocket server = new ServerSocket(8888);	//指定端口
				
		//2.while(): 不断接收客户端
		while(true){
			Socket client = server.accept();
			System.out.println("一个客户端建立了连接");
					
			//为连入的客户建立线程,不断的接受发送信息
			Channel c = new Channel(client);
			all.add(c);
			new Thread(c).start();
			
			//做个统计
			System.out.println(c.name + "加入了聊天室");
			System.out.println("共有 "+ all.size() + " 人在线。");
		}
	}
	
	/*
	 * 一个客户代表一个Channel
	 */
	static class Channel implements Runnable{
		private DataOutputStream dos;
		private DataInputStream dis;
		private Socket client;
		private boolean isRunning;
		private String name;
		
		public Channel(){
		}
		public Channel(Socket client){
			this.client = client;
			try {
				dis = new DataInputStream(client.getInputStream());
				dos = new DataOutputStream(client.getOutputStream());
				isRunning = true;
				name = receive();
			} catch (IOException e) {
				System.out.println("Client");
				release();	//直接释放
			}
		}
		//接收消息
		private String receive(){
			String msg = "";
			try {
				msg = dis.readUTF();
			} catch (IOException e) {
				System.out.println("receive");
				release();	//直接释放
			}
			return msg;
		}
		//发送消息给自己
		private void send(String msg){
			try {
				dos.writeUTF(msg);
				dos.flush();
			} catch (IOException e) {
				System.out.println("send");
				release();	//直接释放
			}
		}
		/**
		 * 群聊:发给其他所有人
		 * 私聊:约定数据格式:@xxx:msg
		 * @param msg
		 */
		private void sendOthers(String msg){
			boolean isPrivate = msg.startsWith("@"); //判断是否是私聊
			
			if(!isPrivate){		//群发模式
				for(Channel other : all){
					if(other == this){
						continue;
					}
					other.send(this.name+ ": " + msg);	//发给其他人
				}
			}else{		//私聊模式
				//获取目标和数据
				int index  = msg.indexOf(":");
				String targetName = msg.substring(1, index);	//截取姓名
				msg = msg.substring(index+1, msg.length());		//获取信息
				
				//遍历客户端
				for(Channel other : all){
					if(other.name.equals(targetName)){
						other.send(this.name + ":" + msg);
					}
				}
			}
			
		}
		//释放资源
		private void release(){
			this.isRunning = false;
			Utils.close(dos, dis, client);	//释放资源
			//退出
			all.remove(this);
			System.out.println(this.name + "离开了群聊");
		}
		
		/**
		 * 实现run方法
		 * 在一个线程中要做什么?
		 * 接收消息->发送消息
		 */
		public void run(){
			while(isRunning){
				//接收信息
				String msg = receive();
				//如果信息不为空,则发送信息
				if(!msg.equals("")){
					sendOthers(msg);	//群聊模式
				}	
			}
		}
	}
}
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页