JAVA SE 实战篇 C5 简单CSFramework(下) API层的搭建

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适配器

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43541094/article/details/110879421
今日推荐