在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();
}
}
}