Java Socket上的Read操作阻塞問(wèn)題詳解
Socket上的Read操作阻塞問(wèn)題
從Socket上讀取對(duì)端發(fā)過(guò)來(lái)的數(shù)據(jù)一般有兩種方法
1)按照字節(jié)流讀取
BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); int r = -1; List<Byte> l = new LinkedList<Byte>(); while ((r = in.read()) != -1) { l.add(Byte.valueOf((byte) r)); }
2)按照字符流讀取
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); String s; while ((s = in.readLine()) != null) { System.out.println("Reveived: " + s); }
這兩個(gè)方法read()和readLine()都會(huì)讀取對(duì)端發(fā)送過(guò)來(lái)的數(shù)據(jù),如果無(wú)數(shù)據(jù)可讀,就會(huì)阻塞直到有數(shù)據(jù)可讀。或者到達(dá)流的末尾,這個(gè)時(shí)候分別返回-1和null。
這個(gè)特性使得編程非常方便也很高效。
但是這樣也有一個(gè)問(wèn)題,就是如何讓程序從這兩個(gè)方法的阻塞調(diào)用中返回。
總結(jié)一下,有這么幾個(gè)方法
1)發(fā)送完后調(diào)用Socket的shutdownOutput()方法關(guān)閉輸出流,這樣對(duì)端的輸入流上的read操作就會(huì)返回-1。
注意不能調(diào)用socket.getInputStream().close()。這樣會(huì)導(dǎo)致socket被關(guān)閉。
當(dāng)然如果不需要繼續(xù)在socket上進(jìn)行讀操作,也可以直接關(guān)閉socket。
但是這個(gè)方法不能用于通信雙方需要多次交互的情況。
2)發(fā)送數(shù)據(jù)時(shí),約定數(shù)據(jù)的首部固定字節(jié)數(shù)為數(shù)據(jù)長(zhǎng)度。這樣讀取到這個(gè)長(zhǎng)度的數(shù)據(jù)后,就不繼續(xù)調(diào)用read方法。
3)為了防止read操作造成程序永久掛起,還可以給socket設(shè)置超時(shí)。
如果read()方法在設(shè)置時(shí)間內(nèi)沒(méi)有讀取到數(shù)據(jù),就會(huì)拋出一個(gè)java.net.SocketTimeoutException異常。
例如下面的方法設(shè)定超時(shí)3秒。
socket.setSoTimeout(3000);
Socket編程---read方法阻塞問(wèn)題
java通信項(xiàng)目簡(jiǎn)單寫(xiě)了個(gè)聊天室,實(shí)現(xiàn)群聊私聊了,就大言不慚地往簡(jiǎn)歷上寫(xiě)了對(duì)java網(wǎng)絡(luò)編程和多線(xiàn)程有了一定的了解。給客戶(hù)端各自開(kāi)了線(xiàn)程,寫(xiě)了句server.accept()、Socket client=new Socket("127.0.0.1",9999),就叫了解了? Too young too simple.
然而一問(wèn),BIO和NIO有什么區(qū)別?--- 納尼?什么玩意兒?
- 那你說(shuō)說(shuō)你那個(gè)聊天室的流? --- 臥槽,這有什么好說(shuō)的,get啊,輸入流用來(lái)讀的,輸出流用來(lái)寫(xiě)數(shù)據(jù)給對(duì)方的
- 可能知道我沒(méi)get到點(diǎn)吧,那你自己說(shuō)一下你的通信項(xiàng)目吧 --- 哈哈,先創(chuàng)建一個(gè)ServerSocket對(duì)象,然后accept等客戶(hù)端來(lái)連,給每個(gè)客戶(hù)端都開(kāi)一個(gè)線(xiàn)程各自處理
- 奧,你是給每個(gè)客戶(hù)端都開(kāi)了一個(gè)線(xiàn)程啊============ 結(jié)束會(huì)話(huà)
聊完了啊,可是我并不知道發(fā)生了什么,這特么到底要問(wèn)啥啊?,F(xiàn)在一想,可能對(duì)方覺(jué)得我特么就是傻缺吧,一個(gè)只會(huì)碼而沒(méi)有思想的人。
那不管,先不說(shuō)BIO和NIO,通過(guò)這個(gè),我倒是好像get到他是不是在跟我說(shuō)流的阻塞問(wèn)題。那么,總結(jié)一下吧。
講文件流的時(shí)候,我們會(huì)看到這兩個(gè)read方法,看一下API
如果只是使用一次,那么沒(méi)有數(shù)據(jù)讀的時(shí)候,會(huì)一直阻塞,然后想執(zhí)行下面那是不可能的了,直到有數(shù)據(jù)可讀;
如果用在while里,那么沒(méi)讀到文件末尾,也是一直阻塞的,直到被返回-1。
文件它是自己知道讀到文件末尾了,那么我們的Socket是兩端的通信,一直等待著對(duì)方傳來(lái)數(shù)據(jù)的,并不知道啥時(shí)候會(huì)完,所以就會(huì)一直不會(huì)等于-1,阻塞在while循環(huán)里了,下面的代碼就不會(huì)被執(zhí)行。
package SocketIO; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) throws IOException { String m = ""; ServerSocket ss = new ServerSocket(9999); Socket s = ss.accept(); String ip = s.getInetAddress().getHostAddress(); System.out.println(ip+"...connected..."); m = ip+"...connected..."; InputStream in = s.getInputStream(); int len = 0; byte[] buf = new byte[1024]; while((len=in.read(buf))!=-1){ m += new String(buf); } System.out.println(m); OutputStream out = s.getOutputStream(); out.write("飯菜馬上到".getBytes()); out.flush(); in.close(); out.close(); s.close(); ss.close(); } }
package SocketIO; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.UnknownHostException; public class Client { public static void main(String[] args) throws UnknownHostException, IOException { Socket s = new Socket("127.0.0.1", 9999); OutputStream out = s.getOutputStream(); out.write("我肚子餓了".getBytes()); out.flush(); String m = ""; InputStream in = s.getInputStream(); byte[] buf = new byte[1024]; int len = 0; while((len=in.read(buf))!=-1){ m += new String(buf); } System.out.println(m); in.close(); out.close(); s.close(); } }
當(dāng)客戶(hù)端連接上服務(wù)器時(shí),把“我肚子餓了”寫(xiě)給服務(wù)器,服務(wù)器讀的時(shí)候不知道客戶(hù)端發(fā)沒(méi)發(fā)完,就一直阻塞在while循環(huán)里,故服務(wù)器的console只會(huì)輸出
,
而客戶(hù)端的console什么也不會(huì)輸出
那么,我們只用加一句,
客戶(hù)端寫(xiě)完數(shù)據(jù)后,就直接把輸出流shutdown,那么服務(wù)器讀到的就會(huì)是-1,跳出循環(huán),繼續(xù)往下執(zhí)行。那為什么服務(wù)器寫(xiě)完數(shù)據(jù)后不把輸出流shutdown,你沒(méi)看見(jiàn)所有的流都close了嗎?客戶(hù)端當(dāng)然會(huì)讀到-1,進(jìn)而輸出我們想看到的東東了。如果你還不服,你把close都注釋掉,看看效果(都注釋掉了客戶(hù)端讀的時(shí)候又阻塞了),再加上s.shutdownOutput()再看看效果(這會(huì)又恢復(fù)正常了)。
這個(gè)例子其實(shí)只是想說(shuō)明socket編程中流的read方法是阻塞的。
那么,回到我們的聊天室來(lái),我一個(gè)服務(wù)器要處理這么多個(gè)客戶(hù)端,如果一個(gè)客戶(hù)端的read方法阻塞了,那別的客戶(hù)端不都得等著它嗎?顯然,這是不可能的。那,一個(gè)客戶(hù)端給一個(gè)線(xiàn)程吧,讓它們自己阻塞自己的。
對(duì)于服務(wù)器端而言,給每個(gè)客戶(hù)端啟動(dòng)一個(gè)線(xiàn)程,然后在每個(gè)客戶(hù)端各自的線(xiàn)程里循環(huán)去讀客戶(hù)端發(fā)來(lái)的數(shù)據(jù),沒(méi)的話(huà)阻塞等待直到有,有的話(huà)轉(zhuǎn)發(fā),所以阻塞在循環(huán)里也無(wú)所謂,反正while循環(huán)之外我暫時(shí)也沒(méi)想著執(zhí)行。當(dāng)然,還是要把它結(jié)束掉。
對(duì)于服務(wù)器端,先讀后轉(zhuǎn)發(fā),嵌在一個(gè)循環(huán)里,不會(huì)有什么問(wèn)題。
而對(duì)于客戶(hù)端而言,我要隨時(shí)監(jiān)聽(tīng)去讀,又想著隨時(shí)去寫(xiě)。那寫(xiě)的操作不能在讀的循環(huán)里了啊,只能寫(xiě)在外面。而讀的操作又是一直阻塞著的,豈會(huì)讓出時(shí)間讓你寫(xiě)?那,大招來(lái)了。你把讀的操作放一個(gè)線(xiàn)程里不就得了,由它去阻塞,想怎么讀就怎么讀。那互不影響的話(huà),我也想怎么寫(xiě)就怎么寫(xiě)啊。
寫(xiě)的操作我就不截圖了,就是在主線(xiàn)程里完成的。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java設(shè)計(jì)模式之代理模式原理及實(shí)現(xiàn)代碼分享
這篇文章主要介紹了Java設(shè)計(jì)模式之代理模式原理及實(shí)現(xiàn)代碼分享,設(shè)計(jì)代理模式的定義,靜態(tài)代理,動(dòng)態(tài)代理,jdk動(dòng)態(tài)代理實(shí)現(xiàn)步驟,原理及源碼等相關(guān)內(nèi)容,具有一定參考價(jià)值,需要的朋友可以了解下。2017-11-11springboot接口接收數(shù)組及多個(gè)參數(shù)的問(wèn)題及解決
這篇文章主要介紹了springboot接口接收數(shù)組及多個(gè)參數(shù)的問(wèn)題及解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11Java 8 Stream 的終極技巧——Collectors 功能與操作方法詳解
這篇文章主要介紹了Java 8 Stream Collectors 功能與操作方法,結(jié)合實(shí)例形式詳細(xì)分析了Java 8 Stream Collectors 功能、操作方法及相關(guān)注意事項(xiàng),需要的朋友可以參考下2020-05-05詳解Android系統(tǒng)中的root權(quán)限獲得原理
這篇文章主要介紹了詳解Android系統(tǒng)中的Root權(quán)限獲得原理,安卓基于Linux,所以原理也相當(dāng)于Linux中的root用戶(hù),需要的朋友可以參考下2015-08-08Java實(shí)現(xiàn)布隆過(guò)濾器的示例詳解
布隆過(guò)濾器(Bloom?Filter)是1970年由布隆提出來(lái)的,實(shí)際上是由一個(gè)很長(zhǎng)的二進(jìn)制數(shù)組+一系列hash算法映射函數(shù),用于判斷一個(gè)元素是否存在于集合中。本文主要介紹了Java實(shí)現(xiàn)布隆過(guò)濾器的示例代碼,希望對(duì)大家有所幫助2023-03-03使用Spring初始化加載InitializingBean()方法
這篇文章主要介紹了使用Spring初始化加載InitializingBean()方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01java中l(wèi)ambda(函數(shù)式編程)一行解決foreach循環(huán)問(wèn)題
這篇文章主要介紹了java中l(wèi)ambda(函數(shù)式編程)一行解決foreach循環(huán)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07Win10 Java jdk14.0.2安裝及環(huán)境變量配置詳細(xì)教程
這篇文章主要介紹了Win10 Java jdk14.0.2安裝及環(huán)境變量配置,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08