CSFramework---通信层(Communication)的实现_Ailsa Zhang的博客-程序员秘密

技术标签: java  CSFramework  网络  

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的会话层的实现。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Ailsazxk/article/details/104895131

智能推荐

signature=ad248ee50cb35fb429594f302bf99ddf,Error Detection Using Dynamic Dataflow Verification_风华秋实的博客-程序员秘密

摘要:A significant fraction of the circuitry in a modern processor is dedicated to converting the linear instruction stream into a representation that allows the execution of instructions in data depend...

textview 与textsize_jks456的博客-程序员秘密

在设计安卓界面的时候我发现一个TextView在布局上占用的高度和属性textSize的大小不一样,要比textSize要来的大(比如textSize="12dp",实际的高度大概有14-16dp),仔细看的话会发现文字的上方和下发留有空白。 这个问题我纠结了很久。。。因为这严重影响布局的效果啊。不过这么基础的问题网上竟然找不到资料。。。 在安卓文档中发现一个TextVie

[转]TOKUDB VS. INNODB FLASH MEMORY_z-pan的博客-程序员秘密

Details on the software settings for these tests can be found at the end of this page.IIBENCH TESTINGTokutek created the iiBench benchmark in 2008. The point of the benchmark is to measure the per...

Java并发编程之异步Future机制的原理和实现_a1282379904的博客-程序员秘密

Java并发编程之异步Future机制的原理和实现         项目中经常有些任务需要异步(提交到线程池中)去执行,而主线程往往需要知道异步执行产生的结果,这时我们要怎么做呢?用runnable是无法实现的,我们需要用callable看下面的代码:Java代码 import java.util.concurrent.Callable;  impo

关于XSHELL连接主机_鞠崽23333的博客-程序员秘密_xshell连接主机

xshell 6如何连接本地虚拟机?Xhell是一款强大的安全终端模拟软件,很多新手没有办法进行远程实战操作, 所以下面就为用户们介绍xshell 6连接本地虚拟机的操作方法。 具体请看下文! 1.首先打开虚拟机,登录到操作系统,鼠标右键打开终端,输入命令:ifconfig,注意查看 eth0 这一栏中的 inet 即为虚拟机的ip地址...

最短路径算法—Bellman-Ford(贝尔曼-福特)算法分析与实现(C/C++)_沧浪之水清兮的博客-程序员秘密

相关文章:1.Dijkstra算法:http://www.wutianqi.com/?p=18902.Floyd算法:http://www.wutianqi.com/?p=1903Dijkstra算法是处理单源最短路径的有效算法,但它局限于边的权值非负的情况,若图中出现权值为负的边,Dijkstra算法就会失效,求出的最短路径就可能是错的。这时候,就需要使用

随便推点

java8之parallelStream并发_ylf尘风的博客-程序员秘密_parallelstream 设置线程池大小

java8之stream一、stream介绍1、基础用法二、parallelStream并行parallelStream并行,底层实现采用ForkJoin,线程池大小默认为:cpu核心数*获取cpu核心数代码:Runtime.getRuntime().availableProcessors();*手动设置ForkJoin线程池大小// 线程池大小配置String FORK_JOIN_POOL_PARALLELISM = "java.util.concurrent.ForkJoinPool

【Android自动化打包】03. APK的数字签名_weixin_34087503的博客-程序员秘密

1. 什么是数字签名?数字签名就是为你的程序打上一种标记,来作为你自己的标识,当别人看到签名的时候会知道它是与你相关的2. 为什么要数字签名?最简单直接的回答: 系统要求的。Android系统要求每一个Android应用程序必须要经过数字签名才能够安装到系统中,也就是说如果一个Android应用程序没有经过数字签名,是没有办法安装到系统中的!And...

01-Grafana+Prometheus+exports+Alertmanager监控告警系统基础配置_zhangpfly的博客-程序员秘密

Grafana+Prometheus+Exporter+Alertmanager监控告警系统1. GrafanaPrometheusExporter+Alertmanager监控告警系统说明1.1 简述1.2 要实现的功能2. Grafana+Prometheus安装与简单配置2.1 Grafana安装2.2 Prometheus安装1. GrafanaPrometheusExporter+Alertmanager监控告警系统说明1.1 简述grafana+Prometheus的组合是现在一个比较流行

docker常见报错[email protected]的博客-程序员秘密

在我们实际使用docker的过程中,难免出现各种各样的错误,下面我将自己遇到的比较难解决的错误和解决方法记录下来。1.docker启动报错一(1).描述docker启动报错 :Error starting daemon: SELinux is not supported with the overlay2 graph driver on this kernel. Either boot...

面向对象提高———友元_過去※的博客-程序员秘密

友元函数:1:一个类的友元函数可以访问该类的私有成员.#include &lt;iostream&gt;using namespace std;class CCar ; //提前声明 CCar类,以便后面的CDriver类使用class CDriver{ public: void ModifyCar( CCar * pCar) ; //改装汽车};class CCar{ private: int price; public: CCar(int p):pric

java面试刷题------Java基础(二)_Fuly1024的博客-程序员秘密

解释内存中的栈(stack)、堆(heap)和静态区(static area)的用法。  答:通常我们定义一个基本数据类型的变量,一个对象的引用,还有就是函数调用的现场保存都使用内存中的栈空间;而通过new关键字和构造器创建的对象放在堆空间;程序中的字面量(literal)如直接书写的100、”hello”和常量都是放在静态区中。栈空间操作起来最快但是栈很小,通常大量的对象都是放在堆空间,理论上...

推荐文章

热门文章

相关标签