Java通信实战:编写自定义通信协议实现FTP服务

简介:

前言

以前,对JAVA通信,了解的不多,有些东西都迷迷糊糊的,经过一段时间的学习,知道

了不少,也编写了一个简单的FTP服务器,下面分享给大家!


实战


要做什么?


wKioL1ZG4eGCjrJ9AAAzgNBIyL8486.png


我们知道,很多WEB服务器,例如Apache HTTPD,Nginx等都提供类似上面图示的方式进行工作:


Server负责Worker的创建,销毁;

Woker负责具体与客户端的通信,处理请求;


那么,我们接下来要做的就是一个简单的例子,实现客户端和服务端的交互,例如发送

本消息,客户端上传文件到服务器,服务器提供下载文件功能。



要通信,就要约定协议!


我们知道计算机发送,接受的都是字节数据,如果A“胡乱”的给B发数据,B能知道是

什么意思吗?很显然,A应该清楚的告诉B如何接受数据,接受多大的数据,接受完毕后如何处理,数据都是些什么意思,而这些就是协议~



那么下面,就来约定协议:


sendMsg charset=gbk 世界,你好


sendFile charset=gbk JAVA并发编程实战.pdf


downloadFile charset=utf-8 JAVA编程思想.pdf


上面的格式,说明了,client可以给server发送消息、文件,还可以向server索要文

件。对于发送文本消息,很显然,接受方需要知道用什么编码将字节流进行转换;类似的,上传文件/下载文件,需要知道文件名称编码。对于文件上传下载,我们都采用字节流处理,并不涉及到转换成字符流,所以对于文件可以不用提供文件内容编码了。至于上传下载的路径,我们可以配置即可。另外,需要注意的是,不论对于发送文本消息,还是文件,都需要结束,所以需要发送消息的长度,文件的长度。具体来说,我们可以用1个BYTE来代表sendMsg/sendFile/downloadFile;用1个BYTE来代表charset;用1个LONG来代表长度;其他信息就是字节流了。



从类的角度出发进行设计


要提供SOCKET的封装类


说到底,是SOCKET之间的通信,如果不对SOCKET进行一次封装,那么就会有很多代码

反复写,而且封装之后,将隐藏流的细节,有利于外部调用。要清楚的是,SOCKET的通信,最终也是反映到IO流的操作上的,那么多JAVA IO流,选择什么流呢?我们应该从协议的角度出发,我们需要读写的协议数据格式是什么,哪些IO流提供的方法多些,方便我们操作呢?DataInputStream/DataOutputStream,这种数据流,提供了众多数据格式的write/read操作。


wKiom1ZG9zbTELliAAA1lJEh5QQ325.png


注意到,由于我们设计到3种命令格式,只需要一个BYTE来代表COMMAND TYPE,因此我们

要readByte/writeByte方法;由于我们需要消息/文件的长度信息,因此我们需要readLong/writeLong方法;既然涉及到流,必然需要关闭,我们可以给SocketWrapper打上Closeable标签,提供close方法(实际上,InputStream/OutputStream/Reader/Writer都是打上了Closeable标签的);另外,提供了writeString方法,会将String信息以CharsetByte指定的编码格式进行写入;writeFile方法则是针对文件。我们可以先来看看writeFile的实现:


wKioL1ZG-h3iSKn1AABMHryDAKo128.png

wKiom1ZG-dywc94RAAAjUnOiuTo943.png


这里需要注意的是:


根据文件大小来选择一次性字节发送,还是分批发送;

要知道如果一次性将非常大的文件字节流发送到对方,会造成对方内存区域紧张,而

分批字节发送会很好的缓解压力!



提供和协议相关的信息类


字符集信息类:


wKiom1ZH2mKw51-XAAAjy6fJhLk026.png


对于服务器,需要知道根据编码BYTE找到字符集,对于客户端,需要根据字符集找到

对应编码的BYTE。


wKioL1ZH3CjjB6E3AAAnjARwyWE506.png



wKioL1ZH3E-wlvvUAAANMfQA2YE177.png



那么在内存中,应该存在初始化好的字符集!


wKioL1ZH3THRIu8dAAAlhhCtako089.png



命令信息类:


wKiom1ZH3UewdPOXAAAh6upJJds717.png

wKioL1ZH3d6BlT-HAAAtD00WSdY485.png


我们可以清楚的看到,通过ENUM,我们轻松完成了字符串命令与命令编码的映射关系!

加重要的是见名知意!


我们来看看getSendableClass()是干嘛的呢?


wKiom1ZH3jPimIfCAAAvfCZt614769.png


很显然,如果sendMsg,那么是一类处理手段,如果是sendFile将是另一类处理手段。


同样的,在内存中,我们应该初始化好这类信息:


wKioL1ZH4KHh9bLLAAAj5eAWN8E865.png


提供客户端处理类


对于sendMsg,sendFile,downloadFile而言,它们是可以抽象出来的!


wKiom1ZH4bfQF4-eAAAT1f7fZ3Q445.png


wKioL1ZH4iSSmWQyAAAOgpBOM58971.png


我们可以来具体看一看SendFileable这个类:


wKiom1ZH4iDgwCMmAAAaCwAftQA954.png


先来看看getCommandType():


wKiom1ZH4n6hZovdAAAQlOoiDjY035.png

其实,就是为了客户端向服务端发送命令类型提供支持!


String[] token是什么呢?


对于sendMsg charset=gbk 世界,你好  而言,token就是{“sendMsg” , “gbk” , 

世界,你好”}。也就是说,TOKEN其实就是一组逻辑单元!


看看具体的doTask()是怎么做的:


wKiom1ZH5CLQScNFAABeC9TN9BU108.png


第一步,发送命令类型;

第二步,发送文件名称编码以及文件名称对应编码的字节流以及长度

第三步,等待服务端响应,如果服务端已经存在了此文件,则拒绝;否则开始writeFile


感悟:


有些时候,我们需要等待;而不是一股脑的把东西都发送过去,也许是不必要的!


让客户端运转起来!---》ClientMain


wKioL1ZH5eDxH7UYAAAqlMHAdTw125.png


循环起来:


wKioL1ZH5g3TEKtUAABOW3n7Jx4667.png


客户端在CMD下发送的命令,首先通过LineProcesser预处理下,然后形成TOKEN,根据

TOKEN找到对应处理类,利用反射实例化处理类,调用doTask方法即可!



提供服务端处理类:


wKioL1ZH5xfQsoawAAAgJkWGDnE572.png

Worker是具体负责和客户端通信的线程,应该持有SocketWrapper的引用,同时通过ID来

行Worker的标示,下面我们来看看run()是怎么处理的:


wKiom1ZH55zCZrk0AABYPibju2g382.png


processMsg/processSendFile/processDownloadFile具体实现,很简单了,大家可以

自己动手去实现!


ServerMain:


wKioL1ZH6MDRhxVQAABQZvw8Ado837.png


通过代码,我们清楚的看到了,每accept一个client socket,服务端就new一个

Worker进行处理!


本文转自zfz_linux_boy 51CTO博客,原文链接:http://blog.51cto.com/zhangfengzhe/1712845,如需转载请自行联系原作者

相关文章
|
16天前
|
存储 Java 数据库连接
java多线程之线程通信
java多线程之线程通信
|
25天前
|
存储 Java 关系型数据库
社区医院管理服务系统【GUI/Swing+MySQL】(Java课设)
社区医院管理服务系统【GUI/Swing+MySQL】(Java课设)
25 1
|
20天前
|
设计模式 安全 Java
Java并发编程实战:使用synchronized关键字实现线程安全
【4月更文挑战第6天】Java中的`synchronized`关键字用于处理多线程并发,确保共享资源的线程安全。它可以修饰方法或代码块,实现互斥访问。当用于方法时,锁定对象实例或类对象;用于代码块时,锁定指定对象。过度使用可能导致性能问题,应注意避免锁持有时间过长、死锁,并考虑使用`java.util.concurrent`包中的高级工具。正确理解和使用`synchronized`是编写线程安全程序的关键。
|
4天前
|
Java 关系型数据库 MySQL
Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
【4月更文挑战第12天】Elasticsearch【问题记录 01】启动服务&停止服务的2类方法【及 java.nio.file.AccessDeniedException: xx/pid 问题解决】(含shell脚本文件)
29 3
|
3天前
|
安全 Java 调度
Java线程:深入理解与实战应用
Java线程:深入理解与实战应用
20 0
|
19小时前
|
消息中间件 缓存 NoSQL
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
Java多线程实战-CompletableFuture异步编程优化查询接口响应速度
|
6天前
|
JavaScript Java 测试技术
基于Java的宠物服务平台的设计与实现(源码+lw+部署文档+讲解等)
基于Java的宠物服务平台的设计与实现(源码+lw+部署文档+讲解等)
16 1
|
6天前
|
JavaScript Java 测试技术
基于Java的中学生课后服务的信息管理与推荐的设计与实现(源码+lw+部署文档+讲解等)
基于Java的中学生课后服务的信息管理与推荐的设计与实现(源码+lw+部署文档+讲解等)
24 2
|
7天前
|
存储 Java 数据库连接
java DDD 领域驱动设计思想的概念与实战
【4月更文挑战第19天】在Java开发中,领域驱动设计(Domain-Driven Design, DDD) 是一种软件设计方法论,强调以领域模型为中心的软件开发。这种方法通过丰富的领域模型来捕捉业务领域的复杂性,并通过软件满足核心业务需求。领域驱动设计不仅是一种技术策略,而且还是一种与业务专家紧密合作的思维方式
25 2
|
7天前
|
JavaScript Java 测试技术
基于Java的珠江学院大学生自愿者服务网的设计与实现(源码+lw+部署文档+讲解等)
基于Java的珠江学院大学生自愿者服务网的设计与实现(源码+lw+部署文档+讲解等)
27 0