多线程(下)——多人网络通信程序
1.client包
ChatRoomClient类
package ChatRoom.client;
import ChatRoom.common.ChatMessage;
import ChatRoom.common.DataExchange;
import ChatRoom.common.Utils;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;
import static ChatRoom.common.Constants.*;
public class ChatRoomClient {
private String server;
DataExchange exchange;
private String userName;
private String currentToName;
private Scanner input = new Scanner(System.in);
public ChatRoomClient(String server) throws IOException {
System.out.println("客户端连接中······");
this.server = server;
Socket socket = new Socket(server, SERVER_PORT);
System.out.println("客户端已连接到服务器:" + socket.getRemoteSocketAddress());
exchange = new DataExchange(socket);
this.server = server;
}
public void start() throws IOException {
initName();
Thread readThread = new Thread(() -> {
while (true) {
try {
ChatMessage chatMessage = exchange.receive();
if (chatMessage.getFrom().equalsIgnoreCase(ADMIN_NAME) &&
chatMessage.getMessage().trim().equalsIgnoreCase(BYE)) {
exchange.close();
System.out.println("已经离开可聊天室,聊天结束");
System.exit(0);
}
System.out.println("from\"" + chatMessage.getFrom() + "\":" + chatMessage.getMessage());
} catch (IOException e) {
e.printStackTrace();
exchange.close();
System.exit(-2);
}
}
});
readThread.start();
Thread writeThread = new Thread(() -> {
while (true) {
try {
exchange.send(creatChatMessage());
} catch (Exception e) {
e.printStackTrace();
exchange.close();
System.exit(-3);
}
}
});
writeThread.start();
}
private ChatMessage creatChatMessage() {
while (true) {
String line = input.nextLine().trim();
String to = null;
String message = null;
try {
if (line.startsWith(CHAT_WITH_START)) {
to = line.substring(1, line.indexOf(SPACE_STRING)).trim();
String error = Utils.isValidToUserName(to);
if (error == null) {
currentToName = to;
message = line.substring(line.indexOf(SPACE_STRING) + 1);
} else {
System.out.println("用户名\"" + to + "\"不合法:" + error);
continue;
}
} else {
if (currentToName == null) {
System.out.println("请使用@输入想和谁聊天,以后如果和同一个人聊天,就可以不用@啦");
continue;
}
message = line;
}
} catch (Exception e) {
System.out.println("输入的消息不对:" + line);
continue;
}
return new ChatMessage(userName, currentToName, message);
}
}
private void initName() throws IOException {
String typedName = null;
while (true) {
ChatMessage chatMessage = exchange.receive();
String serverMessage = chatMessage.getMessage();
if (serverMessage.equalsIgnoreCase(USER_NAME_PASS)) {
userName = typedName;
break;
} else {
System.out.println(serverMessage);
typedName = input.nextLine();
exchange.send(new ChatMessage(NO_NAME, ADMIN_NAME, typedName.trim()));
}
}
}
}
2.server包
ChatRoomServer类
package ChatRoom.server;
import ChatRoom.common.ChatMessage;
import ChatRoom.common.DataExchange;
import chat.Chat;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import static ChatRoom.common.Constants.*;
import static ChatRoom.common.Utils.isValidUserName;
public class ChatRoomServer {
private int port;
private Map<String, DataExchange> name2DataEx = new ConcurrentHashMap<>();
private ExecutorService service = Executors.newCachedThreadPool();
public ChatRoomServer(int port) {
this.port = port;
}
public void start() throws IOException {
System.out.println("服务器已启动");
ServerSocket serverSocket = new ServerSocket(SERVER_PORT);
while (true) {
Socket socket = serverSocket.accept();
handleClientSocket(socket);
}
}
private void handleClientSocket(Socket socket) {
service.submit(new ClientHandler(socket));
}
class ClientHandler implements Runnable {
private Socket socket;
private String userName = null;
public ClientHandler(Socket socket) {
this.socket = socket;
}
private void handleChatMessage(DataExchange dataExchange, ChatMessage chatMessage, String toName) {
DataExchange toEx = name2DataEx.get(toName);
if (toEx == null) {
dataExchange.send(new ChatMessage(ADMIN_NAME, chatMessage.getFrom(), "用户名\"" + toName + "\"不存在"));
} else {
toEx.send(chatMessage);
}
}
private void handleServerCommand(ChatMessage chatMessage) {
DataExchange from = name2DataEx.get(chatMessage.getFrom());
String command = chatMessage.getMessage();
if (command.equalsIgnoreCase(SERVER_COMMAND_LOGOFF)) {
from.send(new ChatMessage(ADMIN_NAME, chatMessage.getFrom(), BYE));
from.close();
name2DataEx.remove(userName).close();
System.out.println("用户\"" + chatMessage.getFrom() + "\"离开聊天室");
} else if (command.equalsIgnoreCase(SERVER_COMMAND_LIST)) {
String allNames = getALLNames();
from.send(new ChatMessage(ADMIN_NAME, chatMessage.getFrom(), "所有在线用户:" + allNames));
}
}
@Override
public void run() {
System.out.println("处理来自" + socket.getRemoteSocketAddress() + "的连接");
DataExchange dataExchange = null;
try {
dataExchange = new DataExchange(socket);
} catch (Exception e) {
e.printStackTrace();
return;
}
initUser(dataExchange);
while (true) {
try {
ChatMessage chatMessage = dataExchange.receive();
String toName = chatMessage.getTo();
if (toName.equalsIgnoreCase(ADMIN_NAME)) {
handleServerCommand(chatMessage);
} else {
handleChatMessage(dataExchange, chatMessage, toName);
}
} catch (IOException e) {
name2DataEx.remove(userName).close();
e.printStackTrace();
return;
}
}
}
private void initUser(DataExchange dataExchange) {
String errorMsg = null;
while (true) {
String allNames = getALLNames();
dataExchange.send(new ChatMessage(ADMIN_NAME, NO_NAME, (errorMsg == null ? "" : "用户名非法" +
",错误信息为:" + errorMsg + ":") + "现有的用户名有:" + allNames + "。请输入你的用户名:"));
try {
ChatMessage chatMessage = dataExchange.receive();
String userName = chatMessage.getMessage();
errorMsg = isValidUserName(userName);
if (errorMsg == null && (!name2DataEx.containsKey(userName))) {
this.userName = userName;
name2DataEx.put(userName, dataExchange);
dataExchange.send(new ChatMessage(ADMIN_NAME, userName, USER_NAME_PASS));
dataExchange.send(new ChatMessage(ADMIN_NAME, userName, COMMAND_INTRODUCTION));
return;
}
} catch (IOException e) {
e.printStackTrace();
dataExchange.close();
return;
}
}
}
private String getALLNames() {
String userNameListSep = ",";
if (name2DataEx.isEmpty()) {
return CHAT_WITH_START + ADMIN_NAME;
} else {
return CHAT_WITH_START + ADMIN_NAME + userNameListSep + CHAT_WITH_START + String.join(userNameListSep + CHAT_WITH_START, name2DataEx.keySet());
}
}
}
}
3.common包
ChatMessage类
package ChatRoom.common;
import java.util.Objects;
import static ChatRoom.common.Constants.MESSAGE_BREAK;
import static ChatRoom.common.Constants.MESSAGE_SEP;
public class ChatMessage {
private String from;
private String to;
private String message;
public ChatMessage(String from, String to, String message) {
this.from = from;
this.to = to;
this.message = message.trim();
}
public ChatMessage() {
}
public static ChatMessage buildFrom(String message) {
ChatMessage ret = new ChatMessage();
int fromEnd = message.indexOf(MESSAGE_SEP);
ret.from = message.substring(0, fromEnd);
int toEnd = message.indexOf(MESSAGE_SEP, fromEnd + 1);
ret.to = message.substring(fromEnd + 1, toEnd);
ret.message = message.substring(toEnd + 1).trim();
return ret;
}
public String toMessageString() {
StringBuilder ret = new StringBuilder();
ret.append(from).append(MESSAGE_SEP).append(to).append(MESSAGE_SEP).append(message).append(MESSAGE_BREAK);
return ret.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ChatMessage)) return false;
ChatMessage that = (ChatMessage) o;
return Objects.equals(from, that.from) &&
Objects.equals(to, that.to) &&
Objects.equals(message, that.message);
}
@Override
public int hashCode() {
return Objects.hash(from, to, message);
}
public String getFrom() {
return from;
}
public void setFrom(String from) {
this.from = from;
}
public String getTo() {
return to;
}
public void setTo(String to) {
this.to = to;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
Constants接口
package ChatRoom.common;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
public interface Constants {
int SERVER_PORT = 45678;
Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
char MESSAGE_SEP = Character.UNASSIGNED;
String MESSAGE_BREAK = "\n";
String ADMIN_NAME = "admin";
String SPACE_STRING = " ";
String MESSAGE_SEP_STR = String.valueOf(Character.UNASSIGNED);
String USER_NAME_PASS = "UserNamePass";
String COMMAND_INTRODUCTION = "欢迎来到聊天室,你可以使用@admin list查看所有在线用户,使用@用户名聊天";
String CHAT_WITH_START = "@";
String NO_NAME = "anonymous";
String SERVER_COMMAND_LOGOFF = "logoff";
String SERVER_COMMAND_LIST = "list";
String BYE = "bye";
}
DataExchange类
package ChatRoom.common;
import java.io.*;
import java.net.Socket;
import static ChatRoom.common.Constants.DEFAULT_CHARSET;
public class DataExchange {
private Socket socket;
private BufferedReader reader;
private PrintWriter writer;
private boolean isClosed = false;
public DataExchange(Socket socket) throws IOException {
this.socket = socket;
init(socket);
}
private void init(Socket socket) throws IOException {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), DEFAULT_CHARSET));
writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), DEFAULT_CHARSET));
}
public void send(ChatMessage chatMessage) {
writer.println(chatMessage.toMessageString());
writer.flush();
}
public ChatMessage receive() throws IOException {
String line = null;
while (true) {
line = reader.readLine();
if (line != null && line.length() > 0) {
break;
}
}
return ChatMessage.buildFrom(line);
}
public void close() {
try {
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
writer.close();
} catch (Exception e) {
e.printStackTrace();
}
try {
socket.close();
} catch (Exception e) {
e.printStackTrace();
}
isClosed =true;
}
}
Utils类
package ChatRoom.common;
import static ChatRoom.common.Constants.*;
public class Utils {
public static String isValidUserName(String userName) {
String ret = isValidToUserName(userName);
if (ret != null) {
return ret;
}
if (userName.toLowerCase().contains(ADMIN_NAME)) {
return "用户名不可以包含" + ADMIN_NAME;
}
return null;
}
public static String isValidToUserName(String userName) {
if (userName.trim().length() == 0) {
return "用户名不可以为空";
}
if (userName.contains(MESSAGE_SEP_STR)) {
return "用户名不可以包含分隔符";
}
if (userName.contains(CHAT_WITH_START)) {
return "用户名不可以包含" + CHAT_WITH_START;
}
if (userName.toLowerCase().contains(NO_NAME)) {
return "用户名不可以包含" + NO_NAME;
}
if (userName.contains(SPACE_STRING)) {
return "用户名不可以包含空格";
}
return null;
}
public static String getNormalizedUserName(String userName) {
return userName.trim();
}
}
4.ChatRoomClientAPPMain
package ChatRoom;
import ChatRoom.client.ChatRoomClient;
import java.io.IOException;
public class ChatRoomClientAPPMain {
public static void main(String[] args) throws IOException {
String server= args[0];
ChatRoomClient client = new ChatRoomClient(server);
client.start();
}
}
5.ChatRoomServerAPPMain
package ChatRoom;
import ChatRoom.server.ChatRoomServer;
import java.io.IOException;
import static ChatRoom.common.Constants.SERVER_PORT;
public class ChatRoomServerAPPMain {
public static void main(String[] args) throws IOException {
ChatRoomServer server = new ChatRoomServer(SERVER_PORT);
server.start();
}
}