socket的一些細節

在socket的編程時,一個發送一個接收,類似如下的代碼

發送:     outputStream = sock.getOutputStream()     outputStream.write( … )  

接收:   inputStream= sock.getInputStream()     inputStream.read( … )

看似很簡單,其實並不是那麼簡單,很多東不瞭解底層的東西,在寫代碼的時候,往往容易出問題。這一篇,來說說關於系統緩衝區的問題。

        發送與接收的細節是比較複雜的,首先說說發送方,當我們調用outputStream.write(data),其實並不沒有把數據發送出去,只是把數據放到數據發送緩衝區,然後系統從數據發送緩衝區取出數據,經網卡發出去,也就是說,發數據這個動作並不是有程序來完成而是操作系統來完成的。

        接着來說說接收方,首先是操作系統從網卡讀取網絡數據,然後存在一個叫做數據接收緩衝區,然後程序調用inputStream.read( ),從數據接收緩衝區中讀取數據。由此看來,每當我們打開一個socket連接,則系統會分配兩個緩衝區,數據發送緩衝區和數據接收緩衝區。

       說完數據緩衝區的存在,我們就說說數據的發送和接收的規則。

      數據的發送規則:1、保證所有數據都會被髮出去;2、保證數據的先來後到;3、不保證數據包的完整性;4、不保證馬上能把數據發出去

      數據的接收規則:1、自動接收:無論程序有沒有調用inputStream.read(),數據都會自動被OS接收並存儲在數據接收緩衝區中;2,inputStream.read()僅僅是從數據接收緩衝區中取走數據,如果數據額接收緩衝區滅有數據,則次方法就會發生阻塞,如果緩衝區中數據較少,則是有多少讀多少;3、InputStream.read() 重載了2個方法read(byte b[])、 read(byte b[], int off, int len)接收到的數據存到數組b裏,而len表示試圖讀取的字節數,off是從哪裏開始。由此可以知道,調用inputStream.read()取到的數據不保證完整性!

      爲了能使數據的完整性,我們制定新的協議。1、發送方,我們可以先發送數據的長度,再講數據發出去,byte[4]  + byte[N]  ( 變長編碼 );2、接收方,先讀取先四個字節得知數據的長度,然後再接收數據。

以下給出我自己改造後的接收和發送,算是拋磚引玉

發送方:

package my;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;

public class AdClientConnection
{
	private Socket socket;
	private InputStream input;
	private OutputStream out;
	private byte[] inputData = new byte[400];
	
    private String charSet = "UTF-8";
	

    /**
     * 連接
     * @param ip
     * @param port
     * @throws IOException
     */
	public void connetion(String ip , int port) throws IOException
	{
		socket = new Socket();
		socket.connect(new InetSocketAddress(ip, port));
		
		input = socket.getInputStream();
		out = socket.getOutputStream();
	}
	/**
	 * 關閉連接
	 * @throws IOException 
	 */
	public void close() throws IOException
	{
		if(socket != null)
		{
			socket.close();
			socket = null;
		}
	}
    /**
     * 完整讀取數據
     * @param data  讀取的數據
     * @param off   開始讀取的下標
     * @param N    一共要讀取的數據
     * @throws IOException 
     */
    public int readFully(byte[] data , int off , int N) throws IOException
    {
    	int count = 0;//已經讀取的數據
    	while(count < N)
    	{
    		int remain = N - count;
    		int numBytes = input.read(data, off + count, remain);
    		if(numBytes < 0)
    			return -1;
    		count += numBytes;
    	}
    	return N;   	
    }
    /**
     * 發送文字
     * @param str
     * @throws IOException 
     */
    public void sendString(String str) throws IOException
    {
    	ByteBuffer buf = ByteBuffer.allocate(4);
    	//先獲取字符串的大小
    	byte[] data = str.getBytes(this.charSet);
    	buf.putInt(data.length);
    	//先發送字符串的長度
    	out.write(buf.array(), 0, 4);
    	//再發送內容
    	out.write(data);   	
    }
    /**
     * 接收字符串
     * @return
     * @throws IOException 
     */
    public String recvString() throws IOException
    {
    	//首先接收字符串的長度
    	ByteBuffer buf = ByteBuffer.allocate(4);
    	int n = readFully(buf.array(), 0, 4);
    	int len = buf.getInt();
    	
    	readFully(inputData , 0 , len);
    	return new String(inputData , 0 , len , this.charSet);
    }

}

       接收方:

package my;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class MyServser
{

	public static void main(String[] args)
	{
		try
		{
			System.out.println("服務器已啓動,等待連接");
			ServerSocket serverSocket = new ServerSocket(2019);
			Socket socket= serverSocket.accept();
			AdServerConnection server = new AdServerConnection(socket);
			socket.setSoTimeout(3000);
			String str = server.recvString();
			str += server.recvString();
			System.out.println("收到的信息" + str);
			server.sendString("我已收到,你也好呀");
			server.close();
		} catch (IOException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

}

 

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