Java随笔记 - BIO Socket 编程实例
Java隨筆記 - BIO Socket 編程實例
Review
-
在上上篇博客(Java隨筆記 - TCP通信的基本過程,三次握手,四次揮手)的最后,留下了三個經(jīng)常被追問的問題,這里做一下個人的總結(jié):
-
為什么需要三次握手?兩次行嗎?會有什么問題?
- 針對這個問題,從為什么兩次握手不行來進(jìn)行回答。我們假設(shè)就采用兩次握手的策略來建立連接,也就是說,客戶端向服務(wù)器發(fā)起一個連接請求,服務(wù)端收到這一連接請求后,返回一個確認(rèn)包,連接就建立了。這看起來似乎沒什么問題,也可以達(dá)到和三次握手同樣的作用效果。但是,試設(shè)想,客戶端向服務(wù)端發(fā)出的一個連接請求,在傳輸過程中并沒有丟失,而是在某個網(wǎng)絡(luò)節(jié)點滯留了較長時間,以至于客戶端的連接釋放后該報文才到達(dá)服務(wù)端。此時服務(wù)端收到的其實是一個已經(jīng)失效的報文,但是服務(wù)端對此并不知情,而以為是客戶端發(fā)起的一個新的連接請求,于是服務(wù)端在返回一個確認(rèn)包后就進(jìn)入連接狀態(tài),但是客戶端收到他的確認(rèn)包之后只會將其當(dāng)作無效報文丟棄,最后導(dǎo)致服務(wù)端傻傻的等待著客戶端發(fā)來數(shù)據(jù),但其實他是等不到的,只是導(dǎo)致服務(wù)端資源的浪費。這就是使用兩次握手會遇到的問題,所以使用兩次握手是不行的,還需要最后客戶端向服務(wù)端返回一個確認(rèn)包,即第三次握手,才能正確的把連接建立起來。
-
為什么需要四次揮手?
- TCP是使用全雙工模式進(jìn)行數(shù)據(jù)交流的,也就是說,當(dāng)客戶端向服務(wù)端發(fā)送一個FIN包,表明其想要斷開連接,服務(wù)端給客戶端返回一個ACK包進(jìn)行確認(rèn),但這只是說明客戶端已經(jīng)沒有數(shù)據(jù)要發(fā)送給服務(wù)端了,此時客戶端還是可以接收來自服務(wù)端的信息的。所以在服務(wù)端發(fā)送完他的數(shù)據(jù)后,他再向客戶端發(fā)送一個FIN包,表示其數(shù)據(jù)已經(jīng)發(fā)送完畢,連接可以斷開,客戶端返回一個確認(rèn)包后此次TCP連接就結(jié)束了。
-
為什么最后客戶端的TIME_WAIT狀態(tài)的時間為2MSL?
- 最后客戶端發(fā)送給服務(wù)端的ACK包可能出現(xiàn)丟失的情況,導(dǎo)致服務(wù)端收不到該確認(rèn)包,所以客戶端在最后不會馬上關(guān)閉端口,而是會進(jìn)入TIME_WAIT狀態(tài),因為如果服務(wù)端沒收到相應(yīng)的ACK包,會向客戶端發(fā)來重發(fā)請求,客戶度啊需要再向服務(wù)端發(fā)送ACK包。至于為什么TIME_WAIT的時間是2MSL,MSL指的是一個報文在網(wǎng)絡(luò)中的最長生存時間,而2MSL就是一個發(fā)送和一個回復(fù)所需要的最長時間,如果直到2MSL客戶端都沒有收到服務(wù)端的FIN包,那么就可以推斷服務(wù)端已經(jīng)成功收到ACK包了,就可以關(guān)閉其端口了。
-
BIO Socket編程實例
- 在上篇博客(Java隨筆記 - Java BIO,Socket通信)中,大致講述了BIO Socket典型的編程模型,以及幾個常用的API。這里就給出兩個簡單的編程實例,分別實現(xiàn)的是字節(jié)流傳輸和字符流傳輸
1)Java Socket實現(xiàn)字節(jié)流傳輸
-
本實例將實現(xiàn)一個基于短連接的二進(jìn)制字節(jié)流文件傳輸,在連接建立后,客戶端向服務(wù)端傳輸一個文件,并在文件傳輸完成后通過調(diào)用socket.shutdownOutput( )方法告知服務(wù)端其數(shù)據(jù)已經(jīng)發(fā)送完成。之后,客戶端則是等待服務(wù)端完成邏輯處理后,返回標(biāo)識(“ok”),然后結(jié)束連接。而服務(wù)端則是循環(huán)調(diào)用socket.isInputShutDown( )來判斷客戶端是否已經(jīng)完成了數(shù)據(jù)的傳輸,一旦返回的是true,說明客戶端的數(shù)據(jù)傳輸完成,服務(wù)端就會退出讀取數(shù)據(jù)的循環(huán)。具體實現(xiàn)代碼如下:
// 客戶端代碼 Client.javapublic class Client {public static final Logger logger = LoggerFactory.getLogger(Client.class);public static void main(String[] args) {BufferedReader socketReader = null;BufferedInputStream fileInputStream = null;BufferedOutputStream socketOutputStream = null;Socket socket = null;byte[] buffer = new byte[16];try {socket = new Socket("127.0.0.1", 8080);fileInputStream = new BufferedInputStream(new FileInputStream(new File("tempClient.txt")));socketOutputStream = new BufferedOutputStream(socket.getOutputStream());int readByte;while ((readByte = fileInputStream.read(buffer)) > 0) {logger.info("read {} bytes from the file.", readByte);socketOutputStream.write(buffer, 0, readByte);socketOutputStream.flush();}socket.shutdownOutput();logger.info("finish writing data to server.");socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));String response = socketReader.readLine();if ("ok".equalsIgnoreCase(response)) {logger.info("transport the data to server successfully.");}} catch (Exception e) {logger.error("error in client", e);} finally {StreamUtil.close(socketReader);StreamUtil.close(fileInputStream);StreamUtil.close(socketOutputStream);StreamUtil.close(socket);}}} // 服務(wù)端代碼 Processor.java 實現(xiàn)了Runnable接口 // Server每接收到一個客戶端的請求,就會實例化一個Processor對象,開啟一個線程進(jìn)行處理public class Processor implements Runnable {public static final Logger logger = LoggerFactory.getLogger(Processor.class);private Socket socket = null;public Processor(Socket socket) {this.socket = socket;}@Overridepublic void run() {BufferedInputStream socketInputStream = null;BufferedOutputStream fileOutputStream = null;PrintWriter socketWriter = null;byte[] buffer = new byte[16];try {fileOutputStream = new BufferedOutputStream(new FileOutputStream(new File("tempServer.txt")));socketInputStream = new BufferedInputStream(socket.getInputStream());int readByte;while (!socket.isInputShutdown() && (readByte = socketInputStream.read(buffer)) > 0) {logger.info("receive {} bytes from client.", readByte);fileOutputStream.write(buffer, 0, readByte);fileOutputStream.flush();}logger.info("finish reading data from client.");socketWriter = new PrintWriter(socket.getOutputStream(), true);socketWriter.println("ok");} catch (Exception e) {logger.error("error in processor", e);} finally {StreamUtil.close(socketInputStream);StreamUtil.close(fileOutputStream);StreamUtil.close(socketWriter);StreamUtil.close(this.socket);}} } // 服務(wù)端代碼 Server.java // 主要就是負(fù)責(zé)監(jiān)聽來自客戶端的連接請求public class Server implements Runnable {public static final Logger logger = LoggerFactory.getLogger(Server.class);private final ExecutorService threadPool = Executors.newCachedThreadPool();private ServerSocket serverSocket = null;public void start() throws IOException {serverSocket = new ServerSocket(8080);threadPool.execute(this);}@Overridepublic void run() {Socket socket = null;try {while ((socket = serverSocket.accept()) != null) {logger.info("client {} connected.", socket.getRemoteSocketAddress());threadPool.execute(new Processor(socket));}} catch (Exception e) {e.printStackTrace();}}public void close() {if (serverSocket != null && !serverSocket.isClosed()) {try {serverSocket.close();} catch (IOException e) {logger.error("error on close.", e);}}threadPool.shutdown();}public static void main(String[] args) {Server server = new Server();BufferedReader keyboardReader = null;try {server.start();System.out.println("type 'exit' to end.");keyboardReader = new BufferedReader(new InputStreamReader(System.in));String cmd = null;while ((cmd = keyboardReader.readLine()) != null) {if ("exit".equalsIgnoreCase(cmd)) {break;}}} catch (Exception e) {e.printStackTrace();} finally {StreamUtil.close(keyboardReader);server.close();}}} // 工具類public class StreamUtil {public static final Logger logger = LoggerFactory.getLogger(StreamUtil.class);public static void close(Closeable stream) {if (stream == null) {return ;}try {stream.close();} catch (Exception e) {logger.error("errors on close {}", stream.getClass().getName(), e);}}}
2)Java Socket實現(xiàn)字符流傳輸
-
TCP協(xié)議是基于二進(jìn)制字節(jié)流的,字符流則是在字節(jié)流的基礎(chǔ)上增加了字符編解碼的過程,從而實現(xiàn)字節(jié)和字符之間的轉(zhuǎn)換。Java提供了面向字符的流處理API,通過這些API我們可以很方便的以字符流的形式讀寫Socket。
-
本實例將實現(xiàn)一個基于字符流和長連接的Socket通信。在代碼中,使用BufferedReader的readLine()方法和PrintWriter的println()方法分別進(jìn)行讀寫操作,這其實已經(jīng)約定了基于字符流的傳輸協(xié)議,也就是每次傳輸一串字符,并以換行符表示一行結(jié)束。具體代碼如下,其中Server.java以及StreamUtil.java同上,不再贅述:
// 客戶端代碼 Client.javapublic class Client {public static final Logger logger = LoggerFactory.getLogger(Client.class);public static void main(String[] args) {BufferedReader socketReader = null;BufferedReader keyboardReader = null;PrintWriter socketWriter = null;Socket socket = null;String cmd = null;try {socket = new Socket("127.0.0.1", 8080);socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));socketWriter = new PrintWriter(socket.getOutputStream(), true);System.out.println("Type 'bye' to exit.");keyboardReader = new BufferedReader(new InputStreamReader(System.in));while ((cmd = keyboardReader.readLine()) != null) {socketWriter.println(cmd);String s = socketReader.readLine();System.out.println(s);if ("bye".equalsIgnoreCase(cmd)) {break;}}System.out.println("bye.");} catch (Exception e) {logger.error("errors on connection: ", e);} finally {StreamUtil.close(socketReader);StreamUtil.close(keyboardReader);StreamUtil.close(socketWriter);StreamUtil.close(socket);}} } // 服務(wù)端代碼 Processor.javapublic class Processor implements Runnable {public static final Logger logger = LoggerFactory.getLogger(Processor.class);private Socket socket;public Processor(Socket socket) {this.socket = socket;}@Overridepublic void run() {BufferedReader socketReader = null;PrintWriter socketWriter = null;try {socketReader = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));socketWriter = new PrintWriter(this.socket.getOutputStream(), true);while (!Thread.interrupted()) {String s = socketReader.readLine();socketWriter.println(String.format("%s says %s.", this.socket.getRemoteSocketAddress(), s));System.out.println(Thread.currentThread().getName() + " - " + s);if ("bye".equalsIgnoreCase(s)) {break;}}} catch (Exception e) {logger.error("error on processor.", e);} finally {StreamUtil.close(socketReader);StreamUtil.close(socketWriter);}} }
總結(jié)
以上是生活随笔為你收集整理的Java随笔记 - BIO Socket 编程实例的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实战项目——小王优品铺
- 下一篇: Java之移位运算符