前幾天一個有個同學咨詢我關於java socket編程的一些問題,因為我這個同學今年剛從.NET轉到java 對於java的IO體系不是很清楚,在給他解答一些問題時我自己也總結了比較容易出錯的問題。
我們直接貼一段socket代碼看一下
客戶端:
public class SocketClient {
public static void main(String[] args) throws UnknownHostException, IOException, InterruptedException {
Socket client = new Socket("localhost",8888);
OutputStream out = client.getOutputStream();
InputStream input = client.getInputStream();
out.write("sender say hello socket".getBytes());
out.flush();
read(input);
out.close();
}
public static void read(InputStream input) throws IOException {
byte[] buf = new byte[128];
int size = 0;
while ((size = input.read(buf,0,buf.length)) != -1) {
System.out.print(new String(buf));
}
}
}
服務端:
public class SocketServer {
public static void main(String[] args) {
SocketServer ss = new SocketServer();
int port = 8888;
try {
ss.startServer(port);
} catch (Exception e) {
e.printStackTrace();
}
}
public void startServer(int port) throws Exception {
ServerSocket serverSocket = new ServerSocket(port);
Socket server = null;
try {
while (true) {
server = serverSocket.accept();
System.out.println("server socket is start……");
try {
BufferedReader input = new BufferedReader(new InputStreamReader(new ByteArrayInputStream("服務端發給客戶端的信息".getBytes())));
BufferedInputStream in = new BufferedInputStream(server.getInputStream());
PrintWriter out = newPrintWriter(newOutputStreamWriter(server.getOutputStream()));
String clientstring = null;
out.println("歡迎客戶端連入");
out.flush();
byte[] buf = new byte[128];
int size = 0;
while (( size = in.read(buf,0,buf.length)) != -1) {
System.out.println(new String(buf));
}
out.print(" client is over");
out.flush();
out.close();
System.out.println(" client is over");
} catch (Exception e) {
e.printStackTrace();
} finally {
server.close();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
serverSocket.close();
}
}
}
這段代碼執行以后會發現server類 read()方法發生了阻塞,經過查找資料發現 read() 是一個阻塞函數,如果客戶端沒有聲明斷開outputStream那么它就會認為客戶端仍舊可能發送數據,像read()這種阻塞讀取函數還有
BufferedReader
類種的 readLine()、DataInputStream種的readUTF()等。
下面我們來看下一些解決方案
1)Socket任意一端在調用完write()方法時調用shutdownOutput()方法關閉輸出流,這樣對端的inputStream上的read操作就會返回-1, 這里我們要注意下不能調用socket.getInputStream().close()。因為它會導致socket直接被關閉。 當然如果不需要繼續在socket上進行讀操作,也可以直接關閉socket。但是這個方法不能用於通信雙方需要多次交互的情況。
out.write("sender say hello socket".getBytes());
out.flush();
client.shutdownOutput(); //調用shutdown 通知對端請求完畢
這個解決方案缺點非常明顯,socket任意一端都依賴於對方調用
shutdownOutput()來完成read返回 -1,如果任意一方沒有執行shutdown函數那么就會出現問題。所以一般我們都會在socket請求時設置連接的超時時間
socket.setSoTimeout(5000);以防止長時間沒有響應造成系統癱瘓。
while (true) {
server = serverSocket.accept();
System.out.println("server socket is start……");
server.setSoTimeout(5000);
.....
}
2)發送數據時,約定數據的首部固定字節數為數據長度。這樣讀取到這個長度的數據后,就不繼續調用read方法
這種方式優點是不依賴對方調用shutdown函數,響應較快,缺點是數據傳輸是最大字節數固定,需雙方事先約定好長度,伸縮性差。
3)發送數據時,約定前幾位返回數據byte[]長度大小或最后輸出 \n 或 \r 作為數據傳輸終止符。
客戶端
out.write("sender say hello socket \n".getBytes());
out.flush();
服務器端
byte[] buf = new byte[1];
int size = 0;
StringBuffer sb = new StringBuffer();
while (( size = in.read(buf,0,buf.length)) != -1) {
String str = new String(buf);
if(str.equals("\n")) {
break;
}
sb.append(str);
System.out.print(str);
}
這種方式是對第二種方案的改良版,但不得不說 這是目前socket數據傳輸的最常用處理read()阻塞的解決方案。
