一个简单的自定义通信协议(socket)
2016-06-02 17:59
465 查看
转自:http://vtrtbb.javaeye.com/blog/849336
这是转自javaeye的一篇文章,作者是vtrtbb。
按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。
一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。
先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体
包类型 byte 型
包长度 int 型
消息体 byte[]
包总长度为 1 + 4 + 消息体.getBytes().length
发包方法如下:
[java] view
plain copy
private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {
byte[] bytes= msg.getBytes();
int totalLen = 1 + 4 + bytes.length;
out.writeByte(1);
out.writeInt(totalLen);
out.write(bytes);
out.flush();
}
客户端发送消息类为:
[java] view
plain copy
import <a href="http://lib.csdn.net/base/17" class='replace_word' title="Java EE知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java</a>.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class MsgClient {
private DataOutputStream outs;
public static void main(String[] args) {
try {
MsgClient client = new MsgClient();
client.connServer("127.0.0.1", 9292);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {
byte[] bytes= msg.getBytes();
int totalLen = 1 + 4 + bytes.length;
out.writeByte(1);
out.writeInt(totalLen);
out.write(bytes);
out.flush();
}
public void connServer(String ip,int port) throws UnknownHostException, IOException {
Socket client = new Socket(ip,port);
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
outs = new DataOutputStream(out);
while(true) {
Scanner scaner = new Scanner(System.in);
sendTextMsg(outs, "测试消");
}
}
服务端接收类为:
[java] view
plain copy
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MsgServer {
public static void main(String[] args) {
try {
MsgServer server = new MsgServer();
server.setUpServer(9090);
} catch (IOException e) {
e.printStackTrace();
}
}
public void setUpServer(int port) throws IOException {
ServerSocket server = new ServerSocket(port);
while(true) {
Socket client = server.accept();
System.out.println("客户端IP:"+client.getRemoteSocketAddress());
processMesage(client);
}
}
private void processMesage(Socket client) throws IOException {
InputStream ins = client.getInputStream();
DataInputStream dins = new DataInputStream(ins);
//服务端解包过程
while(true) {
int totalLen = dins.readInt();
byte flag = dins.readByte();
System.out.println("接收消息类型"+flag);
byte[] data = new byte[totalLen - 4 - 1];
dins.readFully(data);
String msg = new String(data);
System.out.println("发来的内容是:"+msg);
}
}
}
这样就基本完成了,但实际还有好多问题,比如说服务端用如何用多线程服务来完成客户端的请求已提高效率,如果是NIO方式怎么来实现?多个消息类型时候怎么抽象?这些都没有考虑
另外有两个开源的框架不错,一个是apache mina 还有个是netty ,有机会试试。
另一篇文章中叙述:
------------------
TCP Socket协议定义
------------------
本文从这里开始,主要介绍TCP的socket编程。
新手们(例如当初的我),第一次写socket,总是以为在发送方压入一个"Helloworld",接收方收到了这个字符串,就“精通”了Socket编程了。而实际上,这种编程根本不可能用在现实项目,因为:
1. socket在传输过程中,helloworld有可能被拆分了,分段到达客户端),例如 hello + world,一个分段就是一个包(Package),这个就是分包问题。
2. socket在传输过成功,不同时间发送的数据包有可能被合并,同时到达了客户端,这个就是黏包问题。例如发送方发送了hello+world,而接收方可能一次就接受了helloworld.
3. socket会自动在每个包后面补n个 0x0 byte,分割包。具体怎么去补,这个我就没有深入了解。
4. 不同的数据类型转化为byte的长度是不同的,例如int转为byte是4位(int32),这样我们在制作socket协议的时候要特别小心了。具体可以使用以下代码去测试:
代码
public void test()
{
int myInt = 1;
byte[] bytes = new byte[1024];
BinaryWriter writer = new BinaryWriter(new MemoryStream(bytes));
writer.Write(myInt);
writer.Write("j");
writer.Close();
}
尽管socket环境如此恶劣,但是TCP的链接也至少保证了:
包发送顺序在传输过程中是不会改变的,例如发送方发送 H E L L,那么接收方一定也是顺序收到H E L L,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。
如果发送方发送的是helloworld, 传输过程中分割成为hello+world,那么TCP保证了在hello与world之间没有其他的byte。但是不能保证helloworld和下一个命令之间没有其他的byte。
因此,如果我们要使用socket编程,就一定要编写自己的协议。目前业界主要采取的协议定义方式是:包头+包体长度+包体。具体如下:
1. 一般包头使用一个int定义,例如int = 173173173;作用是区分每一个有效的数据包,因此我们的服务器可以通过这个int去切割、合并包,组装出完整的传输协议。有人使用回车字符去分割包体,例如常见的SMTP/POP协议,这种做法在特定的协议是没有问题的,可是如果我们传输的信息内容自带了回车字符串,那么就糟糕了。所以在设计协议的时候要特别小心。
2. 包体长度使用一个int定义,这个长度表示包体所占的比特流长度,用于服务器正确读取并分割出包。
3. 包体就是自定义的一些协议内容,例如是对像序列化的内容(现有的系统已经很常见了,使用对象序列化、反序列化能够极大简化开发流程,等版本稳定后再转入手工压入byte操作)。
一个实际编写的例子:比如我要传输2个整型 int = 1, int = 2,那么实际传输的数据包如下:
173173173 8 1 2
|------包头------|----包体长度----|--------包体--------|
这个数据包就是4个整型,总长度 = 4*4 = 16。
这是转自javaeye的一篇文章,作者是vtrtbb。
按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。
一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。
先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体
包类型 byte 型
包长度 int 型
消息体 byte[]
包总长度为 1 + 4 + 消息体.getBytes().length
发包方法如下:
[java] view
plain copy
private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {
byte[] bytes= msg.getBytes();
int totalLen = 1 + 4 + bytes.length;
out.writeByte(1);
out.writeInt(totalLen);
out.write(bytes);
out.flush();
}
客户端发送消息类为:
[java] view
plain copy
import <a href="http://lib.csdn.net/base/17" class='replace_word' title="Java EE知识库" target='_blank' style='color:#df3434; font-weight:bold;'>Java</a>.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class MsgClient {
private DataOutputStream outs;
public static void main(String[] args) {
try {
MsgClient client = new MsgClient();
client.connServer("127.0.0.1", 9292);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {
byte[] bytes= msg.getBytes();
int totalLen = 1 + 4 + bytes.length;
out.writeByte(1);
out.writeInt(totalLen);
out.write(bytes);
out.flush();
}
public void connServer(String ip,int port) throws UnknownHostException, IOException {
Socket client = new Socket(ip,port);
InputStream in = client.getInputStream();
OutputStream out = client.getOutputStream();
outs = new DataOutputStream(out);
while(true) {
Scanner scaner = new Scanner(System.in);
sendTextMsg(outs, "测试消");
}
}
服务端接收类为:
[java] view
plain copy
import java.io.DataInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class MsgServer {
public static void main(String[] args) {
try {
MsgServer server = new MsgServer();
server.setUpServer(9090);
} catch (IOException e) {
e.printStackTrace();
}
}
public void setUpServer(int port) throws IOException {
ServerSocket server = new ServerSocket(port);
while(true) {
Socket client = server.accept();
System.out.println("客户端IP:"+client.getRemoteSocketAddress());
processMesage(client);
}
}
private void processMesage(Socket client) throws IOException {
InputStream ins = client.getInputStream();
DataInputStream dins = new DataInputStream(ins);
//服务端解包过程
while(true) {
int totalLen = dins.readInt();
byte flag = dins.readByte();
System.out.println("接收消息类型"+flag);
byte[] data = new byte[totalLen - 4 - 1];
dins.readFully(data);
String msg = new String(data);
System.out.println("发来的内容是:"+msg);
}
}
}
这样就基本完成了,但实际还有好多问题,比如说服务端用如何用多线程服务来完成客户端的请求已提高效率,如果是NIO方式怎么来实现?多个消息类型时候怎么抽象?这些都没有考虑
另外有两个开源的框架不错,一个是apache mina 还有个是netty ,有机会试试。
另一篇文章中叙述:
------------------
TCP Socket协议定义
------------------
本文从这里开始,主要介绍TCP的socket编程。
新手们(例如当初的我),第一次写socket,总是以为在发送方压入一个"Helloworld",接收方收到了这个字符串,就“精通”了Socket编程了。而实际上,这种编程根本不可能用在现实项目,因为:
1. socket在传输过程中,helloworld有可能被拆分了,分段到达客户端),例如 hello + world,一个分段就是一个包(Package),这个就是分包问题。
2. socket在传输过成功,不同时间发送的数据包有可能被合并,同时到达了客户端,这个就是黏包问题。例如发送方发送了hello+world,而接收方可能一次就接受了helloworld.
3. socket会自动在每个包后面补n个 0x0 byte,分割包。具体怎么去补,这个我就没有深入了解。
4. 不同的数据类型转化为byte的长度是不同的,例如int转为byte是4位(int32),这样我们在制作socket协议的时候要特别小心了。具体可以使用以下代码去测试:
代码
public void test()
{
int myInt = 1;
byte[] bytes = new byte[1024];
BinaryWriter writer = new BinaryWriter(new MemoryStream(bytes));
writer.Write(myInt);
writer.Write("j");
writer.Close();
}
尽管socket环境如此恶劣,但是TCP的链接也至少保证了:
包发送顺序在传输过程中是不会改变的,例如发送方发送 H E L L,那么接收方一定也是顺序收到H E L L,这个是TCP协议承诺的,因此这点成为我们解决分包、黏包问题的关键。
如果发送方发送的是helloworld, 传输过程中分割成为hello+world,那么TCP保证了在hello与world之间没有其他的byte。但是不能保证helloworld和下一个命令之间没有其他的byte。
因此,如果我们要使用socket编程,就一定要编写自己的协议。目前业界主要采取的协议定义方式是:包头+包体长度+包体。具体如下:
1. 一般包头使用一个int定义,例如int = 173173173;作用是区分每一个有效的数据包,因此我们的服务器可以通过这个int去切割、合并包,组装出完整的传输协议。有人使用回车字符去分割包体,例如常见的SMTP/POP协议,这种做法在特定的协议是没有问题的,可是如果我们传输的信息内容自带了回车字符串,那么就糟糕了。所以在设计协议的时候要特别小心。
2. 包体长度使用一个int定义,这个长度表示包体所占的比特流长度,用于服务器正确读取并分割出包。
3. 包体就是自定义的一些协议内容,例如是对像序列化的内容(现有的系统已经很常见了,使用对象序列化、反序列化能够极大简化开发流程,等版本稳定后再转入手工压入byte操作)。
一个实际编写的例子:比如我要传输2个整型 int = 1, int = 2,那么实际传输的数据包如下:
173173173 8 1 2
|------包头------|----包体长度----|--------包体--------|
这个数据包就是4个整型,总长度 = 4*4 = 16。
相关文章推荐
- 汉字转换为拼音
- 浅谈矩阵分解在推荐系统中的应用
- Bitmap图片处理的效率----BitmapData
- HDU1006
- Scrapy集成selenium+PhantomJS+代理IP 解析js动态内容
- 2016 Android面试题(一)
- TreeList的实现方法
- iOS RunLoop
- request 和session
- Android Studio下Vim的使用
- LeetCode:Find the Duplicate Number
- 常见浏览器兼容问题与解决
- 6.2
- 项目笔记0001
- 第二阶段工作总结07
- .Net中使用com组件后发生System.ArithmeticException异常的解决办法(Message=算术运算中发生溢出或下溢。)
- 阅读下面程序,请回答如下问题:
- Linux常用命令--more
- 键盘记录器
- 各种浏览器内核的比较