C/S开发框架之最外层(服务器端和客户端)的实现
最外层是服务器端和客户端,也就是直接由使用C/S框架的用户使用的两个类。
服务器端—Server类
服务器端的编写,首先考虑服务器端应该提供的最基本的功能:
1.启动服务器;
2.关闭服务器;
3.强制关闭服务器;
4.显示客户端连接信息;
5.显示客户端下线信息;
6.显示客户端异常掉线信息;
…
服务器的启动和关闭
服务器的启动和关闭是服务器端最基本的功能,由于服务器端需要不断地侦听客户端的连接请求,所以Server类实现Runnable接口,生成线程。
public void startup() throws IOException {
if (goon) {
publishMessage("服务器已启动!");
return;
}
publishMessage("开始启动服务器……");
server = new ServerSocket(port);
temporaryConversationPool = new TemporaryConversationPool();
clientPool = new ClientConversationPool();
publishMessage("服务器已启动!");
goon = true;
new Thread(this, "服务器").start();
}
@Override
public void run() {
publishMessage("开始侦听客户端连接……");
while (goon) {
try {
Socket client = server.accept();
ServerConversation clientConversation = new ServerConversation(client, this);
clientConversation.setActionProcessor(this.actionProcessor);
if (temporaryConversationPool.getTempClientcount() + clientPool.getClientCount()
>= this.maxClientCount) {
clientConversation.outOfRoom();
continue;
}
temporaryConversationPool.addTempConversation(clientConversation);
clientConversation.whoAreYou();
} catch (IOException e) {
goon = false;
}
}
}
public void shutdown() {
if (!goon) {
publishMessage("服务器已宕机!");
return;
}
if (!clientPool.isEmpty()) {
publishMessage("尚有客户端在线,不能宕机!");
return;
}
close();
clientPool = null;
publishMessage("服务器已正常关闭!");
}
public void forcedown() {
if (!goon) {
publishMessage("服务器已宕机!");
return;
}
List<ServerConversation> clientList = clientPool.getClientList();
for (ServerConversation conversation : clientList) {
// 通知客户端,服务器强制宕机,并强行终止所有在线客户端的会话!
conversation.forcedown();
}
close();
clientPool = null;
publishMessage("服务器已强制关闭!");
}
工具与APP的信息交互—观察者模式
服务器端,启动和关闭服务器,需要告知应用层(APP),客户端连接服务器成功,客户端上线下线,也需要告知APP,那么服务器端如何将这些消息告知给APP层呢?这时候就要用到接口了。
于是,定义一个“倾听者”接口 IListener 和 “发布者”接口 “ISpeaker”;
让APP在未来实现 IListener 接口,提供一个能够具体处理Server信息的方法,而Server实现 ISpeaker 接口,负责发布消息,另外,用户的IListener实现类有可能不止一个,即需要从Server中得到信息的APP相关类不止一个,因此,我们在Server中用一个列表保存它们,并提供增加和删除“倾听者”的方法。
package com.mec.csframework.core;
public interface IListener {
void processMessage(String message);
}
package com.mec.csframework.core;
public interface ISpeaker {
void addListener(IListener listener);
void removeListener(IListener listener);
void publishMessage(String message);
}
@Override
public void addListener(IListener listener) {
if (this.listenerList.contains(listener)) {
return;
}
this.listenerList.add(listener);
}
@Override
public void removeListener(IListener listener) {
if (!this.listenerList.contains(listener)) {
return;
}
this.listenerList.remove(listener);
}
@Override
public void publishMessage(String message) {
if (this.listenerList.isEmpty()) {
return;
}
for (IListener listener : listenerList) {
listener.processMessage(message);
}
}
服务器会话层的 "池子"
客户端连接服务器成功之后,Server要为每一个客户端创建一个独立线程,以实现与该客户端的会话过程,另外,一个客户端要求与另一个客户端进行通信,也是需要通过服务器的。因此,服务器端准备一个Map和一个临时列表,来对客户端进行管理。随着客户端接入的数量越来越多,线程数量也越来越多,当线程数量多到一定程度,服务器性能会下降!所以,这里应该控制客户端接入数量。
package com.mec.csframework.core;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ClientConversationPool {
private Map<String, ServerConversation> clientPool;
ClientConversationPool() {
clientPool = new HashMap<>();
}
boolean isEmpty() {
return clientPool.isEmpty();
}
int getClientCount() {
return clientPool.size();
}
void addClient(ServerConversation client) {
String clientId = client.getId();
clientPool.put(clientId, client);
}
void removeClient(ServerConversation client) {
String clientId = client.getId();
clientPool.remove(clientId);
}
ServerConversation getClientById(String id) {
return clientPool.get(id);
}
List<ServerConversation> getClientExcept(String id) {
List<ServerConversation> clientList = new ArrayList<>();
for (String orgId : clientPool.keySet()) {
if (orgId.equals(id)) {
continue;
}
clientList.add(clientPool.get(orgId));
}
return clientList;
}
List<ServerConversation> getClientList() {
return getClientExcept(null);
}
}
package com.mec.csframework.core;
import java.util.LinkedList;
import java.util.List;
public class TemporaryConversationPool {
private List<ServerConversation> tempConversationList;
TemporaryConversationPool() {
tempConversationList = new LinkedList<>();
}
int getTempClientcount() {
return tempConversationList.size();
}
void addTempConversation(ServerConversation conversation) {
tempConversationList.add(conversation);
}
ServerConversation removeTempConversation(ServerConversation conversation) {
if (tempConversationList.remove(conversation) == true) {
return conversation;
}
return null;
}
}
不论是会话层池子还是临时客户端列表,都提供了基本的增加删除操作,而对于ClientConversationPool 还提供了一些与id有关的方法,这是为客户端之间进行通信,比如私聊,群聊等提供的基础。对于临时客户端列表TemporaryConversationPool,这个类的存在是考虑到,客户端请求连接服务器后,Server要先判断客户端是否已满,并判断是否是合法客户,若是则加入到ClientConversationPool 中,否则可暂时加入到TemporaryConversationPool中,比如同一客户端如果多次登录,那我们还可以强制客户端退出等操作。
简单测试
到目前为止,Server的服务器开启,宕机,强制宕机一些基本操作已完成,可以通过编写一个View界面进行测试。注意,这个View界面类就是作为一个”倾听者“,要实现IListener接口。
View界面的代码在这里就不在赘述了,测试的结果如下图:
Server类完整代码:
package com.mec.csframework.core;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import com.mec.csframework.action.DefaultActionProcessor;
import com.mec.csframework.action.IActionProcessor;
import com.mec.util.PropertiesParser;
public class Server implements Runnable, ISpeaker {
private ServerSocket server;
private int port;
private volatile boolean goon;
private ClientConversationPool clientPool;
private List<IListener> listenerList;
private TemporaryConversationPool temporaryConversationPool;
private int maxClientCount;
private IActionProcessor actionProcessor;
public Server() {
this.listenerList = new ArrayList<>();
this.actionProcessor = new DefaultActionProcessor();
this.port = INetBaseConfig.DEFAULT_PORT;
this.maxClientCount = INetBaseConfig.DEFAULT_MAX_CLIENT_COUNT;
}
public void initServer(String configPath) {
PropertiesParser pp = new PropertiesParser();
pp.loadProperties(configPath);
String strPort = pp.value("port");
if (strPort != null && strPort.length() > 0) {
this.port = Integer.valueOf(strPort);
}
String strMaxClientCount = pp.value("maxClientCount");
if (strMaxClientCount != null && strMaxClientCount.length() > 0) {
this.maxClientCount = Integer.valueOf(strMaxClientCount);
}
}
public void initServer() {
initServer("/net.cfg.properties");
}
public void setPort(int port) {
this.port = port;
}
public boolean isStartup() {
return goon;
}
public void startup() throws IOException {
if (goon) {
publishMessage("服务器已启动!");
return;
}
publishMessage("开始启动服务器……");
server = new ServerSocket(port);
temporaryConversationPool = new TemporaryConversationPool();
clientPool = new ClientConversationPool();
publishMessage("服务器已启动!");
goon = true;
new Thread(this, "服务器").start();
}
void toOther(InteractiveInfo interactiveInfo) {
List<ServerConversation> otherList = clientPool.getClientExcept(interactiveInfo.getSourceId());
for (ServerConversation client : otherList) {
client.toOther(interactiveInfo);
}
}
void toOne(InteractiveInfo interactiveInfo) {
String targetId = interactiveInfo.getTargetId();
ServerConversation targetClient = clientPool.getClientById(targetId);
if (targetClient != null) {
targetClient.toOne(interactiveInfo);
}
}
ClientConversationPool getClientPool() {
return clientPool;
}
TemporaryConversationPool getTemporaryConversationPool() {
return temporaryConversationPool;
}
@Override
public void run() {
publishMessage("开始侦听客户端连接……");
while (goon) {
try {
Socket client = server.accept();
ServerConversation clientConversation = new ServerConversation(client, this);
clientConversation.setActionProcessor(this.actionProcessor);
if (temporaryConversationPool.getTempClientcount() + clientPool.getClientCount()
>= this.maxClientCount) {
clientConversation.outOfRoom();
continue;
}
temporaryConversationPool.addTempConversation(clientConversation);
clientConversation.whoAreYou();
} catch (IOException e) {
goon = false;
}
}
}
public void shutdown() {
if (!goon) {
publishMessage("服务器已宕机!");
return;
}
if (!clientPool.isEmpty()) {
publishMessage("尚有客户端在线,不能宕机!");
return;
}
close();
clientPool = null;
publishMessage("服务器已正常关闭!");
}
public void forcedown() {
if (!goon) {
publishMessage("服务器已宕机!");
return;
}
List<ServerConversation> clientList = clientPool.getClientList();
for (ServerConversation conversation : clientList) {
// 通知客户端,服务器强制宕机,并强行终止所有在线客户端的会话!
conversation.forcedown();
}
close();
clientPool = null;
publishMessage("服务器已强制关闭!");
}
private void close() {
goon = false;
try {
if (server != null && !server.isClosed()) {
server.close();
}
} catch (IOException e) {
} finally {
server = null;
}
}
@Override
public void addListener(IListener listener) {
if (this.listenerList.contains(listener)) {
return;
}
this.listenerList.add(listener);
}
@Override
public void removeListener(IListener listener) {
if (!this.listenerList.contains(listener)) {
return;
}
this.listenerList.remove(listener);
}
@Override
public void publishMessage(String message) {
if (this.listenerList.isEmpty()) {
return;
}
for (IListener listener : listenerList) {
listener.processMessage(message);
}
}
}
写至此,Server类基本功能简述结束。
发现篇幅过长,所以下一篇继续Client类的实现。