Socket引發的死鎖問題。

     剛剛看了下socket的知識,於是自己做了一個簡單的從客戶端向服務端傳遞圖片的程序。 但是中間出現了一些小問題,程序曾經一度在某個位置進入等待狀態,無法繼續運行下去。

     下面分別是服務端和客戶端的代碼....


     客戶端代碼:

   

package cn.dzr.tcp.Client;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class ClientTcp
{
	public static void main(String[] args) throws IOException
	{
		System.out.println("客服端啓動,準備傳輸數據....");
		
		//1,創建Socket
		int port = 10010;						//設定端口號
		InetAddress address = InetAddress.getLocalHost();
		Socket socket = new Socket(address,port);
		
		//2,獲取Socket中的數據
		OutputStream out = socket.getOutputStream();
		InputStream in = socket.getInputStream();
		//選擇需要傳輸的文件.
		File file = new File("D:+"+File.separator+"Client/ke.jpg");
		
		//將文件進行傳輸
		//1,將文件內容轉爲文件流,先是傳輸到一個字節數組中。
		//2,將字節數組中的內容傳輸到out中。
		byte[] buff = new byte[1024];
		FileInputStream fis = new FileInputStream(file);
		
		int len = 0;   
		
		while((len =fis.read(buff))!=-1)
		{
			out.write(buff, 0, len);
		}
		System.out.println("文件傳輸完畢....");
		
		byte[] bufin = new byte[1024];
		int reclen = in.read(bufin);
		
		
		String str = new String(bufin,0,reclen);
		
		System.out.println("服務端傳來的消息:"+str);
		
		
		
		fis.close();
		
		
		socket.close();
		
		
	}
}

服務端的代碼如下:

 

package cn.dzr.tcp.Server;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerTcp
{
	public static void main(String[] args) throws IOException
	{
		System.out.println("服務端啓動....準備接受數據.");
		//創建ServSocket,
		ServerSocket ss = new ServerSocket(10010);
		//獲取連接過來的socket
		
		//爲了讓服務端能夠一直接受消息,我們需要定義個while循環。
		
		int count = 0;
		int time = 1;
		while(true)
		{
			System.out.println("準備第"+(time++)+"次接受數據");
		Socket socket = ss.accept();
		
		
		InputStream in = socket.getInputStream();
		OutputStream out = socket.getOutputStream();
		
		//創建文件保存傳輸過來的數據.
		File file = new File("D:"+File.separator+"Server/ke.jpg");
		
		while(file.exists())
		{
			file = new File("D:"+File.separator+"Server"+
					File.separator+"keke("+(++count)+").jpg");
		}
		//直到文件不存在,才創建新的文件。
		file.createNewFile();
		
		System.out.println("保存的文件名字爲: "+file.getName());
		//綁定輸出流,等會將接受的數據傳遞到該文件中....
		FileOutputStream fos = new FileOutputStream(file);
		
		
		byte[] buff = new byte[1024];
		
		int len = 0;  //保存讀取的字節個數
		int c = 1;
		while((len = in.read(buff))!=-1)
	  //while((len = in.read(buff))!=-1)
		{
			
			fos.write(buff, 0, len);
			System.out.println("第"+(c++)+"次"+"接受成功.. 數據長度爲:"+len );
		}
		
		String str = "文件讀取完畢!";
		System.out.println(str);
		out.write(str.getBytes());
		
		try
		{
			Thread.sleep(1000);
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		fos.close();
		socket.close();
		}
		//ss.close();
		
		
	}
}

 在啓動服務端之後,我再啓動客戶端。
       結果我發現客戶端和服務都沒有停止運行。爲了瞭解到更詳細的信息,我在服務端設置了很多的數據輸出..
我發現服務端在下面這個while循環中一直都沒有退出。

 
while((len = in.read(buff))!=-1)
	  //while((len = in.read(buff))!=-1)
		{
			
			fos.write(buff, 0, len);
			System.out.println("第"+(c++)+"次"+"接受成功.. 數據長度爲:"+len );
		}

 我百思不得其解,客戶端數據已經傳輸完畢,爲什麼服務端會停留在這個while循環,在len的值變成60之後,就沒有繼續執行下去。爲什麼服務端一直都沒有讀取到文件流的末尾,從而讓len的值變成-1,退出循環。
     
       最後,我想,如果客戶端的的socket的out流沒有關閉,那該out就可能一直都在傳輸數據。所以,服務端就會處於一直接受數據的狀態。
       我再回頭看我客戶端的代碼。

  while((len =fis.read(buff))!=-1)
		{
			out.write(buff, 0, len);
		}
		System.out.println("文件傳輸完畢....");
		
		byte[] bufin = new byte[1024];
		int reclen = in.read(bufin);

 在如上這段代碼中。
      當客戶端將整個圖片的數據發送出去之後,他就進入到“準備接受從服務端發送過來的數據”的狀態。
      而此時,由於客戶端的socket,socket中的out流都沒有關閉。
      所以導致服務端停止在while循環中。其下面的

String str = "文件讀取完畢!";
		System.out.println(str);
		out.write(str.getBytes());
  這段向客戶端發送數據的代碼就無法執行,而客戶端就一直在等待服務端傳過來的消息,以便繼續執行下去。所以,雙方都在等待對方的資源,從而造成一個類似死鎖的情況。
 
      想到這一點,於是,我就在客戶端準備接受數據之前。
      將socket中的OutputStream流給關閉。
      
      也就是在

while((len =fis.read(buff))!=-1)
		{
			out.write(buff, 0, len);
		}
		System.out.println("文件傳輸完畢....");
		
		byte[] bufin = new byte[1024];
		int reclen = in.read(bufin);

這段代碼的System.out.println("文件傳輸完畢....");的後面加上out.close();
    我想,這樣關閉了out流,那麼服務端應該就會跳出循環,讓代碼執行下去。

 
   這個時候,服務端的數據是繼續執行下去了,但是客服端卻又出現了新的問題。運行客戶端程序的時候,會出現如下的提示:


 客服端啓動,準備傳輸數據....
   文件傳輸完畢....
Exception in thread "main" java.net.SocketException: socket closed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:150)
    at java.net.SocketInputStream.read(SocketInputStream.java:121)
    at java.net.SocketInputStream.read(SocketInputStream.java:107)
    at cn.dzr.tcp.Client.ClientTcp.main(ClientTcp.java:43)


 socket closed,流已經被關閉。
 
  我想到的是,這裏客戶端的程序還沒有執行下去,因爲我沒看到輸出結果。

System.out.println("文件傳輸完畢....");
		out.close();
		byte[] bufin = new byte[1024];
		int reclen = in.read(bufin);
		
		
		String str = new String(bufin,0,reclen);
		
		System.out.println("服務端傳來的消息:"+str);


這一段代碼,下面的System.out.println("服務端傳來的消息:"+str);沒有被執行。
 那麼,這段代碼只有可能  int reclen = in.read(bufin); 這句代碼執行時出現問題。因爲如果這句通過,後面的代碼沒有什麼可以阻礙他們的運行。
            
       (at cn.dzr.tcp.Client.ClientTcp.main(ClientTcp.java:43))
        這裏其實已經標明瞭錯誤的位置....!!
        再看服務端那邊的情況:
        
        其輸出情況如下:

 服務端啓動....準備接受數據.
準備第1次接受數據
保存的文件名字爲: keke(28).jpg
第1次接受成功.. 數據長度爲:1024
第2次接受成功.. 數據長度爲:1024
第3次接受成功.. 數據長度爲:1024
第4次接受成功.. 數據長度爲:1024
第5次接受成功.. 數據長度爲:1024
第6次接受成功.. 數據長度爲:1024
第7次接受成功.. 數據長度爲:1024
第8次接受成功.. 數據長度爲:1024
第9次接受成功.. 數據長度爲:1024
第10次接受成功.. 數據長度爲:1024
第11次接受成功.. 數據長度爲:1024
第12次接受成功.. 數據長度爲:1024
第13次接受成功.. 數據長度爲:1024
第14次接受成功.. 數據長度爲:1024
第15次接受成功.. 數據長度爲:1024
第16次接受成功.. 數據長度爲:1024
第17次接受成功.. 數據長度爲:1024
第18次接受成功.. 數據長度爲:1024
第19次接受成功.. 數據長度爲:1024
第20次接受成功.. 數據長度爲:1024
第21次接受成功.. 數據長度爲:1024
第22次接受成功.. 數據長度爲:1024
第23次接受成功.. 數據長度爲:1024
第24次接受成功.. 數據長度爲:1024
第25次接受成功.. 數據長度爲:1024
第26次接受成功.. 數據長度爲:1024
第27次接受成功.. 數據長度爲:1024
第28次接受成功.. 數據長度爲:1024
第29次接受成功.. 數據長度爲:1024
第30次接受成功.. 數據長度爲:1024
第31次接受成功.. 數據長度爲:1024
第32次接受成功.. 數據長度爲:1024
第33次接受成功.. 數據長度爲:1024
第34次接受成功.. 數據長度爲:60
文件讀取完畢!
準備第2次接受數據

這標明服務端已經正常執行完成。

   那麼,到底是什麼引起客戶端的 (at cn.dzr.tcp.Client.ClientTcp.main(ClientTcp.java:43))
    
    socket closed,流已經被關閉。
  這個問題呢。
   開始我在服務端加入了一個sleep(),線程休眠。


  	System.out.println(str);
		out.write(str.getBytes());
		
		try
		{
			Thread.sleep(1000);
		} catch (InterruptedException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		fos.close();
		socket.close();
		}
		//ss.close();



   就是爲了讓服務端進行短暫的休眠,而不去執行socket.close();讓客戶端可以正常的接受數據。
   我開始以爲是因爲服務端執行速度過快,導致socket.close()執行,從而關閉了流。
   但是目前看來,情況並非如此。
   於是,我想,是不是因爲socket的out流關閉,就會導致in流也關閉,或者整個socket都被關閉呢。
   我先試試看,將out.close();改成 in.close();
   看看有什麼情況。
       
    結果,程序依然如上面 out.close() 被執行一般。
    證明in.close和out.close會起到同樣的作用。
    那麼,他是否可能會導致socket流被關閉呢。
    於是,我想在in.close()下,再加上一句socket.close();
    看下會發生什麼情況。
  

 客服端啓動,準備傳輸數據....
文件傳輸完畢....
Exception in thread "main" java.net.SocketException: socket closed
    at java.net.SocketInputStream.socketRead0(Native Method)
    at java.net.SocketInputStream.read(SocketInputStream.java:150)
    at java.net.SocketInputStream.read(SocketInputStream.java:121)
    at java.net.SocketInputStream.read(SocketInputStream.java:107)
    at cn.dzr.tcp.Client.ClientTcp.main(ClientTcp.java:44)  


錯誤依舊指向   “    int reclen = in.read(bufin); ”
    同時,我在socket.close()下加上許多句socket.close();
    在執行的時候,這些socket.close()並不會產生錯誤。
    這說明socket.close()是可以被執行無數次的,一個關閉的流再次被關閉並不會引發異常。
    
    不過,我們依然無法驗證,in.close()是否導致socket.close()發生。
    雖然在錯誤提示信息裏,有一個socket.closed的提示,但是異常分析通常具有非常大的不可預料性。很可能其他的原因,最後也導致出現這麼一個提示。
    比如在一段程序裏,你少寫一個;號,結果系統就提示一堆莫名其妙的錯誤。
    如果你試圖根據這些錯誤來解決問題,這比南轅北轍更加離譜了。

    後來發現socket有一個isclosed的方法。
     於是,我想通過代碼驗證一下。

while((len =fis.read(buff))!=-1)
		{
			out.write(buff, 0, len);
		}
		System.out.println("文件傳輸完畢....");
		in.close();
		System.out.println(socket.isClosed());


結果,這裏會輸出true。也就是說在in.close,out.close()後,socket也就隨之關閉了。我思考下原因,可能是因爲socket本來就是用來傳輸數據的,而你把這個數據的輸入輸出流給關閉了,他就不具備傳輸數據的作用,自然再以打開狀態運行也是無效的。


   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章