Java通訊實戰:編寫自定義通訊協議實現FTP服務

前言編程

之前,對JAVA通訊,瞭解的很少,有些東西都迷迷糊糊的,通過一段時間的學習,知道服務器

了很多,也編寫了一個簡單的FTP服務器,下面分享給你們!
併發


實戰app


要作什麼?socket


wKioL1ZG4eGCjrJ9AAAzgNBIyL8486.png


咱們知道,不少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操做。


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進行處理!

相關文章
相關標籤/搜索