基于连接通信Socket、多线程的Java聊天室
1、开发环境:
IDEA2018.1+JDK1.8
2、实现功能:
实现了模拟登录注册、群聊、私聊、显示当前在线人数列表;
在发送信息时,会向对方发送者及显示发送时间;
显示在线人数列表时,也会显示查询时间;
实现了多线程发送消息、接收消息过程。
3、代码解析(源码可见本篇博客最后):
1)客户端源码解析
客户端使用两个线程操作
一个读线程:创建客户端输入流,while循环将读到的信息输出道控制台,模拟一直等待监听输入的情况;
一个写线程:创建客户端输出流,while循环获取用户从控制台输入的内容,若用户输入的信息中包含"bye",则关闭流并且关闭该用户的Socket,此时退出while循环,写线程结束;此时读线程的if条件判断就会执行break;while循环停止,读线程结束。
2)服务器源码解析
利用Executors类创建固定大小为20的线程池(实现多线程);
使用Map集合来存储用户信息,<String, Socket>:用户名,客户端的socket;Map集合使用子类ConcurrentHashMap来实例化,保证线程安全(主要可见Java集合总结);
使用内部类来处理客户端的连接与发送信息;
根据控制台约束信息进行用户注册、私发、群聊等等功能的实现
3)两台计算机之间使用套接字建立TCP连接过程:
服务器实例化一个ServerSocket对象,new ServerSocket(6666):表示是通过服务器上的6666端口进行通信;
服务器调用ServerSocket类的accept(),该方法阻塞式等待客户端的连接;
客户端实例化一个Socket对象,new Socket("127.0.0.1", 6666):指定服务器名称和端口号来请求连接,该构造方法试图将客户端连接到指定的服务器和端口号,如果通信建立,则会在客户端创建Socket对象能够与服务器进行通信;
在服务端,accept()方法返回客户端的socket对象。
4、结果展示:
开启了三个客户端
测试: 用户上线功能、显示当前用户列表功能、私聊功能、用户下线功能
测试: 用户上线功能、显示当前用户列表功能、群聊功能
测试: 用户上线功能、显示当前用户列表功能、群发功能、私聊功能
服务端:体现出了用户上线及用户下线的情况
客户端源代码:
//读线程
class ReadThread implements Runnable{
private Socket client;
public ReadThread(Socket client) {
this.client = client;
}
@Override
public void run() {
try{
Scanner in = new Scanner(client.getInputStream());
while(true){
if(in.hasNextLine()){
System.out.println(in.nextLine());
}
if(client.isClosed()){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//写线程
class WriteThread implements Runnable{
private Socket client;
public WriteThread(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
PrintStream out = new PrintStream(client.getOutputStream(),
true, "UTF-8");
Scanner scanner = new Scanner(System.in);
String str = "";
while(true){
System.out.println("在此输入:");
if(scanner.hasNextLine()){
str = scanner.nextLine();
out.println(str);
}
if(str.contains("bye")){
scanner.close();
out.close();
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public class MutilThreadClient {
public static void main(String[] args) {
try {
Socket client = new Socket("127.0.0.1", 6666);
Thread readThread = new Thread(new ReadThread(client));
Thread writeThread = new Thread(new WriteThread(client));
readThread.start();
writeThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器源代码:
public class MutilThreadServer {
//使用Map的K,V存储用户信息(用户名,socket),模拟登陆现象
private static Map<String, Socket> clientMap = new ConcurrentHashMap<String, Socket>();//线程安全
//内部类,处理客户端
private static class ExecuteClient implements Runnable{
private Socket client;
private ExecuteClient(Socket client) {
this.client = client;
}//构造注入客户端socket
//返回给客户端信息时间
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@Override
public void run() {
try {
//获取输入流
Scanner in = new Scanner(client.getInputStream());
String str = "";
while(true){
if(in.hasNextLine()){
str = in.nextLine();
//windows下将默认的换行\r\n中的\r替换为空字符串
Pattern pattern = Pattern.compile("\r");
Matcher matcher = pattern.matcher(str);
str = matcher.replaceAll("");
//聊天室实现的功能
//用户上线:
// user:123
if(str.startsWith("user")){
String user = str.split("\\:")[1];
register(user, client);
continue;
}
//群聊:
// G:发送内容
if(str.startsWith("G")){
String text = str.split("\\:")[1];
String user = getUser(client);
if(user == ""){
continue;
}
groupChat(text, user);
continue;
}
//私聊
//P-sendUser:text
if(str.startsWith("P")){
String sendUser = str.split("\\-")[1]
.split("\\:")[0];
String text = str.split("\\-")[1]
.split("\\:")[1];
String user = getUser(client);
if(user == ""){
continue;
}
privateChat(sendUser, text, user);
continue;
}
//查看当前用户在线列表
//用户输入ls:显示当前在线人数
if(str.equals("ls")){
PrintStream out = new PrintStream(client.getOutputStream(),
true, "UTF-8");
out.println("当前在线用户列表如下 " + sdf.format(System.currentTimeMillis()));
for(String key : clientMap.keySet()){
out.println(" · 用户" + key);
}
out.println(" · 当前在线人数:" + clientMap.size());
continue;
}
//用户下线
if(str.contains("bye")){
String user = getUser(client);
System.out.println("用户" + user + "下线了.....");
clientMap.remove(user);
continue;
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
//通过客户端socket获取用户名
private String getUser(Socket client) {
String user = "";
for(String key : clientMap.keySet()) {
if (clientMap.get(key).equals(client)) {
user = key;
}
}
if(user == ""){
try {
PrintStream out = new PrintStream(client.getOutputStream(),
true, "UTF-8");
out.println("当前您还未注册,请先注册!");
} catch (IOException e) {
e.printStackTrace();
}
}
return user;
}
//注册实现
private void register(String user, Socket client) {
System.out.println("用户"+user+"上线了.......");
clientMap.put(user, client);
System.out.println("当前群聊人数为" + clientMap.size() + "人");
//告知用户注册成功
try {
PrintStream out = new PrintStream(client.getOutputStream(),
true, "UTF-8");
out.println("注册成功!");
} catch (IOException e) {
System.err.println("注册异常:" + e);
}
}
//群聊实现
private void groupChat(String text, String user) {
Set<Entry<String, Socket>> clientSet = clientMap.entrySet();
for (Entry<String, Socket> entry : clientSet) {
try {
PrintStream out = new PrintStream(entry.getValue().getOutputStream(),
true, "UTF-8");
out.println("用户" + user + " " + sdf.format(System.currentTimeMillis()));
out.println(" 群发:" + text);
} catch (IOException e) {
System.err.println("群聊异常:" + e);
}
}
}
//私聊实现
private void privateChat(String sendUser, String text, String user) {
try {
PrintStream out = new PrintStream(clientMap.get(sendUser).getOutputStream(),
true, "UTF-8");
out.println("用户" + user + " " + sdf.format(System.currentTimeMillis()));
out.println(" 私发:"+ text);
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception{
//利用Exectors工具类创建固定大小线程池
ExecutorService executorService = Executors.newFixedThreadPool(20);
//创建服务端Socket
ServerSocket serverSocket = new ServerSocket(6666);
for(int i = 0; i < 20; ++i){
System.out.println("等待客户端链接.........");
Socket client = serverSocket.accept();
System.out.println("有新的客户端连接,端口号为" + client.getPort());
//向线程池提交线程
executorService.submit(new ExecuteClient(client));
}
executorService.shutdown();
serverSocket.close();
}
}
整体概括