前言编程
之前,对JAVA通讯,了解的很少,有些东西都迷迷糊糊的,通过一段时间的学习,知道服务器 了很多,也编写了一个简单的FTP服务器,下面分享给你们! |
实战app
要作什么?socket 咱们知道,不少WEB服务器,例如Apache HTTPD,Nginx等都提供相似上面图示的方式进行工做:ide Server负责Worker的建立,销毁;学习 Woker负责具体与客户端的通讯,处理请求;编码 那么,咱们接下来要作的就是一个简单的例子,实现客户端和服务端的交互,例如发送spa 文本消息,客户端上传文件到服务器,服务器提供下载文件功能。线程 要通讯,就要约定协议! 咱们知道计算机发送,接受的都是字节数据,若是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操做。 注意到,因为咱们设计到3种命令格式,只须要一个BYTE来表明COMMAND TYPE,所以咱们 需要readByte/writeByte方法;因为咱们须要消息/文件的长度信息,所以咱们须要readLong/writeLong方法;既然涉及到流,必然须要关闭,咱们能够给SocketWrapper打上Closeable标签,提供close方法(实际上,InputStream/OutputStream/Reader/Writer都是打上了Closeable标签的);另外,提供了writeString方法,会将String信息以CharsetByte指定的编码格式进行写入;writeFile方法则是针对文件。咱们能够先来看看writeFile的实现: 这里须要注意的是: 根据文件大小来选择一次性字节发送,仍是分批发送; 要知道若是一次性将很是大的文件字节流发送到对方,会形成对方内存区域紧张,而 分批字节发送会很好的缓解压力! 提供和协议相关的信息类 字符集信息类: 对于服务器,须要知道根据编码BYTE找到字符集,对于客户端,须要根据字符集找到 对应编码的BYTE。 那么在内存中,应该存在初始化好的字符集! 命令信息类: 咱们能够清楚的看到,经过ENUM,咱们轻松完成了字符串命令与命令编码的映射关系! 更加剧要的是见名知意! 咱们来看看getSendableClass()是干吗的呢? 很显然,若是sendMsg,那么是一类处理手段,若是是sendFile将是另外一类处理手段。 一样的,在内存中,咱们应该初始化好这类信息: 提供客户端处理类 对于sendMsg,sendFile,downloadFile而言,它们是能够抽象出来的! 咱们能够来具体看一看SendFileable这个类: 先来看看getCommandType(): 其实,就是为了客户端向服务端发送命令类型提供支持! String[] token是什么呢? 对于sendMsg charset=gbk 世界,你好 而言,token就是{“sendMsg” , “gbk” , “世界,你好”}。也就是说,TOKEN其实就是一组逻辑单元! 看看具体的doTask()是怎么作的: 第一步,发送命令类型; 第二步,发送文件名称编码以及文件名称对应编码的字节流以及长度 第三步,等待服务端响应,若是服务端已经存在了此文件,则拒绝;不然开始writeFile 感悟: 有些时候,咱们须要等待;而不是一股脑的把东西都发送过去,也许是没必要要的! 让客户端运转起来!---》ClientMain 循环起来: 客户端在CMD下发送的命令,首先经过LineProcesser预处理下,而后造成TOKEN,根据 TOKEN找到对应处理类,利用反射实例化处理类,调用doTask方法便可! 提供服务端处理类: Worker是具体负责和客户端通讯的线程,应该持有SocketWrapper的引用,同时经过ID来 进行Worker的标示,下面咱们来看看run()是怎么处理的: processMsg/processSendFile/processDownloadFile具体实现,很简单了,你们能够 本身动手去实现! ServerMain: 经过代码,咱们清楚的看到了,每accept一个client socket,服务端就new一个 Worker进行处理! |