(JAVASE)CSFrameWork详解(Server及ServerConversation、观察者视角)

在一个服务器里做到对多个客户端的管理,可以通过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将类变为字符串发送。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魔幻音

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值