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