聊天室 - 广播功能
- 服务端收到信息,群发到客户端
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();
}
}
聊天室 - 打印阻塞
当多个用户同时说话时,无法舒适的写要打印的内容。
因此,当客户端输入时,阻塞服务端传来的数据的打印。
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();
}
}
测试结果
问题: eclipse 中的端口冲突
java.rmi.server.ExportException: Port already in use: 8011; nested exception is: java.net.BindException: Address already in use: JVM_Bind
参考: <https://blog.csdn.net/bbc2005/article/details/94168737>
javaw 跟 java 和 javaws 一样是用来启动程序的(在java/bin文件夹里面)。
分别在 jdk 和 jre里面都存在。
因此,猜测是:
j
d
k
里
面
的
j
a
v
a
w
只
用
一
个
端
口
8011
端
口
,
导
致
开
第
二
个
程
序
时
候
,
会
有
程
序
端
口
的
冲
突
\color{#ff0011}{jdk 里面的 javaw 只用一个端口8011端口,导致开第二个程序时候,会有程序端口的冲突}
jdk里面的javaw只用一个端口8011端口,导致开第二个程序时候,会有程序端口的冲突
因此,尝试修改程序运行时环境的配置