在一个服务器里做到对多个客户端的管理,可以通过map实现。
public class ServerConversationPool {
private Map<String, ServerConversation> conversationPool;
ServerConversationPool() {
this.conversationPool = new HashMap<>();
}
void addClient(ServerConversation client) {
this.conversationPool.put(client.getId(), client);
}
实现一个由用户端id和用户本身对应的键值对,之后的各种提取个别用户等操作就是对图的操作。
观察者视角:
public interface ISpeaker {
void addListener(IListener listener);
void removeListener(IListener listener);
void speakOut(String message);
}
public interface IListener {
void readMessage(String message);
}
由演讲者和听众组成,每个客户就是一个listener,服务端是speaker,类似像用户上线下线这种操作需要向全部成员广播通知,这就是speakout的作用,而客户端接收到信息再根据不同情况采用不同方法处理。
Server:
public class Server implements Runnable, ISpeaker {
public static final String SERVER = "SERVER";
public static final int DEFAULT_MAX_CLIENT_COUNT = 20;
public static final Gson gson = new GsonBuilder().create();
private int port;
private ServerSocket server;
private volatile boolean goon;
private ServerConversationPool clientPool;
private int maxClientCount;
private IServerAction serverAction;
private IRequestResponseAction requestAction;
private List<IListener> listenerList;
public Server() {
this.port = INetConfig.port;
this.maxClientCount = DEFAULT_MAX_CLIENT_COUNT;
this.listenerList = new ArrayList<>();
this.serverAction = new IServerAction() {
@Override
public void dealMessageFromClient(String clientId, String message) {
speakOut(clientId + "@" + message);
}
};
this.requestAction = new RequestResponseAction();
}
启动服务器:
public void startup() {
if (this.goon == true) {
speakOut("服务器已启动!");
return;
}
try {
this.server = new ServerSocket(this.port);
speakOut("服务器启动成功!");
this.clientPool = new ServerConversationPool();
this.goon = true;
new Thread(this).start();
} catch (IOException e) {
speakOut("服务器启动异常!");
}
服务器启动代表服务器socket实例化成功,id与用户键值对生成,服务器这条线程启动。
服务器关闭:
public void shutdown() {
if (!this.goon) {
speakOut("服务器尚未启动!");
return;
}
if (!this.clientPool.isEmpty()) {
speakOut("尚有在线的客户端,不能宕机!");
return;
}
close();
}
关闭除了服务器尚未启动以及有客户端存在的情况下,直接调用close即可。
服务器强制宕机:
public void forcedown() {
if (!this.goon) {
speakOut("服务器尚未启动!");
return;
}
List<ServerConversation> clientList = this.clientPool.getClient();
for (ServerConversation client : clientList) {
client.forcedown();
}
close();
speakOut("服务器强制宕机!");
}
服务器强制宕机就是先对所有客户机强制下机后调用close。
强制用户下机:
public void killClient(String clientId, String reason) {
ServerConversation client = this.clientPool.getClient(clientId);
client.killClient(reason);
}
依据id找到对应客户端下机。
获取在线用户信息:
public List<String> getOnlineClientList() {
List<String> clientList = new ArrayList<>();
List<ServerConversation> onlineClientList = this.clientPool.getClient();
for (ServerConversation client : onlineClientList) {
clientList.add(client.getIp() + ":" + client.getId());
}
return clientList;
}
先获取全部用户,再将每个用户的ip,id存到列表里。
服务器监听:
public void run() {
speakOut("开始侦听客户端连接请求……");
while (this.goon) {
try {
Socket socket = this.server.accept();
ServerConversation client = new ServerConversation(socket, this);
if (this.clientPool.getClientCount() >= this.maxClientCount) {
while (!client.startListenning) {
;
}
client.outOfRoom();
continue;
}
client.createClientId();
speakOut("客户端[" + client + "]连接成功!");
this.clientPool.addClient(client);
} catch (IOException e) {
this.goon = false;
}
}
speakOut("结束侦听客户端连接!");
close();
}
当服务端接收到一个连接,就新建一个conversation用于连接服务器和该客户端,生成一个用户独特的id,并且将用户添加到用户池中。
信息发送:
void toOne(String sourceId, String targetId, String message) {
ServerConversation targetClient = this.clientPool.getClient(targetId);
targetClient.toOne(sourceId, message);
}
void toOther(String sourceId, String message) {
List<ServerConversation> clientList = this.clientPool.getClientExcept(sourceId);
for (ServerConversation client : clientList) {
client.toOther(sourceId, message);
}
}
可以看到服务器进行信息发送,就是在用户池中筛选符合条件的用户并执行client的对应方法。
用户下线:
void clientOffline(String clientId) {
this.clientPool.removeClient(clientId);
}
用户上下线在服务端的表现就是加入或移除用户池。
服务器关闭:
private void close() {
this.goon = false;
try {
if (server != null && !server.isClosed()) {
this.server.close();
}
} catch (IOException e) {
} finally {
this.server = null;
}
}
执行close方法即可。
ServerConversation构造及组成:
private String ip;
private String id;
private Server server;
public ServerConversation(Socket socket, Server server) throws IOException {
super(socket);
this.ip = socket.getInetAddress().getHostAddress();
this.server = server;
}
服务端IP获得:this.ip = socket.getInetAddress().getHostAddress();
客户端id生成:
void createClientId() {
this.id = String.valueOf((this.ip + System.currentTimeMillis()).hashCode());
send(new NetMessage()
.setCommand(ENetCommand.ID)
.setSource(Server.SERVER)
.setTarget(id));
}
这一步体现客户端的id是由服务端生成并由ID命令发送回客户端。
强制下线:
void killClient(String reason) {
send(new NetMessage()
.setCommand(ENetCommand.KILL_CLIENT)
.setMessage(reason));
this.server.clientOffline(id);
close();
this.server.speakOut("客户端[" + this + "]因为\"" + reason + "\"被强制下线!");
}
conversation执行操作一方面是给客户端发送信息,另一方面在server层面进行操作。
处理收到的信息:
public void dealNetMessage(NetMessage netMessage) {
ENetCommand command = netMessage.getCommand();
switch (command) {
case TO_ONE:
this.server.toOne(netMessage.getSource(),
netMessage.getTarget(), netMessage.getMessage());
break;
case TO_OTHER:
this.server.toOther(id, netMessage.getMessage());
break;
case OFFLINE:
this.server.clientOffline(id);
this.server.speakOut("客户端[" + this + "]下线!");
close();
break;
case MESSAGE_TO_SERVER:
this.server.messageFromClient(id, netMessage.getMessage());
break;
case REQUEST:
NetMessage mess = new NetMessage()
.setCommand(ENetCommand.RESPONSE)
.setSource(Server.SERVER)
.setTarget(netMessage.getSource())
.setAction(netMessage.getAction());
Object result = null;
try {
result = this.server.getRequestAction()
.dealRequest(netMessage.getAction(), netMessage.getMessage());
mess.setMessage(Server.gson.toJson(result));
} catch (Exception e) {
}
send(mess);
break;
default:
break;
}
}
在response中因为要发送的信息是RequestAction类,引入了Gson将类变为字符串发送。