TCP通讯过程中,由于网络原因或者其他原因,经常出现粘包和半包现象。所以在具体编程中需要考虑。
下边的 java 代码是用 NIO 实现的一个Server端,消息的通讯格式为:
4字节int类型 [包头] + 包体.
包头描述出包体的长度。
package com.sof.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Reactor implements Runnable
{
private static Logger logger = LoggerFactory.getLogger(Reactor.class);
final Selector selector;
final ServerSocketChannel serverSocket;
public Reactor(String ip, int port) throws IOException
{
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
serverSocket.socket().bind(new InetSocketAddress(port));
serverSocket.configureBlocking(false);
SelectionKey sk = serverSocket.register(selector, SelectionKey.OP_ACCEPT);
sk.attach(new Acceptor());
}
public void run()
{
try
{
while (!Thread.interrupted())
{
logger.debug("selector is waitting event....");
selector.select();
Set keys = selector.selectedKeys();
if (keys.size() == 0)
{
logger.debug("nothing happened");
continue;
}
for (SelectionKey key : keys)
{
if (key.isAcceptable())
{
logger.debug("Acceptable event happened");
}
else if (key.isReadable())
{
logger.debug("Readable event happened");
}
else if (key.isWritable())
{
logger.debug("Writeable event happened");
}
else
{
logger.debug("others event happened");
}
dispatch((SelectionKey) key);
}
keys.clear();
}
}
catch (IOException ex)
{
logger.error(ex.getMessage());
ex.printStackTrace();
}
}
void dispatch(SelectionKey k)
{
Runnable r = (Runnable) (k.attachment());
if (r != null)
{
r.run();
}
}
public class Acceptor implements Runnable
{
public synchronized void run()
{
try
{
SocketChannel c = serverSocket.accept();
logger.info("got a new connection from: " + c.socket().toString());
if (c != null)
{
new Handler(selector, c);
}
}
catch (IOException ex)
{
logger.error(ex.getMessage());
ex.printStackTrace();
}
}
}
}
package com.sof.nio;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.sof.bas.Bytes2util;
import com.sof.bas.Util2Bytes;
final public class Handler implements Runnable
{
private static Logger logger = LoggerFactory.getLogger(Handler.class);
final SocketChannel socket;
final SelectionKey sk;
static final int MESSAGE_LENGTH_HEAD = 4;
byte[] head = new byte[4];
int bodylen = -1;
Handler(Selector selector, SocketChannel socket) throws IOException
{
this.socket = socket;
socket.configureBlocking(false);
sk = socket.register(selector, 0);
sk.attach(this);
sk.interestOps(SelectionKey.OP_READ);
selector.wakeup();
}
public void run()
{
try
{
read();
}
catch (IOException ex)
{
try
{
socket.close();
}
catch (IOException e)
{
e.printStackTrace();
}
logger.info("got a disconnect from " + socket.socket().toString());
sk.cancel();
}
}
public synchronized void read() throws IOException
{
ByteBuffer input = ByteBuffer.allocate(1024);
socket.read(input);
input.flip();
//读取数据的原则: 要么读取一个完整的包头,要么读取一个完整包体。不满足这两种情况,不对ByteBuffer进行任何的get操作
//但是要注意可能发生上次读取了一个完整的包头,下次读才读取一个完整包体情况。
//所以包头部分必须用类的成员变量进行暂时的存储,当完整读取包头和包体后,在给业务处理部分。
logger.debug("1: remain=" + input.remaining() + " bodylen=" + bodylen);
while(input.remaining() > 0)
{
if (bodylen < 0) //还没有生成完整的包头部分, 该变量初始值为-1,并且在拼凑一个完整的消息包以后,再将该值设置为-1
{
if ( input.remaining() >= MESSAGE_LENGTH_HEAD) //ByteBuffer缓冲区的字节数够拼凑一个包头
{
input.get(head, 0, 4);
bodylen = Util2Bytes.bytes2bigint(head);
logger.debug("2: remain=" + input.remaining() + " bodylen=" + bodylen);
}
else//ByteBuffer缓冲区的字节数不够拼凑一个包头,什么操作都不做,退出这次处理,继续等待
{
logger.debug("3: remain=" + input.remaining() + " bodylen=" + bodylen);
break;
}
}
else if(bodylen > 0) //包头部分已经完整生成.
{
if (input.remaining() >= bodylen) //缓冲区的内容够一个包体部分
{
byte[] body = new byte[bodylen];
input.get(body, 0, bodylen);
byte[] headandbody = new byte[MESSAGE_LENGTH_HEAD + bodylen];
System.arraycopy(head, 0, headandbody, 0, head.length);
System.arraycopy(body,0, headandbody, head.length, body.length);
bodylen = -1;
logger.debug("4: remain=" + input.remaining() + " bodylen=" + bodylen);
Bytes2util.outputHex(headandbody, 16);
}
else ///缓冲区的内容不够一个包体部分,继续等待,跳出循环等待下次再出发该函数
{
System.out.println("5: remain=" + input.remaining() + " bodylen=" + bodylen);
break;
}
}
else if(bodylen == 0) //没有包体部分,仅仅有包头的情况
{
byte[] headandbody = new byte[MESSAGE_LENGTH_HEAD + bodylen];
System.arraycopy(head, 0, headandbody, 0, head.length);
Bytes2util.outputHex(headandbody, 16);
bodylen = -1;
}
}
sk.interestOps(SelectionKey.OP_READ);
}
}
package com.sof.bas;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Bytes2util {
private static Logger logger = LoggerFactory.getLogger(Bytes2util.class);
public static byte[] biginttobytes(int value) {
byte[] stream = new byte[4];
for (int i = 0; i < 4; i++) {
stream[i] = (byte) ((value & (0xFF << (4 - i - 1) * 8)) >> ((4 - i - 1) * 8));
}
outputHex(stream, 16);
return stream;
}
public static byte[] bigshorttobytes(short value) {
byte stream[] = new byte[2];
for (int i = 0; i < 2; i++) {
stream[i] = (byte) ((value & (0xFF << (2 - i - 1) * 8)) >> ((2 - i - 1) * 8));
}
outputHex(stream, 16);
return stream;
}
public static byte[] smallinttobytes(int value) {
byte stream[] = new byte[4];
for (int i = 0; i < 4; i++) {
stream[4 - i - 1] = (byte) ((value & (0xFF << (4 - i - 1) * 8)) >> ((4 - i - 1) * 8));
}
outputHex(stream, 16);
return stream;
}
public static byte[] smallshorttobytes(short value) {
byte stream[] = new byte[2];
for (int i = 0; i < 2; i++) {
stream[2 - i - 1] = (byte) ((value & (0xFF << (2 - i - 1) * 8)) >> ((2 - i - 1) * 8));
}
outputHex(stream, 16);
return stream;
}
public static void outputHex(byte[] stream, int number) {
String content = "stream display, length=" + stream.length + "\n";
for (int i = 0; i < stream.length; i++) {
if (i / number != 0 && i % number == 0) {
content += "\n";
}
String tempstr = Integer.toHexString(stream[i] & 0xFF)
.toUpperCase();
if (tempstr.length() == 1)
tempstr = "0" + tempstr;
content += tempstr + " ";
}
logger.debug(content);
}
}
package com.sof.bas;
public class Util2Bytes
{
public static int bytes2smallint(byte stream[])
{
int value = 0;
int temp = 0;
for (int i = 3; i >= 0; i--)
{
if ((stream[i]) >= 0)
{
temp = stream[i];
}
else
{
temp = stream[i] + 256;
}
temp <<= (i * 8);
value += temp;
}
return value;
}
public static short bytes2smallshort(byte stream[])
{
short value = 0;
int temp = 0;
for (int i = 1; i >= 0; i--)
{
if ((stream[i]) >= 0)
{
temp = stream[i];
}
else
{
temp = stream[i] + 256;
}
temp <<= (i * 8);
value += temp;
}
return value;
}
public static int bytes2bigint(byte stream[])
{
int value = 0;
int temp = 0;
for (int i = 0; i < 4; i++)
{
if ((stream[i]) >= 0)
{
temp = stream[i];
}
else
{
temp = stream[i] + 256;
}
temp <<= ((4 - i - 1) * 8);
value += temp;
}
return value;
}
public static short bytes2bigshort(byte stream[])
{
short value = 0;
int temp = 0;
for (int i = 0; i < 2; i++)
{
if ((stream[i]) >= 0)
{
temp = stream[i];
}
else
{
temp = stream[i] + 256;
}
temp <<= ((2 - i - 1) * 8);
value += temp;
}
return value;
}
}