目录
模块4-资源的发送与接收
经过前三个模块的阐述,我们现在需要去处理资源提供端的发送和之后的如何接受资源提供端发送的文件
Sender
ResourceSender
其提到了我们在上一个模块中所提到的ISendsecction接口
/**
*
* <ol>
* 功能:资源提供端根据请求去开启线程从resourcePool中发送资源片段
* </ol>
* @author Quan
* @date 2020/03/07
* @version 0.0.1
*/
@RMIInterfaces(rmiInterfaces = {ISendSection.class})
public class ResourceSender implements ISendSection {
@Override
public void sendSectionInfo(Node receiverNode, ResourceBaseInfo rbi) {
Sender senderClient = new Sender();
senderClient.setNode(receiverNode);
senderClient.setRbi(rbi);
new Thread(senderClient).start();
}
}
SenderServer
/**
*
* <ol>
* 功能:sender方所需要开启的RMIServer服务器,接受来自需求方的请求
* </ol>
* @author Quan
* @date 2020/03/07
* @version 0.0.1
*/
public class SenderRmiServer {
static String ip;
static {
InetAddress address;
try {
address = InetAddress.getLocalHost();
ip = address.getHostAddress();
System.out.println("ip: " + ip);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
private ResourcePool resourcePool;
private Node me;
private RMIServer SenderServer;
private ThreadPoolExecutor threadPoolExecutor;
private MethodFactory methodFactory;
private boolean open;
private ICenterAction action;
private Node centerNode;
private ClientProxy clientProxy;
public SenderRmiServer() {
methodFactory = new MethodFactory();
methodFactory.collectionMethod("com.mec.mfct.sender");
SenderServer = new RMIServer();
centerNode = new Node("192.168.79.1", 54188);
clientProxy = new ClientProxy();
clientProxy.setRmiClient(new RMIClient(centerNode.getPort(), centerNode.getIp()));
loadConfig();
}
private void loadConfig() {
loadConfig("/config.port.properties");
}
private void loadConfig(String path) {
PropertiesUtil.loadProperties(path);
String configPort = PropertiesUtil.getValue("rmiPort");
if (configPort != null) {
int port = Integer.valueOf(configPort);
System.out.println("配置Port:" + port);
SenderServer.setPort(port);
me = new Node(ip, port);
}
}
public boolean registryResource(Node netNode, ResourceBaseInfo rbi) {
if (resourcePool == null) {
System.out.println("请先设置resourcePool");
return false;
}
if (netNode == null) {
netNode = me;
}
resourcePool.addResource(rbi);
boolean ok = false;
try {
action = clientProxy.getProxy(ICenterAction.class, false);
ok = action.registryNode(netNode, rbi);
} catch (UndeclaredThrowableException e) {
System.out.println(e);
return false;
}
return ok;
}
public boolean logOutNode(Node netNode, ResourceBaseInfo rbi) {
if (resourcePool == null) {
return false;
}
if (netNode == null) {
netNode = me;
}
action = clientProxy.getProxy(ICenterAction.class, false);
boolean ok = action.logOutNode(netNode);
if (ok) {
if (!SenderServer.isShutDown()) {
SenderServer.shutDown();
open = false;
}
return true;
}
return false;
}
public boolean startServer() {
SenderServer.setThreadPool(threadPoolExecutor);
open = SenderServer.startRMIServer();
return open;
}
public void setResourcePool(ResourcePool resourcePool) {
this.resourcePool = resourcePool;
}
public void closeServer() {
if (!SenderServer.isShutDown() || open) {
SenderServer.shutDown();
open = false;
}
}
public void setThreadPool(ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolExecutor = threadPoolExecutor;
}
}
Sender
/**
*
* <ol>
* 功能:处理发送的单线程
* <li>每个文件的句柄应该有一个池子管理,这样可减少资源的开销</li>
* <li>依照请求端的请求去发送相关资源片段</li>
* </ol>
* @author Quan
* @date 2020/03/07
* @version 0.0.1
*/
public class Sender implements Runnable {
private Map<String, RandomAccessFile> rafPool = new HashMap<String, RandomAccessFile>();
private INetNode node;
private ResourceBaseInfo rbi;
private Socket sender;
private DataOutputStream dos;
public Sender() {
}
public INetNode getNode() {
return node;
}
public void setNode(INetNode node) {
this.node = node;
}
public ResourceBaseInfo getRbi() {
return rbi;
}
public void setRbi(ResourceBaseInfo rbi) {
this.rbi = rbi;
}
private void connectToServer() throws UnknownHostException, IOException {
try {
sender = new Socket(node.getIp(), node.getPort());
} catch (Exception e) {
}
this.dos = new DataOutputStream(sender.getOutputStream());
}
private ResourceStructInfo getRsiByFileHandle(int fileHandle, List<ResourceStructInfo> rsiList) {
for (ResourceStructInfo rsi : rsiList) {
if (rsi.getFileHandle() == fileHandle) {
return rsi;
}
}
return null;
}
private RandomAccessFile getRaf(String filePath) throws FileNotFoundException{
RandomAccessFile raf = rafPool.get(filePath);
if (raf == null) {
raf = new RandomAccessFile(filePath, "r");
rafPool.put(filePath, raf);
}
return raf;
}
private void closeFile() {
for (RandomAccessFile accessFile : rafPool.values()) {
try {
accessFile.close();
} catch (IOException e) {
}
}
}
private void sendSection() {
ResourceBaseInfo orgRbi = ResourcePool.getResourceBaseInfo(rbi.getName());
String orgAbsolutePath = orgRbi.getAbsoluteRoot();
List<FileSectionInfo> fsiList = rbi.getFsiList();
List<ResourceStructInfo> rsiList = orgRbi.getRsiList();
for (FileSectionInfo fsi : fsiList) {
int handle = fsi.getFileHandle();
String filePath = getRsiByFileHandle(handle, rsiList).getFilePath();
String path = orgAbsolutePath + filePath;
long offset = fsi.getOffset();
int size = fsi.getSize();
byte[] buffer = new byte[size];
try {
RandomAccessFile raf = getRaf(path);
raf.seek(offset);
raf.read(buffer, 0, size);
FileSection fileSection = new FileSection();
fileSection.setFileSectionInfo(new FileSectionInfo(handle, offset, size));
fileSection.setValue(buffer);
fileSection.sendFileSection(dos);
} catch (IOException e) {
}
}
}
//发送fileSection.setFileSectionInfo(new FileSectionInfo(-1, -1, -1));代表发送完毕
private void sendNoResource() {
try {
FileSection fileSection = new FileSection();
fileSection.setFileSectionInfo(new FileSectionInfo(-1, -1, -1));
fileSection.sendFileSection(dos);
} catch (IOException e) {
System.out.println(e);
}
}
@Override
public void run() {
try {
connectToServer();
//resourcePool中获取
ResourceBaseInfo rbiFromPool = ResourcePool.getResourceBaseInfo(rbi.getName());
if (rbiFromPool == null) {
sendNoResource();
close();
return;
}
sendSection();
close();
} catch (UnknownHostException e) {
} catch (IOException e) {
}
closeFile();
}
private void close() {
if (sender != null || !sender.isClosed()) {
try {
sender.close();
} catch (IOException e) {
} finally {
sender = null;
}
}
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
} finally {
dos = null;
}
}
}
}
Receiver
此时我们的接受端在发送了资源请求后也就是我们上一个模块中所说的resourceRequestor后此时应该开启一个临时Server服务器,用来接受资源。
ReceiverServer
/**
*
* <ol>
* 功能:资源请求端提供的用来接受资源的临时服务器
* <li>DEFAULT_GET_PORT:可根据策略选择每次从资源管理中心取还是从缓存中获取</li>
* <li>timerFlag:定时的清理接受线程,检查完成情况</li>
* <li>若仍有资源未完成,则断点续传</li>
* <li>iRegistrySelf: 将自己向资源管理中心注册的接口</li>
* <li>IRecieveViewAction:向外界通知传输情况的接口</li>
* </ol>
* @author Quan
* @date 2020/03/07
* @version 0.0.1
*/
public class ReceiverServer implements Runnable {
static String ip;
static {
InetAddress address;
try {
address = InetAddress.getLocalHost();
ip = address.getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
private static int port;
private static final String DEFAULT_GET_PORT = "local";
private static ServerSocket receiveServer;
private static volatile boolean timerFlag = false;
private static Map<Integer, UnReceivedFileInfo> urfMap;
private ResourceBaseInfo rbi;
private List<Node> localNodeList;
private static ThreadPoolExecutor executor;
private static ThreadGroup threadGroup;
private String getPortConfig = DEFAULT_GET_PORT;
private static Timer timer;
private long delayTime;
private static IRegistrySelf iRegistrySelf;
private IRecieveViewAction action;
public ReceiverServer() {
localNodeList = new ArrayList<Node>();
loadConfig();
}
private void loadConfig() {
loadConfig("/config.sender.properties");
}
private void loadConfig(String path) {
PropertiesUtil.loadProperties(path);
String getPortConfig = PropertiesUtil.getValue("requestAgainFrom");
if (getPortConfig != null && getPortConfig.equalsIgnoreCase("center")) {
this.getPortConfig = getPortConfig;
}
}
public IRecieveViewAction getAction() {
return action;
}
public void setAction(IRecieveViewAction action) {
this.action = action;
}
public ReceiverServer setRbi(ResourceBaseInfo rbi) {
this.rbi = rbi;
urfMap = new HashMap<Integer, UnReceivedFileInfo>();
List<ResourceStructInfo> rsiList = rbi.getRsiList();
long totalSize = 0;
for (ResourceStructInfo rsi : rsiList) {
int handle = rsi.getFileHandle();
long size = rsi.getFsize();
totalSize += size;
UnReceivedFileInfo urf = new UnReceivedFileInfo(handle, size);
urfMap.put(handle, urf);
}
rbi.setTotalSize(totalSize);
return this;
}
public void setReallySendNodeList(List<Node> reallySendNodeList) {
this.localNodeList = reallySendNodeList;
}
private static boolean isUrfMapOver() {
for (UnReceivedFileInfo info : urfMap.values()) {
if (!info.isOk()) {
return false;
}
}
return true;
}
public long getDelayTime() {
return delayTime;
}
public void setDelayTime(long delayTime) {
this.delayTime = delayTime;
}
//接受完毕后向资源管理中心注册自己
public static boolean isServerCanClose() {
if (isUrfMapOver()) {
iRegistrySelf.registrySelf(new Node(ip, 0));
return true;
}
return false;
}
public int getPort() {
return port;
}
public void setPort(int port) {
ReceiverServer.port = port;
System.out.println("port :" + port);
}
public ResourceBaseInfo getRbi() {
return rbi;
}
public static boolean isTimerFlag() {
return timerFlag;
}
public static void setTimerFlag(boolean timerFlag) {
ReceiverServer.timerFlag = timerFlag;
}
public void setThreadPool(ThreadPoolExecutor threadPoolExecutor) {
executor = threadPoolExecutor;
}
public void startUp() {
if (action == null) {
System.out.println("请先设置action");
return;
}
try {
receiveServer = new ServerSocket(port);
threadGroup = new ThreadGroup("接收线程组");
executor.execute(this);
} catch (IOException e) {
e.printStackTrace();
}
}
public void showMap() {
for (UnReceivedFileInfo info : urfMap.values()) {
System.out.println(info);
}
}
@Override
public void run() {
if (rbi == null) {
action.receiveFail("需接收的资源信息不存在!");
return;
}
while (!isServerCanClose()) {
try {
Socket receive = receiveServer.accept();
Receiver receiver = new Receiver(receive, urfMap, rbi, action);
@SuppressWarnings("unused")
Thread receiverThread = new Thread(threadGroup, receiver);
executor.execute(receiver);
if (timerFlag == false) {
timerFlag = true;
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (threadGroup.activeCount() > 0 || !isServerCanClose()) {
cleanThreadGroup();
if (isServerCanClose()) {
action.receiveSuccess(rbi.getName());
ReceiverServer.close();
return;
}
showMap();
List<FileSectionInfo> fsiList = collectionAgainSection();
rbi.setFsiList(fsiList);
ResourceRequestor requestor = new ResourceRequestor();
List<Node> nodeList = null;
if (getPortConfig == DEFAULT_GET_PORT) {
nodeList = localNodeList;
} else {
nodeList = requestor.getPortFromCenter(rbi);
}
try {
requestor.requestResourceAgain(nodeList, rbi, rbi.getAbsoluteRoot());
} catch (WrongAgainTransferException e) {
ReceiverServer.close();
action.receiveFail("断点续传失败");
e.printStackTrace();
return;
} catch (Exception e) {
ReceiverServer.close();
action.receiveFail("断点续传失败");
e.printStackTrace();
return;
}
}
}
}, delayTime);
}
} catch (IOException e) {
System.out.println("关闭接收端服务器");
close();
return;
}
}
}
@SuppressWarnings("deprecation")
private static void cleanThreadGroup() {
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
for (Thread thread : threads) {
System.out.println("关闭" + thread.getName());
thread.stop();
}
}
private List<FileSectionInfo> collectionAgainSection() {
List<FileSectionInfo> fsiList = new ArrayList<FileSectionInfo>();
for (UnReceivedFileInfo info : urfMap.values()) {
List<UnReceivedFileSectionInfo> urfsiList = info.getSections();
for (UnReceivedFileSectionInfo uInfo : urfsiList) {
FileSectionInfo fileSectionInfo =
new FileSectionInfo(uInfo.getFileHandle(), uInfo.getOffset(), (int)uInfo.getSize());
fsiList.add(fileSectionInfo);
}
}
return fsiList;
}
public static void close() {
if ( receiveServer != null || !receiveServer.isClosed()) {
try {
receiveServer.close();
} catch (IOException e) {
receiveServer = null;
}
}
{
RandAccessFilePool.closePool();
ReceiveServerPortPool.returnPort(port);
try {
timer.cancel();
} catch (Exception e) {
}
executor.shutdownNow();
cleanThreadGroup();
}
}
}
Receiver
/**
*
* <ol>
* 功能:接受提供端发来的资源线程的接受线程
* </ol>
* @author Quan
* @date 2020/03/07
* @version 0.0.1
*/
public class Receiver implements Runnable {
private Socket socket;
private DataInputStream dis;
private ResourceBaseInfo rbi;
private Map<Integer, ResourceStructInfo> rsiMap;
private Map<Integer, UnReceivedFileInfo> urfMap;
private IRecieveViewAction action;
public Receiver(Socket socket, Map<Integer, UnReceivedFileInfo> urfMap, ResourceBaseInfo rbi, IRecieveViewAction action) {
this.rbi = rbi;
this.socket = socket;
this.urfMap = urfMap;
this.action = action;
this.rsiMap = new HashMap<Integer, ResourceStructInfo>();
List<ResourceStructInfo> rsiList = rbi.getRsiList();
for (ResourceStructInfo resourceStructInfo : rsiList) {
rsiMap.put(resourceStructInfo.getFileHandle(), resourceStructInfo);
}
}
@Override
public void run() {
String absoluteRoot = rbi.getAbsoluteRoot();
RandAccessFilePool randAccessFilePool = new RandAccessFilePool();
try {
this.dis = new DataInputStream(socket.getInputStream());
} catch (IOException e) {
System.out.println("某接受线程出错");
close();
return;
}
while (true) {
FileSection fileSection = new FileSection();
try {
fileSection.receiveFileSection(dis);
FileSectionInfo fileSectionInfo = fileSection.getFileSectionInfo();
if (fileSectionInfo == new FileSectionInfo(-1, -1, -1)) {
close();
throw new Exception("对方无资源");
}
long receiveSize = fileSectionInfo.getSize();
byte[] value = fileSection.getValue();
int fileHandle = fileSectionInfo.getFileHandle();
ResourceStructInfo rsi = rsiMap.get(fileHandle);
String filePath = null;
filePath = absoluteRoot + rsi.getFilePath();
RandomAccessFile raf = randAccessFilePool.getRaf(filePath);
raf.seek(fileSectionInfo.getOffset());
raf.write(value);
UnReceivedFileInfo unReceivedFileInfo = urfMap.get(fileHandle);
unReceivedFileInfo.afterReceiveSection(new UnReceivedFileSectionInfo(fileSectionInfo));
action.change(receiveSize, rbi.getTotalSize());
} catch (PeerDownException e) {
System.out.println("对方关闭,本方结束");
close();
if (ReceiverServer.isServerCanClose()) {
ReceiverServer.close();
}
return;
} catch (WrongHeadLenException e) {
} catch (IOException e) {
close();
} catch (Exception e) {
close();
}
}
}
private void close() {
if (socket != null || !socket.isClosed()) {
try {
socket.close();
} catch (IOException e) {
socket = null;
}
}
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
dis = null;
}
}
}
}
工具类
RandAcessFilePool
/**
*
* <ol>
* 功能:用来将文件的读写句柄放在map中,提高操作利用率
* </ol>
* @author Quan
* @date 2020/03/07
* @version 0.0.1
*/
public class RandAccessFilePool {
private static Map<String, RandomAccessFile> rafPool;
static {
rafPool = new ConcurrentHashMap<>();
}
RandAccessFilePool() {
}
RandomAccessFile getRaf(String filePath) {
RandomAccessFile raf = rafPool.get(filePath);
if (raf == null) {
try {
// TODO 根据filePath,创建相关目录
CreateFileUtil.createFile(filePath);
raf = new RandomAccessFile(filePath, "rw");
rafPool.put(filePath, raf);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
return raf;
}
public static void closePool() {
for (RandomAccessFile raf : rafPool.values()) {
try {
raf.close();
} catch (IOException e) {
raf = null;
}
}
}
}
PortPool
/**
* 关于port池,首先有如下基本设定:<br>
* prot池的取值范围必须有限,且设置最大和最小值范围;<br>
* 其中的port,可以反复使用。<br>
* 实现手段有简单、普通、可回收三种:<br>
* 简单模式:定义一个整型量,初值为minPort;每次申请后自增;当增加为maxPort后,回绕重新分配<br>
* 普通模式:定义一个线程安全的队列,并用从minPort到maxPort的整型量初始化;<br>
* 设定如下几个方法:
* <ul>
* <li>boolean hasNext();只要队列非空,则,返回真;</li>
* <li>int next();总是返回队首port,并"出队列";</li>
* <li>void returnPort(int port);归还port到队尾。</li>
* </ul>
* 可回收模式:定义两个线程安全的队列,分别为:已分配port队列和未分配port队列;<br>
* 且,其中的已分配port队列的泛型类,包括:<br>
* int port;<br>
* long time;<br>
* ReceiveServer server;<br>
* 其中的time是分配时间,以System.currentMilliTime()为值;<br>
* server是用port建立的接收服务器;<br>
* 并启用DidaDida时钟,将超过30分钟未归还的port,通过server强制关闭,并回收port。<br>
* 超时时间可配置;port范围可配置。
* @author quan
*
*/
public class ReceiveServerPortPool {
private static final int DEFAULT_PORT_SIZE = 100;
private static Queue<Integer> portQueue = new LinkedBlockingDeque<Integer>();
static {
for(int i = 0; i < DEFAULT_PORT_SIZE; i++) {
portQueue.add(Integer.valueOf(54000 + i));
}
}
public ReceiveServerPortPool() {
}
public static boolean hasNext() {
return portQueue.size() != 0;
}
public static int next() {
return portQueue.poll();
}
public static boolean returnPort(int port) {
if (portQueue.size() >= DEFAULT_PORT_SIZE) {
return false;
}
portQueue.add(port);
return true;
}
}