關於java socket 一些常見錯誤的解決辦法

現在大三自己寫socket大概也寫了一年多了,之前覺得自己對socket通信還蠻熟悉的但是最近在做一個項目的時候發現自己對在用socket的時候還是會犯一些常犯的錯誤。經過艱辛的debug終於做出了自己要的效果。在此爲了總結經驗教訓當做學習筆記,也爲了幫助更多初學者特此寫這篇博客,本人水平有限希望高手能夠糾正不對的地方。(這裏的socket主要是針對TCP協議)

socket通信的建立流程

首先我們先來總結一下TCP協議下建立socket通信的一個最基本的流程。

1.首先在服務器端我們通過ServerSocket類來得到一個綁定於特定端口號(通常大於1024)serverSocket的實例,該實例用於監聽客戶端的連接請求。

ServerSocket serverrSocket = new ServerSocket(portNum1);

2.服務器監聽客戶端的請求。accept()操作是一個阻塞操作,如果沒有客戶端請求連接則進程會一直停留在該操作上,所以通常接受到新的請求後我們會開啓新的子線程去處理客戶端的請求而主線程則繼續監聽新的客戶連接請求。accept返回的是一個Socket類的實例,我們主要通過這個實例與客戶程序打交道。

 Socket socket =serverSocket.accept();

到此是服務器建立連接所要做的工作接下去是客戶端的工作。

在服務器運行之後(注意一般要在服務器運行監聽客戶請求之後才運行客戶端)。客戶端要想服務器發起連接要做兩步工作

3.首先要新建一個socket的實例該socket是通信用的socket跟ServerSocket監聽請求的稍有區別

4.最後就是要通過socket.bind()操作去連接相應ip和端口下的服務器進程。具體怎麼使用百度上已經有很多例子了。或者可以在new操作生成實例的時候直接在參數中聲明

socket所需要連接的的IP地址和端口。

 以上就是socket通信的基本流程。

  接下來要說說如何用socket發送數據以及我在這裏邊所遇到的問題

第一、文件傳輸文件,接受收件出錯

    socket最原始的直接數據操作辦法是直接通過socket獲取輸入輸出流對象,通過輸入輸出流對象發送字節數據。任何形式的數據都可以通過此方法發送出去

     InputStream ip = socket.getInputStream();ip.write()發送字節數據

對於字符串我們可以 byte buf[] = string.getByte()獲得字節數據。

同樣的對於獲取數據的辦法我們通過OutputStream os =socket.getOutputStream(); byte []buf = new byte[1024];len=os.read(buf,0,offset);

offset是一次性接受數據的允許的最大長度也就是buf的長度,而len是實際讀到的數據長度這個很重要。

下面我舉個失敗的例子

public void recieveFile(String path,String name) throws IOException

{

 File f = new File(path);

  if(!f.exists())

  {

  f.mkdir();

  }

 InputStream dis = socket.getInputStream()

FileOutputStream fos = new FileOutputStream(new File(path+"/"+name));    

 byte [] buf = new byte[1024];

 int offset ;

  while((offset=dis.read(buf, 0, 1024))>0)

  {

  fos.write(buf, 0,1024);
  }
  fos.close();
}

  }

  fos.close();

}

在這個例子中我想做的是將socket接收到數據寫入到磁盤中

這裏我犯了一個錯誤  fos.write(buf, 0,1024);偏移量t我直接使用了緩衝區最大的長度1024而不是socket在本次操作中實際讀到的數據長度,可想而知當文件傳輸結束後得到文件的長度當然是錯的大於原來的文件長度,文件也是錯的,而實際這句話我應該這樣寫fos.write(buf, 0,offset);

同樣對於寫操作:

public  void sendFile(String path) throws IOException

{

byte []buf = new byte[1024];

File file = new File(path);

if(file.exists())

{

FileInputStream in = new FileInputStream(file);

int offset =0;

while((offset=in.read(buf,0,1024))>0)

{

dos.write(buf,0,offset);

}

dos.flush();//把最後的字節發送出去

in.close();//關閉文件讀入流。

}else{ 

System.out.println("文件不存在");

}

}

第二點對用socket傳輸多個文件最好的辦法是爲每一次文件傳輸建立一次socket用來重置socket的狀態否則會當年想用一個socket完成全部文件傳輸時,有可能會出現亂碼或者收不到消息的情況,或者進程一直佔用文件流不釋放。遇到文件和字符傳輸的情況最好將文件和字符分開處理,文件用一個socket通道字符用另外一個socket通道。雖然多次重新建立socket連接會有一定上的時間損失但是爲了文件不出現亂碼我覺得還是值得的。

第三、發送和接收不成功,亂碼,阻塞等問題的解決方法。

剛剛說到直接使用socket的輸入輸出流對數據進行操作可以發送各種類型的數據包括字符串在內。但是如果用

 InputStream ip = socket.getInputStream();

byte buf[] = string.getByte()

ip.write(buf)發送字節數據

OutputStream os =socket.getOutputStream(); 

        byte []buf = new byte[1024];

        len=os.read(buf,0,offset);

      String str = new String(buf);

這樣的辦法雖然可行但是如果連續快速地發送幾個不同的字符串就會出現問題。因爲我們知道socket的輸入流操作其實是到系統的網絡緩存處去讀取字節數據,而我們知道

由於各種因素的制約其實發送和接收很難做到同步很少出現發送端發送1024個字節,接受端恰好同時就能收到這1024個一樣的字節,事實上每次去緩衝區讀取數據的時候

這些數據有可能就是一個字符串留下的數據加上新發的字符串的數據的雜交,他分不清楚這些字節數據到底屬於哪個字符串的,所以當你將字節讀出的時候再用new

String(buf)的辦法還原字符串的時候就亂碼了。除非你控制發送端發送的速度,以至於使得接收端每次在緩衝區收到的數據都只是一個字符串的字節數據這樣不會亂碼。

所以我們比較常用字符串發送辦法最好是要有分隔符,而java提供的BufferedReader 和PrintWriter就有這樣的機制

該方法的使用方式如下

socket= new Socket(IP,port);

dos = new DataOutputStream(socket.getOutputStream());

pw = new PrintWriter(new OutputStreamWriter(dos,"utf-8"));


dis = new DataInputStream(socket.getInputStream());

br =new BufferedReader(new InputStreamReader(dis,"utf-8"));

而UTF-8呢則定義了字符串的編碼方式方式編碼中文的時候出現亂碼。

 此時接收端

str=br.readLine();這樣就可以接收到一行一行的字符了因爲該API通過“\r”或者"\n"分割或者“\n\r,”這裏有要注意的地方如果你的字符串最後沒有分隔符br.readLine()將會一直阻塞這是很多人會犯的錯誤。


具體參考英文說明

而發送端這頭呢我們要做的是pw.println(str);

這樣在你發送出去的字符就會自動添加換行符

但是當我寫到了這裏客戶端還是沒有收到消息,這是爲什麼呢?

其實這時候是因爲你的數據已經發送出去了但是給你添加的換行符還在緩衝區裏面(坑爹吧)!

這時候你要想讓接收端接受數據停止阻塞還得調用flush。所以正確的做法是

 pw.println(msg);

pw.flush();

將緩衝區的數據強行寫出。

當讓如果你也可以講PrintWriter設置成自動flush();

在聲明的時候我們可以這樣做;

dos = new DataOutputStream(socket.getOutputStream());

pw = new PrintWriter(new OutputStreamWriter(dos,"utf-8"),true);

最後附上pw.println()的說明。

  {Prints the string representation of the strings followed by a newline. Flushes this writer if the autoFlush flag is set totrue.

The string is converted to an array of bytes using the encoding chosen during the construction of this writer. The bytes are then written to the target withwrite(int).Finally, this writer is flushed if the autoFlush flag is set totrue. }

最後附上我自己的代碼:

package com.example.diarydemo;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;


public class NetWorkUtil {
	private Socket socket ;
	private DataOutputStream dos;
	private DataInputStream dis;
	private PrintWriter pw;
	private BufferedReader br;

	public  NetWorkUtil(String IP,int port) throws UnknownHostException, IOException
	{

			socket= new Socket(IP,port);
			dos = new DataOutputStream(socket.getOutputStream());
			 pw = new PrintWriter(new OutputStreamWriter(dos,"utf-8"),true);
				
			 dis = new DataInputStream(socket.getInputStream());
			 br =new BufferedReader(new InputStreamReader(dis,"utf-8"));
			 
	}
	
	public void recieveFile(String path,String name) throws IOException
	{
		  File f = new File(path);
		   if(!f.exists())
		   {
			   f.mkdirs();
		   }
		 FileOutputStream fos = new FileOutputStream(new File(path+"/"+name));    
		  byte [] buf = new byte[1024];
		  int offset=0;
		   while((offset=dis.read(buf, 0, 1024))>0)
		   {
			   fos.write(buf, 0, offset);
				   
		   }
		   fos.close();
	 }
	  public String recieveMSG() throws IOException
	  {
		  String msg =null;
	     msg = br.readLine();
		    return msg;
	  }
	public void  sendMSG(String msg) throws IOException
	{
	    
	    pw.println(msg);
	    pw.flush();
	}
	public  void sendFile(String path) throws IOException
	 {
		 byte []buf = new byte[1024];
		 File file = new File(path);
		 if(file.exists())
		 {
		 FileInputStream in = new FileInputStream(file);
		 int offset =0;
		 while((offset=in.read(buf,0,1024))>0)
		 {
			 dos.write(buf,0,offset);
	
		 }
		 dos.flush();
		 dos.close();
		in.close();//關閉文件讀入流。
	 }else{
		 
		 System.out.println("文件不存在");
	 }
	 }
	public void closeSocket() throws IOException
	{
		socket.close();
	
	}
	
}


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