文章目录
- P6 服务器API层
- 1 服务器 Server类
- 2 留给服务器APP层待处理的方法
- (1) IServerAction接口
- (2) ServerActionAdapter适配器
- P7 客户端API层
- 1 客户端 Client类
- 2 留给客户端APP层待处理的方法
- (1) IClientAction接口
- (2) ClientActionAdapter适配器
P6 服务器API层
1 服务器 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.my.util.IListener;
import com.my.util.ISpeaker;
public class Server implements Runnable,ISpeaker{
public static final String SERVER = "SERVER";
public static final int DEFAULT_MAX_CLIENT_COUNT = 20;
private int port;
private ServerSocket server;
private volatile boolean goon;
//会话池
private ServerConversationPool clientPool;
//最大连接数量
private int maxClientCount;
//把待实现方法的接口当作成员,以便调用
private IServerAction serverAction;
IServerAction getServerAction() {
return serverAction;
}
public void setServerAction(IServerAction serverAction) {
this.serverAction = serverAction;
}
//听众列表,通过addListener方法将一个实现了
//IListener的类加入到听众列表中,这样实现ISpeaker
//接口的类就可以发布消息给所有听众
//这些听众可以得知发布的消息,并做出反应
private List<IListener> listenerList;
public Server() {
this.port = INetConfig.port;
this.maxClientCount = DEFAULT_MAX_CLIENT_COUNT;
this.listenerList = new ArrayList<IListener>();
this.serverAction = new ServerActionAdapter();
}
public void setMaxClientCount(int maxClientCount) {
this.maxClientCount = maxClientCount;
}
public void setPort(int port) {
this.port = port;
}
/**
* 启动服务器并开启侦听线程
*/
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("服务器启动异常");
}
}
/**
* 关闭侦听
*/
public void close() {
this.goon = false;
try {
if(server != null && !server.isClosed()) {
this.server.close();
}
} catch (IOException e) {
} finally {
this.server = null;
}
}
/**
* 服务器宕机
*/
public void shutDown() {
if(!this.goon) {
speakOut("服务器尚未启动");
return;
}
//还有正在连接的客户端
if(this.clientPool.isEmpty()) {
speakOut("尚有在线的客户端,不能宕机");
return;
}
close();
}
/**
* 服务器强制宕机
* 定时维护服务器
*/
public void forceDown() {
if(!this.goon){
speakOut("服务器尚未启动");
return;
}
//获取当前的所有连接的服务器会话端
List<ServerConversation> clientList = this.clientPool.getClient();
for(ServerConversation client : clientList) {
client.forceDown();
}
close();
speakOut("服务器强制宕机");
}
/**
* 指定一个客户端,告诉它为什么将它关闭
* 这里是API,实现由ServerConversation再调用clientOff实现
*/
public void killClient(String clientId, String reason) {
ServerConversation client = this.clientPool.getClient(clientId);
client.killClient(reason);
}
/**
* 获取当前在线的客户端的id+ip字符串类型的列表
*/
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;
}
/**
* 处理客户端消息的方法
* 最终由服务器APP层实现
*/
void messageFromClient(String clientId, String message) {
this.serverAction.dealMessageFromClient(clientId, message);
}
/**
* 不间断侦听来自客户端的连接请求
*/
@Override
public void run() {
speakOut("服务器开始侦听客户端连接请求");
while(this.goon) {
try {
Socket socket = this.server.accept();
//侦听到连接请求,产生一个ServerConversation对象
//这个对象调用底层Communication的构造方法,获得输入,输出信道
ServerConversation client = new ServerConversation(socket, this);
//是否满员,是则拒绝连接
if(this.clientPool.getClientCount() > this.maxClientCount) {
client.OutOfRoom();
continue;
}
//连接成功则为其产生一个ID
client.createClientId();
//再加入会话池
this.clientPool.addClient(client);
} catch (IOException e) {
this.goon = false;
}
}
speakOut("结束侦听客户端连接");
close();
}
/**
* 这里的toOne是将信息从服务器转发给指定客户端的服务器会话端的内部工具
* 服务器需要将接收到的信息私聊给指定的客户端
* 就得先找到目标的ServerConveersation
* 这通过目标id在会话池中寻找
* 并通过ServerConversation的toOne发送信息
*/
void toOne(String sourceId, String targetId, String message) {
//根据Id找到指定客户端的服务器会话层
ServerConversation targetClient = this.clientPool.getClient(targetId);
targetClient.toOne(sourceId, message);
}
/**
* 得知发送者和信息内容
* 发送的过程和toOne大致相同
*/
void toOther(String sourceId, String message) {
//得到除了发送者外的所有客户端的ServerConversation
List<ServerConversation> clientList = this.clientPool.getClientExcept(sourceId);
for(ServerConversation client : clientList) {
client.toOther(sourceId, message);
}
}
/**
* 得知要下线的客户端的ID
* 在会话池中移除这个客户端对应的ServerConversation
*/
void clientOffLine(String clientId) {
this.clientPool.removeClient(clientId);
}
/**
* ISpeaker内需要实现的方法
*/
@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 speakOut(String message) {
for(IListener listener : this.listenerList) {
listener.readMessage(message);
}
}
}
2 留给服务器APP层待处理的方法
(1) IServerAction接口
(2) ServerActionAdapter适配器
P7 客户端API层
1 客户端 Client类
package com.mec.csframework.core;
import java.io.IOException;
import java.net.Socket;
public class Client {
private String ip;
private int port;
private Socket socket;
private ClientConversation conversation;
//将无法处理的问题抛给APP层解决
//IClientAction内就是各种未定的处理特殊情况的方法
//将一个接口作为Client类的成员时
//可以通过get方法取得接口中的方法解决问题
private IClientAction clientAction;
//获取Client对应的ClientConversation的ID
public String getId() {
return conversation.getId();
}
public Client() {
this.ip = INetConfig.ip;
this.port = INetConfig.port;
//做一个适配器,这个适配器实现了IClientAction类
//但其中的方法什么都不做,这样保证clientAction永远不会为空
this.clientAction = new ClientActionAdapter();
}
//public的set方法可以让用户自己写一个实现了具体操作的新接口
//以便调用其中的特殊情况处理方法
public void setClientAction(IClientAction clientAction) {
this.clientAction = clientAction;
}
//包权限的get方法,可以在框架内调用APP层设置好的特殊情况处理方法
IClientAction getClientAction() {
return clientAction;
}
/**
* 建立连接
*/
public boolean connectToServer() {
try {
this.socket = new Socket(this.ip, this.port);
this.conversation = new ClientConversation(socket, this);
} catch (IOException e) {
return false;
}
return false;
}
/**
* 这里的toOne是提供给APP的API
* 要调用ClientConversation的toOne才能发送消息
*/
public void toOne(String targetId, String message) {
this.conversation.toOne(targetId, message);
}
/**
* 这里的toOther是提供给APP的API
* 要调用ClientConversation的toOther才能群发消息
*/
public void toOther(String message) {
this.conversation.toOther(message);
}
/**
* 这里的由客户端发给服务器信息的方法是提供给APP的API
* 实际通过ClientConversation提供的方法完成
*/
public void messageToServer(String message) {
this.conversation.messageToServer(message);
}
/**
* 提供给APP的API
* 要调用ClientConversation的offLine()才能完成下线
*/
public void offLine() {
if(this.clientAction.confirmOffLine()) {
//确认下线后,先执行下线前处理,再下线,再善后处理
this.clientAction.beforeOffLine();
this.conversation.offLine();
this.clientAction.afterOffLine();
}
}
}
2 留给客户端APP层待处理的方法
(1) IClientAction接口
(2) ClientActionAdapter适配器