CSFramework---通信层(Communication)的实现

C/S 开发框架之通信层的实现

CSFramework编写的主要目的是,为未来编写相应的服务器/客户端模式下的应用系统提供便利。也就是说,我们的CSFramework将完成最底层的相应工作,使得开发人员在使用此框架时,无需再编写底层操作的代码。

最底层–通信层(Communication)的实现

基于服务器端和客户端,双方之间需要进行信息的交互,也就是说,无论是服务器端还是客户端,都同时作为信息的发送方和接收方,都需要进行信息的传递工作,于是,我们将信息的传递独立出来,形成通信层(Communication),主要负责信息的传递,包括发送信息和侦听对端的消息,以及监控对端异常掉线的情况。另外,通信层需要不断地侦听对端的消息,所以Communication实现Runnable接口来创建一个线程。

public abstract class Communication implements Runnable {
	protected Socket socket;           
	protected DataInputStream dis;
	protected DataOutputStream dos;
	protected volatile boolean goon;

双工通信三要素:
Socket类的对象,即,通信端口封装类。由它才能创建通信信道,并在结束通信时,关闭网络连接;
DataInputStream类的对象,即,输入通信信道;
DataOutputStream类的对象,即,输出通信信道。

在任何需要直接通信的双方,只要拥有这三个要素,就能进行直接通信。

goon,是线程开始和结束的控制;而volatile 是为了消除寄存器优化。

volatile关键字

下面对volatile 关键字修饰作一些解释:
当编译系统对某个变量进行寄存器优化之后,对于这个变量的读写操作,不是对真正的内存变量空间进行读写操作,而是仅仅对提供了这个变量数据的寄存器进行操作。这就意味着,如果存在两个线程,它们都存在对同一个变量的寄存器优化后,那么两个线程表面上是对同一变量进行的访问,实质上,是对各自的寄存器中的值进行的访问。也就是说,就算某一个线程更改了“同一个变量”的值,本质上只是更改了寄存器的值,并没有真正更改“内存空间”的值,也就是变量的值,从而使得两个线程并没有彼此真正影响!
但是,一旦加了 volatile 关键字,意思就是向编译器说明:这个变量不能进行寄存器优化!所以,如果类的某个成员,会在不同的线程中进行访问,那么,这个成员最好加上 volatile 修饰。

下面来看看 Communication 类的线程代码片段:

	@Override
	public void run() {
		String message = null;
			while (goon) {
				try {
					message = this.dis.readUTF();
					dealPeerMessage(new NetMessage(message));
				} catch (IOException e) {
					if (goon == true) {
						dealAbnormalDrop();
						goon = false;
					}
				}
			}
			close();
	}

只要goon为true,这个线程会一直在“后台”不停的运行,不断侦听对端发送的消息,一旦对端异常掉线,message = this.dis.readUTF(); 将立刻产生异常,而另外一种情况,dis 一旦被关闭,也会产生异常。所以为了区分这两种异常,我们在异常捕获块中,对goon的值进行判断,如果goon为true,说明一定是对端异常掉线,这时我们需要对对端异常掉线进行处理,然后令goon为false即可。

接下来解释一下两个抽象方法:

	protected abstract void dealPeerMessage(NetMessage message);
	protected abstract void dealAbnormalDrop();

之所以用抽象方法来处理消息以及对端异常掉线,是因为这不是Communication应该做的事,而应该是由它的上一层具体处理,即会话层。
一开始我们就说,通信层的主要工作是信息的传递,不涉及具体信息的处理,这样Communication类的功能也就单一化。所以这里我们用两个抽象方法,它的上一层只要继承Communication,就必须实现这两个抽象方法。

网络信息的规范化–协议

在上面线程代码片段中,还有一个值得注意的问题,就是NetMessage.
首先,我们在这里是为了简化网络通信的难度,只是用了 readUTF() 和writeUTF() 这两个方法,接收字符串信息和发送字符串信息。而在实际应用中,还可以有其他方式发送字节信息,比如图片信息,音频信息。另外,用户在使用我们的工具的时候,对于传输的数据,其格式是千差万别的。为此,我们需要将传输的网络信息进行规范化,也能够方便以后反复需要做的网络信息解析工作。

首先是网络命令:

public enum ENetCommand {
	SERVER_OUT_OF_ROOM, //服务器连接已满

	ENSURE_ONLINE, //客户端确认上线
	OFFLINE, //客户端下线
	
	SERVER_FORCE_DOWN, //服务器强制宕机
}

这里面分为,服务器命令和客户端命令,还有部分命令是双方共有的。

那么NetMessage所需要做的工作就是,服务器端和客户端要发送的信息,是由这个类的对象生成的;而在发送和接收时,网络上传输的是这个类对象形成的字符串;在发送前和接收后,应该转换为本类对象。
代码实现如下:

	@Override
	public String toString() {
		//我们希望输出字符串按照一定规格:
		//假设三个成员的值为:
		//command:CLIENT_LOGIN
		//action:clientLogin
		//parameter:"id = 123456, password = 54321"
		//希望在网络上传递信息格式如下:
		//CLIENT_LOGIN:clientLogin:id = 123456, password = 54321
		
		return command + ":" + (action == null ? " " : action) + ":" + parameter;
	}
	NetMessage(String message) {
		int colonIndex = message.indexOf(':');
		String commandStr = message.substring(0, colonIndex);
		this.command = ENetCommand.valueOf(commandStr);
		
		message = message.substring(colonIndex + 1);
		colonIndex = message.indexOf(':');
		String actionStr = message.substring(0, colonIndex).trim();
		this.action = (actionStr.length() <= 0 ? null : actionStr);
		
		this.parameter = message.substring(colonIndex + 1);
	}

Communication完整代码实现:

package com.mec.csframewok.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * 通信层主要负责信息的传递;<br>
 * 包括发送信息和侦听对端消息,并初步处理信息;<br>
 * 本层还负责监控对端异常掉线情况;
 * @author 14947
 *
 */

public abstract class Communication implements Runnable {
	protected Socket socket;           
	protected DataInputStream dis;
	protected DataOutputStream dos;
	protected volatile boolean goon;
	
	protected  Communication(Socket socket) throws IOException {
		this.socket = socket;
		this.dis = new DataInputStream(socket.getInputStream());
		this.dos = new DataOutputStream(socket.getOutputStream());
		this.goon = true;
		new Thread(this, "通信层").start();
	}
	
	protected void send(NetMessage message) {
		try {
			this.dos.writeUTF(message.toString());
		} catch (IOException e) {
			close();
		}
	}
	
	protected abstract void dealPeerMessage(NetMessage message);
	protected abstract void dealAbnormalDrop();

	@Override
	public void run() {
		String message = null;
			while (goon) {
				try {
					message = this.dis.readUTF();
					dealPeerMessage(new NetMessage(message));
				} catch (IOException e) {
					if (goon == true) {
						dealAbnormalDrop();
						goon = false;
					}
				}
			}
			close();
	}
	
	protected void close() {
		goon = false;
		try {
			if (dis != null) {
				dis.close();
			}
		} catch (IOException e) {
		} finally {
			dis = null;
		}
		try {
			if (dos != null) {
				dos.close();
			}
		} catch (IOException e) {
		} finally {
			dos = null;
		}
		try {
			if (socket != null && !socket.isClosed()) {
				socket.close();
			}
		} catch (IOException e) {
		} finally {
			socket = null;
		}
	}
	
}

NetMessage完整代码实现:

package com.mec.csframewok.core;

/**
 * 网络信息<br>
 * 服务器端和客户端发送的信息,需由此类的对象生成;<br>
 * 在发送和接收时,网络上传输的是本类的对象形成的字符串;<br>
 * 由于本类是工具的内部核心类,所以包权限;
 * @author 14947
 *
 */

public class NetMessage {
	ENetCommand command;
	String action;
	String parameter;
	
	NetMessage() {
	}
	
	NetMessage(String message) {
		int colonIndex = message.indexOf(':');
		String commandStr = message.substring(0, colonIndex);
		this.command = ENetCommand.valueOf(commandStr);
		
		message = message.substring(colonIndex + 1);
		colonIndex = message.indexOf(':');
		String actionStr = message.substring(0, colonIndex).trim();
		this.action = (actionStr.length() <= 0 ? null : actionStr);
		
		this.parameter = message.substring(colonIndex + 1);
	}

	ENetCommand getCommand() {
		return command;
	}

	NetMessage setCommand(ENetCommand command) {
		this.command = command;
		return this;
	}

	String getAction() {
		return action;
	}

	NetMessage setAction(String action) {
		this.action = action;
		return this;
	}

	String getParameter() {
		return parameter;
	}

	NetMessage setParameter(String parameter) {
		this.parameter = parameter;
		return this;
	}

	@Override
	public String toString() {
		//我们希望输出字符串按照一定规格:
		//假设三个成员的值为:
		//command:CLIENT_LOGIN
		//action:clientLogin
		//parameter:"id = 123456, password = 54321"
		//希望在网络上传递信息格式如下:
		//CLIENT_LOGIN:clientLogin:id = 123456, password = 54321
		
		return command + ":" + (action == null ? " " : action) + ":" + parameter;
	}	
	
}

至此,关于CSFramework的通信层Communication以及网络信息的规范化NetMessage 简述结束。

下一篇将继续CSFramework的会话层的实现。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值