近期上课学习了socket的一些简单使用,不满足老师讲解单个客户端与服务端之间相互交流的案例,便根据过往所学的相关知识写出了这个简易的多人聊天室.若存在不足指出有待改进,还望各位大佬不吝赐教,不胜感激
package com.dgk1024.web5;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 基于TCP协议的服务器端开发
* 1. 创建ServerSocket,并指定端口号
* 2. 调用accept(),接收客户端请求
* 3. 获取输入流,读取客户端发送的数据
* 4. 获取输出流,发送数据给客户端
* 5. 关闭释放资源
* @author dgk1024
* @date 2020/7/2 22:37
*/
public class TcpServer {
/**端口号*/
private static final int PORT = 7777;
/**编码*/
private static final String CHAR_SET = "utf-8";
/**线程池连接数*/
private static final int MAX = 20;
public static void main(String[] args) throws IOException {
/**0. 创建线程池*/
ExecutorService executorService = Executors.newFixedThreadPool(MAX);
/**1. 创建ServerSocket,并指定端口号(负责监听,三次握手)*/
ServerSocket listener = new ServerSocket(PORT);
System.out.println("服务器启动...");
while(true){
/**2. 调用accept(),接收客户端请求,阻塞方法(如果没有客户端请求则阻塞)*/
Socket socket = listener.accept();
/**3. 获取响应的输出流保存*/
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),CHAR_SET));
/**4. 加入map集合*/
Group.users.put(socket,bufferedWriter);
/**5. 从线程池获取线程处理该用户操作*/
executorService.submit(new SocketRunnableImpl(socket));
}
}
}
package com.dgk1024.web5;
import java.io.*;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @author dgk1024
* @date 2020/7/3 8:32
*/
public class SocketRunnableImpl implements Runnable{
private static final SimpleDateFormat time = new SimpleDateFormat("HH时mm分ss秒 ");
private Socket socket=null;
/**
* 构造方法
* @param socket
*/
public SocketRunnableImpl(Socket socket){ this.socket = socket; }
/**
* 监听客户端是否有发送消息
* 若有消息则发送给聊天室成员
* 且提示发送者消息发送成功
*/
@Override
public void run() {
/**
* 用户加入后在服务端控制台发送提醒消息
* 给聊天室其余成员发送提醒
*/
System.out.println(time.format(new Date())+"用户"+socket.getPort()+": 进来了");
for (Socket socket:Group.users.keySet()){
if(socket!=this.socket){
BufferedWriter bufferedWriter =Group.users.get(socket);
/**非本人则发送提醒*/
try {
bufferedWriter.write(time.format(new Date())+"用户"+socket.getPort()+": "+"加入群聊");
bufferedWriter.newLine();
bufferedWriter.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
BufferedReader bufferedReader = null;
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream(),"utf-8"));
} catch (IOException e) {
e.printStackTrace();
}
while(true){
try {
String message = null;
if((message = getMessage(bufferedReader))!=null){
sendMessage(message);
if("退出群聊".equals(message)){
/**
* 若用户退出群聊,结束该进程
*/
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取客户端发送的信息
* @param bufferedReader 用户的字符输入流
* @return 用户发送的信息
* @throws IOException
*/
public String getMessage(BufferedReader bufferedReader) throws IOException {
String message = bufferedReader.readLine();
if("退出群聊".equals(message)){
/**
* 若用户退出群聊
* 交给removeUser进行后继处理
*/
removeUser();
System.out.println(time.format(new Date())+"用户"+socket.getPort()+": "+message);
return "退出群聊";
}
if(!("".equals(message))){
System.out.println(time.format(new Date())+"用户"+socket.getPort()+": "+message);
return message;
}
return null;
}
/**
* 转发用户发送的信息给聊天室所有成员(不包含自己)
* @param message 需要转发给聊天室成员的信息
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
for (Socket socket:Group.users.keySet()){
/**判断是不是本人*/
BufferedWriter bufferedWriter =Group.users.get(socket);
if(socket!=this.socket){
/**非本人则发送消息*/
bufferedWriter.write(time.format(new Date())+"用户"+socket.getPort()+": "+message);
}else{
/**本人则提示发送成功*/
bufferedWriter.write("消息成功发送");
}
bufferedWriter.newLine();
bufferedWriter.flush();
}
}
/**
* 给客户端发送消息退出群聊消息
* 服务端释放相关资源
* 客户端接收退出群聊消息会结束循环状态,进而释放资源
* @throws IOException
*/
public void removeUser() throws IOException {
BufferedWriter bufferedWriter =Group.users.get(socket);
bufferedWriter.write("你已退出群聊");
bufferedWriter.newLine();
bufferedWriter.flush();
Group.users.get(socket).close();
socket.close();
Group.users.remove(socket);
}
package com.dgk1024.web5;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 基于TCP客户端开发
* 1. 创建一个客户端套接字,并指定服务器地址和端口号
* 2. 获取输出流,发送数据给服务器
* 3. 获取输入流,读取服务器回复数据
* 4. 关闭释放资源
* @author dgk1024
* @date 2020/7/2 23:22
*/
public class TcpClient{
private static String HOST;
static {
try {
HOST = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
private static final int PORT = 7777;
private static final String CHAR_SET = "utf-8";
public static void main(String[] args) throws IOException {
// 1. 创建一个客户端套接字,并指定服务器地址和端口号
Socket socket = new Socket(HOST,PORT);
// 2. 获取输入输出流
OutputStream outputStream = socket.getOutputStream();
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream,CHAR_SET));
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream,CHAR_SET));
/**
* 匿名内部类
* 监听是否有消息返回
* 先判断消息是否为空
*/
new Thread(()->{
while(true){
try {
String message = bufferedReader.readLine();
if(message!=null) {
if ("你已退出群聊".equals(message)) {
System.out.println(message);
// 4. 关闭释放资源
bufferedReader.close();
bufferedWriter.close();
socket.close();
break;
}
System.out.println(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
/**
* 用于发送信息
*/
while(true){
String message = new Scanner(System.in).nextLine();
bufferedWriter.write(message);
bufferedWriter.newLine();
bufferedWriter.flush();
if("退出群聊".equals(message)){
break;
}
}
}
}
package com.dgk1024.web5;
import java.io.BufferedWriter;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;
/**
* 聊天室
* @author dgk1024
* @date 2020/7/3 16:29
*/
public class Group {
/**
* key: Socket用户
* Value: 对应的输出流
*/
public static ConcurrentHashMap<Socket, BufferedWriter> users = new ConcurrentHashMap<>();
private Group(){}
}