CSFramework---最外层(Server/Client)的实现

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类的实现。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值