服务端代码
package test20230317.net.chat;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
public class ChatServer {
private static final int MAX_CONNECT = 100; //最多连接数,超过这个数量连接会被断开
private ServerSocket serverSocket;
private Map<String,Socket> clientMap; //客户端:用户名-IP映射关系
public ChatServer(int port) {
try {
serverSocket = new ServerSocket(port);
clientMap = Collections.synchronizedMap(new HashMap<String,Socket>());
ChatUtil.println("端口"+port+"绑定成功");
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("端口"+port+"绑定失败");
}
}
//监听端口
public void listen() {
while(true) {
try {
doClient(serverSocket.accept());
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 处理客户端请求
* @param socket
*/
private void doClient(Socket client) {
new Thread(new Runnable() {
@Override
public void run() {
String ip = client.getRemoteSocketAddress().toString();
ChatUtil.println(ip,"连接成功");
String userName = null;
try {
//超过最多连接数,则断开
if(clientMap.size()>=MAX_CONNECT) {
writeString(client,"当前聊天人数过多,请稍后再试...");
try {
Thread.sleep(500);
}catch(InterruptedException e) {
}
//断开连接
disConnect(client);
return;
}
//获得当前连接设置的用户名
userName = getUserName(client);
if(userName == null) {
disConnect(client); //断开连接
return;
}
//打印用户列表
printClientList();
//发送欢迎词给客户端
writeString(client,userName+"你好,欢迎进入聊天室");
//发送广播给全体在线用户
sendToAll(client,userName+"进入了聊天室");
//监听客户端输入
BufferedReader socketIn = new BufferedReader(new InputStreamReader(client.getInputStream(),"utf-8"));
String clientInput = "";
while(clientInput!=null) {//输入流=null,表明连接已断开
clientInput = socketIn.readLine();
if(clientInput!=null) {
if(clientInput.contains("@")) {//一对一消息 hello@userName
int indexAt = clientInput.indexOf("@");
String targetName = clientInput.substring(indexAt+1);
String targetMsg = clientInput.substring(0,indexAt);
if(ChatUtil.isEmpty(targetMsg)) {
writeString(client,"请勿发送空消息");
}else {
Socket targetSocket = clientMap.get(targetName);
if(targetSocket != null) {
writeString(targetSocket,userName+"对你说:"+targetMsg);
}else {
writeString(client,"目标用户不存在或已断开连接");
}
}
}else if("/user".equals(clientInput)) { //获取当前在线用户
writeString(client,"在线用户:"+clientMap.keySet().toString());
}else { //群发消息
ChatUtil.println(userName,"收到回复",clientInput);
//发送给其他在线用户
sendToAll(client,userName+"说:"+clientInput);
}
}
}
} catch (IOException e) {
ChatUtil.println(e.getMessage());
}
disConnect(client); //断开连接
//从在线用户列表中删除
if(userName !=null) {
clientMap.remove(userName);
sendToAll(client,userName+"退出了聊天室");
}
}
}).start();
}
/**
* 返回当前连接设置的用户名,如果没有设置返还null
* @param client
* @return String
*/
private String getUserName(Socket client) {
//查找当前客户端是否已经设置了用户名
String oldUserName = null;
for(Map.Entry<String, Socket> entry:clientMap.entrySet()) {
if(entry.getValue() == client) {
oldUserName = entry.getKey();
break;
}
}
if(oldUserName == null) {//未设置用户名
try {
//获取客户端输入,超时自动时间为3秒
client.setSoTimeout(3000);
BufferedReader socketIn = new BufferedReader(new InputStreamReader(client.getInputStream(),"utf-8"));
String clientInput = socketIn.readLine();
if(clientInput!=null) {
if(clientInput.startsWith("userName=")){ //设置用户名
String userName = clientInput.substring("userName=".length());
if(!ChatUtil.isEmpty(userName)) {
if(clientMap.containsKey(userName)) {
writeString(client,"用户名已存在,请更换用户名");
}else {
ChatUtil.println(client.getRemoteSocketAddress().toString(),"设置用户名",userName);
clientMap.put(userName, client);
client.setSoTimeout(0);
return userName;
}
}else {
writeString(client,"用户名不能为空");
}
}else {//该条命令不是设置用户名
writeString(client,"请先设置用户名");
}
}
}catch(IOException e) {
ChatUtil.println(e.getMessage());
}
}else {
return oldUserName;
}
return null;
}
/**
* 发送广播给除自己之外的其他在线用户
* @param current 当前用户
* @param msg
*/
private void sendToAll(Socket current,String msg) {
Collection<Socket> list = clientMap.values();
for(Socket socket:list) {
if(current != socket) {
writeString(socket,msg);
}
}
}
/**
* 打印用户列表
*/
private void printClientList() {
ChatUtil.println("当前在线用户",clientMap.keySet().toString());
}
/**
* 发送数据到客户端
* @param client
* @param line
*/
private void writeString(Socket client,String line) {
try {
PrintWriter socketOut = new PrintWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
socketOut.println(line);
socketOut.flush();
ChatUtil.println("发送消息给",client.getRemoteSocketAddress().toString(),line);
} catch (IOException e) {
e.printStackTrace();
}
}
//断开连接
private void disConnect(Socket client) {
try {
ChatUtil.println("与"+client.getRemoteSocketAddress().toString()+"的连接已断开");
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
if(args.length==0) {
throw new RuntimeException("请输入端口号");
}
ChatServer chatServer = new ChatServer(Integer.parseInt(args[0]));
chatServer.listen();
}
}
客户端的代码
package test20230317.net.chat;
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.TreeSet;
/**
* 客户端聊天,支持聊天室,一对一聊天
* @author Administrator
*
*/
public class ChatClient {
private Socket client;
public ChatClient(String ip,int port,String userName) {
try {
if(ChatUtil.isEmpty(userName)) {
throw new RuntimeException("用户名不能为空");
}
client = new Socket(ip,port);
ChatUtil.println("与服务器"+ip+":"+port+"连接成功");
//发送我的用户名
writeString("userName="+userName);
}catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("连接主机"+ip+":"+port+"失败");
}
}
/**
* 处理服务端输入流,单独启动一个线程,因为读取输入流是阻塞的
*/
public void doInputStream() {
Thread td = new Thread(new Runnable() {
@Override
public void run() {
try {
//获取输入流
BufferedReader socketIn = new BufferedReader(new InputStreamReader(client.getInputStream(),"utf-8"));
//不断读取服务端输入消息
String clientInput = "";
while(clientInput!=null) {//输入流=null,表明连接已断开
clientInput = socketIn.readLine();
if(clientInput!=null) {
ChatUtil.println("收到消息:"+clientInput);
}
}
} catch (IOException e) {
e.printStackTrace();
ChatUtil.println(e.getMessage());
}
//断开连接
disConnect();
//系统退出
System.exit(0);
}
});
td.setDaemon(true);
td.start();
}
/**
* 输入字符串,发给服务器
* @throws IOException
*/
public void inputString(){
try {
//从控制台输入
BufferedReader inputIn = new BufferedReader(new InputStreamReader(System.in,"utf-8"));
//本地输入数据,发送给客户端
String line = null;
while(!("exit".equals(line) || client.isClosed())) {//非 (主动退出 或者 连接关闭)
//本地输入
line = inputIn.readLine();
//回复给服务器
if(ChatUtil.isEmpty(line)) {
ChatUtil.println("系统提示:请勿回复空消息");
}else if(!"exit".equals(line)){
writeString(line);
}
}
//断开连接
disConnect();
}catch(IOException e) {
e.printStackTrace();
}
}
/**
* 发送数据到服务端
* @param client
* @param line
*/
private void writeString(String line) {
try {
PrintWriter socketOut = new PrintWriter(new OutputStreamWriter(client.getOutputStream(),"utf-8"));
socketOut.println(line);
socketOut.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
//断开连接
private void disConnect() {
try {
ChatUtil.println("与服务器"+client.getRemoteSocketAddress().toString()+"的连接已断开");
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
if(args.length!=3) {
throw new RuntimeException("请输入[ip port 用户名]");
}
ChatClient chatClient = new ChatClient(args[0],Integer.parseInt(args[1]),args[2]);
chatClient.doInputStream();
chatClient.inputString();
}
}
工具类
package test20230317.net.chat;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ChatUtil {
/**
* 判断字符串是否为空
* @param str
* @return
*/
public static boolean isEmpty(String str) {
if(str == null) {
return true;
}
str = str.trim();
return str.length()==0;
}
/**
* 格式化时间
* @return
*/
public static String getTime() {
SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss.SSS");
return format.format(new Date());
}
/**
* 打印参数
* @param args
*/
public static void println(String ... strs) {
StringBuilder sb = new StringBuilder(getTime()+"|");
for(String s:strs) {
sb.append(s).append("|");
}
String toStr = sb.toString();
System.out.println(toStr.substring(0, toStr.length()-1));
}
}
运行命令
运行服务端:java test20230317.net.chat.ChatServer 2023
运行客户端:java test20230317.net.chat.ChatClient localhost 2023 user1
客户端命令:
发送1对1消息:消息@用户名,例如:nihao@user2
群发消息:消息,例如:nihao
获取在线用户:/user
退出:exit