聊天室 - 广播功能

  • 服务端收到信息,群发到客户端

Server

package cn.edut.com.tarena.server;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;

import com.sun.corba.se.impl.orbutil.threadpool.TimeoutException;

public class ChatServer {
	private static ChatServer server  ; 
	private ChatServer() {}
	public static ChatServer getChatServer() {
		return server!=null?server:(server=new ChatServer());
	}
	
	
	private List<CommunicateThread> communicateThreadList = new ArrayList<>();

	class ServerThread extends Thread {
		private int port = 8000;

		@Override
		public void run() {
			try {
				System.out.println("打开服务器,端口号:" + port + "...");
				ServerSocket serverSocket = new ServerSocket(port);
				System.out.println("成功打开服务器。");
				while (true) {
					Socket clientSocket = serverSocket.accept();
					new CommunicateThread(clientSocket).start();
				}
			} catch (Exception e) {
				System.out.println(port + "端口被占用,或者服务异常终止");
				e.printStackTrace();
			}
		}
	}

	class CommunicateThread extends Thread {
		private String clientName;
		private Socket clientSocket;
		private BufferedReader in; // 字符 输入流 缓存 - 主要有 readline() 方法
		private PrintWriter out; // 字符 输出流 换行打印 - 主要println() 方法
		private int waitTime = 5000;

		public CommunicateThread(Socket clientSocket) {
			this.clientSocket = clientSocket;
			try {
				clientSocket.setSoTimeout(30000);
			} catch (SocketException e) {
				System.out.println("服务器接口超时时间设置异常!");
				e.printStackTrace();
			}
		}

		/** * send() 发给当前客户端 */
		private void send(String msg) {
			out.println(msg);
			out.flush();
		}

		/** * sendAll() 群发 */
		private void sendAll(String msg) {
			synchronized(communicateThreadList) {
				String Symbol = "【群发】";
				// 全部客户端,send(msg)
				for (CommunicateThread c : communicateThreadList) {
					c.send(Symbol + msg);
				}
			}
		}

		@Override
		public void run() {
			try {
				in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
				out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"));

				// 接受昵称
				clientName = in.readLine();
				// 发送欢迎信息
				send("欢迎你," + clientName + "。");
				// 把当前通信线程对象,加入list集合
				synchronized(communicateThreadList) {
					communicateThreadList.add(this);
				}
				System.out.println("<<< 昵称:"+clientName +" ID:" + clientSocket.getInetAddress() + ":" + clientSocket.getPort() + "进入 ... 当前连接:"
						+ communicateThreadList.size());
				// 群发上线消息
				sendAll(clientName + "进入聊天室。");
				// 设置新的timeout,等待客户端发送数据
				clientSocket.setSoTimeout(waitTime);
				// 开始聊天
				String line;
				int count = 0 ;
				int deadline = 4; 
				while (true) {
					try {
						line = in.readLine();
						count = 0 ;
					} catch (SocketTimeoutException e) {
						count++ ; 
						if(count==deadline) {
							send("*** 你已被移出聊天室 ***");
							break; 
						}
						send("*** 请活跃参与聊天!("+count+"/"+(deadline-1)+") ***");
						continue;
					}
					if(line==null) {
						break;
					}
					sendAll(clientName+":"+line);
				}

			} catch (Exception e) {
				System.out.println("连接异常,或者readLine()异常");
				e.printStackTrace();
			}

			synchronized (communicateThreadList) {
				// 把当前通信线程对象,移出list
				communicateThreadList.remove(this);
			}
			System.out.println("--->>>" + clientSocket.getInetAddress() + ":" + clientSocket.getPort() + "断开。当前连接数:"
					+ communicateThreadList.size());
			// 群发离线消息
			sendAll(clientName + "退出聊天室。");
		}

	}

	private boolean hasStart = false;
	public void lunch() {
		if(hasStart) {
			System.out.println("服务已启动");
		}
		else {
			new ServerThread().start();
			hasStart=true;
		}
	}
	
	public static void main(String[] args) {
		ChatServer server = ChatServer.getChatServer();
		server.lunch();
	}

}

Client

package cn.edut.com.tarena.server;

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

import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Receiver;

public class Client {
	Socket serverSocket ; 
	BufferedReader in ; 
	PrintWriter out ; 
	String name  ; 
	private static final String SERVER_ID = "127.0.0.1" ; 
	private static final int PORT = 8000 ; 
	
	public void lunch() {
		try {
			this.serverSocket = new Socket(SERVER_ID, PORT);
			in = new BufferedReader(
					new InputStreamReader(serverSocket.getInputStream(), "UTF-8"));
			out = new PrintWriter(
					new OutputStreamWriter(serverSocket.getOutputStream(), "UTF-8"));
			//发送一个名字
			System.out.print("你的名字:");
			this.name = new Scanner(System.in).nextLine();
			out.println(this.name);
			out.flush(); //注意别忘了flush
			
			//接受线程
			Thread receiveThread = new Thread() {
				@Override
				public void run() {
					receiveMSG();
				};
			};
			receiveThread.start();
			//输入线程
			Thread sendThread = new Thread() {
				@Override
				public void run() {
					sendMSG();
				};
			};
			sendThread.start();
			
			receiveThread.join();
			sendThread.join();
			System.out.println("客户端 - 昵称:"+name+" 线程退出");
			
		} catch (Exception e) {
			System.out.println("不能连接服务器");
			e.printStackTrace();
		}
	}
	
	protected void sendMSG() {
		while (true) {
			System.out.print("输入:");
			String msg = new Scanner(System.in).nextLine();
			if(msg.equals("#exit")) {
				try {
					serverSocket.close();
				} catch (IOException e) {
					System.out.println("客户端断开异常。");
					e.printStackTrace();
				}
				break;
			}
			out.println(msg);
			out.flush();
		}
	}

	protected void receiveMSG() {
		try {
			String line ; 
			while((line=in.readLine())!=null) {
				System.out.println(line);
			}
		} catch (Exception e) {
			if(serverSocket.isClosed()) {
			}else {
				System.out.println("接收异常或者与服务端断开");
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		Client client = new Client();
		client.lunch();
	}
}

测试

package cn.edut.com.tarena.server;

public class Problem01 {
	public static void main(String[] args) {
		ChatServer.getChatServer().lunch(); 
		new Client().lunch();
	}
}

聊天室 - 打印阻塞

当多个用户同时说话时,无法舒适的写要打印的内容。
因此,<mark>当客户端输入时,阻塞服务端传来的数据的打印</mark>。

Client

package cn.edut.com.tarena.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;



public class Client {
	Socket serverSocket ; 
	BufferedReader in ; 
	PrintWriter out ; 
	String name  ;
	private boolean isInputting = false ; 
	private Queue<String> messageQueue = new LinkedList<String>();
	private static final String SERVER_ID = "127.0.0.1" ; 
	private static final int PORT = 8000 ; 
	
	public void lunch() {
		try {
			this.serverSocket = new Socket(SERVER_ID, PORT);
			in = new BufferedReader(
					new InputStreamReader(serverSocket.getInputStream(), "UTF-8"));
			out = new PrintWriter(
					new OutputStreamWriter(serverSocket.getOutputStream(), "UTF-8"));
			//发送一个名字
			System.out.print("你的名字:");
			this.name = new Scanner(System.in).nextLine();
			out.println(this.name);
			out.flush(); //注意别忘了flush
			
			//接收线程
			Thread receiveThread = new Thread() {
				@Override
				public void run() {
					receiveMSG();
				};
			};
			receiveThread.setName("receive");
			receiveThread.start();
			
			//打印线程
			Thread printThread = new Thread() {
				@Override
				public void run() {
					PrinterMSG();
				};
			};
			printThread.setName("print");
			printThread.start();
			
			//输出线程
			Thread sendThread = new Thread() {
				@Override
				public void run() {
					sendMSG();
				};
			};
			sendThread.setName("send");
			sendThread.start();
			
			printThread.join();
			receiveThread.join();
			sendThread.join();
			System.out.println("客户端 - 昵称:"+name+" 线程退出");
			
		} catch (Exception e) {
			System.out.println("不能连接服务器");
			e.printStackTrace();
		}
	}
	
	protected void PrinterMSG() {
		while(serverSocket.isConnected()) {
			synchronized(messageQueue) {
				while(messageQueue.isEmpty() || isInputting) {
					try {
						messageQueue.wait();
					} catch (InterruptedException e) {
						System.out.println("队列异常!");
						e.printStackTrace();
					}
				}
				String msg = messageQueue.poll();
				System.out.println(msg);
			}
		}
	}

	protected void sendMSG() {	
		Scanner scanner = new Scanner(System.in);
		System.out.println("按回车输入数据...");
		
		//用户输入聊天内容,发送给服务器
		while (serverSocket.isConnected()) {
			scanner.nextLine() ; 
			isInputting = true;
			
			System.out.print("输入:");
			String msg = scanner.nextLine();
			if(msg.equals("#exit")) {
				try {
					serverSocket.close();
				} catch (IOException e) {
					System.out.println("客户端断开异常。");
					e.printStackTrace();
				}
				break;
			}
			out.println(msg);
			out.flush();
			
			//输入结束,关闭输入开关
			//通知打印线程,这里已经输入结束,可以继续打印
			isInputting = false ; 
			synchronized (messageQueue) {
				messageQueue.notifyAll();
			}
			
		}
	}

	protected void receiveMSG() {
		try {
			String line ; 
			while(serverSocket.isConnected()) {
				line =in.readLine() ; 
				if(line==null) {
					break;
				}
				synchronized (messageQueue) {
					messageQueue.offer(line);
					messageQueue.notifyAll();
				}
			}
		} catch (Exception e) {
			if(serverSocket.isClosed()) {
			}else {
				System.out.println("接收异常或者与服务端断开");
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) {
		Client client = new Client();
		client.lunch();
	}
}

ChatServer

package cn.edut.com.tarena.server;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.List;


public class ChatServer {
	private static ChatServer server  ; 
	private ChatServer() {}
	public static ChatServer getChatServer() {
		return server!=null?server:(server=new ChatServer());
	}
	
	
	private List<CommunicateThread> communicateThreadList = new ArrayList<>();

	class ServerThread extends Thread {
		private int port = 8000;

		@Override
		public void run() {
			try {
				System.out.println("打开服务器,端口号:" + port + "...");
				ServerSocket serverSocket = new ServerSocket(port);
				System.out.println("成功打开服务器。");
				while (true) {
					Socket clientSocket = serverSocket.accept();
					new CommunicateThread(clientSocket).start();
				}
			} catch (Exception e) {
				System.out.println(port + "端口被占用,或者服务异常终止");
				e.printStackTrace();
			}
		}
	}

	class CommunicateThread extends Thread {
		private String clientName;
		private Socket clientSocket;
		private BufferedReader in; // 字符 输入流 缓存 - 主要有 readline() 方法
		private PrintWriter out; // 字符 输出流 换行打印 - 主要println() 方法
		private int waitTime = 500000000;

		public CommunicateThread(Socket clientSocket) {
			this.clientSocket = clientSocket;
			try {
				clientSocket.setSoTimeout(30000);
			} catch (SocketException e) {
				System.out.println("服务器接口超时时间设置异常!");
				e.printStackTrace();
			}
		}

		/** * send() 发给当前客户端 */
		private void send(String msg) {
			out.println(msg);
			out.flush();
		}

		/** * sendAll() 群发 */
		private void sendAll(String msg) {
			synchronized(communicateThreadList) {
				String Symbol = "【群发】";
				// 全部客户端,send(msg)
				for (CommunicateThread c : communicateThreadList) {
					c.send(Symbol + msg);
				}
			}
		}

		@Override
		public void run() {
			System.out.println("<<< "+" ID:" + clientSocket.getInetAddress() + ":" + clientSocket.getPort() +"进入 ... " );
			try {
				in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
				out = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"));

				// 接受昵称
				clientName = in.readLine();
				// 发送欢迎信息
				send("欢迎你," + clientName + "。");
				// 把当前通信线程对象,加入list集合
				synchronized(communicateThreadList) {
					communicateThreadList.add(this);
				}
				// 群发上线消息
				sendAll(clientName + "进入聊天室。"+ " ... 当前连接:"+ communicateThreadList.size());
				// 设置新的timeout,等待客户端发送数据
				clientSocket.setSoTimeout(waitTime);
				// 开始聊天
				String line;
				int count = 0 ;
				int deadline = 4; 
				while (true) {
					try {
						line = in.readLine();
						count = 0 ;
					} catch (SocketTimeoutException e) {
						count++ ; 
						if(count==deadline) {
							send("*** 你已被移出聊天室 ***");
							break; 
						}
						send("*** 请活跃参与聊天!("+count+"/"+(deadline-1)+") ***");
						continue;
					}
					if(line==null) {
						break;
					}
					sendAll(clientName+":"+line);
				}

			} catch (Exception e) {
			}

			synchronized (communicateThreadList) {
				// 把当前通信线程对象,移出list
				communicateThreadList.remove(this);
			}
			System.out.println("--->>>" + clientSocket.getInetAddress() + ":" + clientSocket.getPort() + "断开。当前连接数:"
					+ communicateThreadList.size());
			// 群发离线消息
			sendAll(clientName + "退出聊天室。");
		}

	}

	private boolean hasStart = false;
	public void lunch() {
		if(hasStart) {
			System.out.println("服务已启动");
		}else{
			new ServerThread().start();
			hasStart=true;
		}
	}
	
	public static void main(String[] args) {
		ChatServer server = ChatServer.getChatServer();
		server.lunch();
	}

}

测试结果

<mstyle mathcolor="&#35;ff0011"> </mstyle> \color{ff0011}{**遇到的问题**}

1. 发现问题

这里有一个问题:如果另外开一个客户端,会报错

错误: ***抛出异常错误: java.rmi.server.ExportException: Port already in use: 8011; nested exception is:
java.net.BindException: Address already in use: JVM_Bind

必须放在一个内里面,一起调用Server和Client才能正常运行。

最后,发现我eclipse的配置 “<mark>无法同时启动/运行两个程序</mark>”

2. 问题根源

  1. 多次检验代码,肯定了代码是没问题的。
  2. 然后,用javac编译、java调用发现程序是可运行的。但是,到了eclipse 里面就 有 8011 端口冲突。
  3. 既然是端口号的问题,搜索"eclipse 8011"
  4. 用这篇文章的测试锁定了javaw这个程序https://blog.csdn.net/bbc2005/article/details/94168737

javaw在哪里?

javaw 跟 java 和 javaws 一样是用来启动程序的(在java/bin文件夹里面)。

分别在 jdk 和 jre里面都存在。
因此,猜测是:<mark> <mstyle mathcolor="&#35;ff0011"> j d k j a v a w 8011 </mstyle> \color{#ff0011}{jdk 里面的 javaw 只用一个端口8011端口,导致开第二个程序时候,会有程序端口的冲突} jdkjavaw8011</mark>

因此,尝试修改程序<mark>运行时环境</mark>的配置

原先

修改后

结果

就可以运行了。。。。。。。。。。。。
没有 8011 端口冲突了。。。。。。。。。
没有:错误: ***抛出异常错误: java.rmi.server.ExportException: Port already in use: 8011; nested exception is:
java.net.BindException: Address already in use: JVM_Bind了。。。。。。。。。。

总之,能用就好。。。

<mark> <mstyle mathcolor="&#35;ff0011"> </mstyle> \color{#ff0011}{有人知道 这样配置好不好,为什么,原来配置为什么不行。望告知,请留言!} </mark>