Java随笔记 - BIO Socket 编程实例
Java隨筆記 - BIO Socket 編程實例
Review
-
在上上篇博客(Java隨筆記 - TCP通信的基本過程,三次握手,四次揮手)的最后,留下了三個經常被追問的問題,這里做一下個人的總結:
-
為什么需要三次握手?兩次行嗎?會有什么問題?
- 針對這個問題,從為什么兩次握手不行來進行回答。我們假設就采用兩次握手的策略來建立連接,也就是說,客戶端向服務器發起一個連接請求,服務端收到這一連接請求后,返回一個確認包,連接就建立了。這看起來似乎沒什么問題,也可以達到和三次握手同樣的作用效果。但是,試設想,客戶端向服務端發出的一個連接請求,在傳輸過程中并沒有丟失,而是在某個網絡節點滯留了較長時間,以至于客戶端的連接釋放后該報文才到達服務端。此時服務端收到的其實是一個已經失效的報文,但是服務端對此并不知情,而以為是客戶端發起的一個新的連接請求,于是服務端在返回一個確認包后就進入連接狀態,但是客戶端收到他的確認包之后只會將其當作無效報文丟棄,最后導致服務端傻傻的等待著客戶端發來數據,但其實他是等不到的,只是導致服務端資源的浪費。這就是使用兩次握手會遇到的問題,所以使用兩次握手是不行的,還需要最后客戶端向服務端返回一個確認包,即第三次握手,才能正確的把連接建立起來。
-
為什么需要四次揮手?
- TCP是使用全雙工模式進行數據交流的,也就是說,當客戶端向服務端發送一個FIN包,表明其想要斷開連接,服務端給客戶端返回一個ACK包進行確認,但這只是說明客戶端已經沒有數據要發送給服務端了,此時客戶端還是可以接收來自服務端的信息的。所以在服務端發送完他的數據后,他再向客戶端發送一個FIN包,表示其數據已經發送完畢,連接可以斷開,客戶端返回一個確認包后此次TCP連接就結束了。
-
為什么最后客戶端的TIME_WAIT狀態的時間為2MSL?
- 最后客戶端發送給服務端的ACK包可能出現丟失的情況,導致服務端收不到該確認包,所以客戶端在最后不會馬上關閉端口,而是會進入TIME_WAIT狀態,因為如果服務端沒收到相應的ACK包,會向客戶端發來重發請求,客戶度啊需要再向服務端發送ACK包。至于為什么TIME_WAIT的時間是2MSL,MSL指的是一個報文在網絡中的最長生存時間,而2MSL就是一個發送和一個回復所需要的最長時間,如果直到2MSL客戶端都沒有收到服務端的FIN包,那么就可以推斷服務端已經成功收到ACK包了,就可以關閉其端口了。
-
BIO Socket編程實例
- 在上篇博客(Java隨筆記 - Java BIO,Socket通信)中,大致講述了BIO Socket典型的編程模型,以及幾個常用的API。這里就給出兩個簡單的編程實例,分別實現的是字節流傳輸和字符流傳輸
1)Java Socket實現字節流傳輸
-
本實例將實現一個基于短連接的二進制字節流文件傳輸,在連接建立后,客戶端向服務端傳輸一個文件,并在文件傳輸完成后通過調用socket.shutdownOutput( )方法告知服務端其數據已經發送完成。之后,客戶端則是等待服務端完成邏輯處理后,返回標識(“ok”),然后結束連接。而服務端則是循環調用socket.isInputShutDown( )來判斷客戶端是否已經完成了數據的傳輸,一旦返回的是true,說明客戶端的數據傳輸完成,服務端就會退出讀取數據的循環。具體實現代碼如下:
// 客戶端代碼 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);}}} // 服務端代碼 Processor.java 實現了Runnable接口 // Server每接收到一個客戶端的請求,就會實例化一個Processor對象,開啟一個線程進行處理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);}} } // 服務端代碼 Server.java // 主要就是負責監聽來自客戶端的連接請求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實現字符流傳輸
-
TCP協議是基于二進制字節流的,字符流則是在字節流的基礎上增加了字符編解碼的過程,從而實現字節和字符之間的轉換。Java提供了面向字符的流處理API,通過這些API我們可以很方便的以字符流的形式讀寫Socket。
-
本實例將實現一個基于字符流和長連接的Socket通信。在代碼中,使用BufferedReader的readLine()方法和PrintWriter的println()方法分別進行讀寫操作,這其實已經約定了基于字符流的傳輸協議,也就是每次傳輸一串字符,并以換行符表示一行結束。具體代碼如下,其中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);}} } // 服務端代碼 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);}} }
總結
以上是生活随笔為你收集整理的Java随笔记 - BIO Socket 编程实例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实战项目——小王优品铺
- 下一篇: oracle触发器实例