前言編程
之前,對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進行處理! |