精华内容
下载资源
问答
  • 要做什么?我们知道,很多WEB服务器,例如Apache HTTPD,Nginx等都提供类似上面图示的方式进行工作:Server负责Worker的创建,销毁;...要通信,就要约定协议!我们知道计算机发送,接受的都是字节数据,如果...

    要做什么?

    b8de269fdc3950e620aca8656e97bd9e.png

    我们知道,很多WEB服务器,例如Apache HTTPD,Nginx等都提供类似上面图示的方式进行工作:

    Server负责Worker的创建,销毁;

    Woker负责具体与客户端的通信,处理请求;

    那么,我们接下来要做的就是一个简单的例子,实现客户端和服务端的交互,例如发送

    文本消息,客户端上传文件到服务器,服务器提供下载文件功能。

    要通信,就要约定协议!

    我们知道计算机发送,接受的都是字节数据,如果A“胡乱”的给B发数据,B能知道是

    什么意思吗?很显然,A应该清楚的告诉B如何接受数据,接受多大的数据,接受完毕后如何处理,数据都是些什么意思,而这些就是协议~

    那么下面,就来约定协议:

    sendMsg charset=gbk 世界,你好

    sendFile charset=gbk JAVA并发编程实战.pdf

    downloadFile charset=utf-8 JAVA编程思想.pdf

    上面的格式,说明了,client可以给server发送消息、文件,还可以向server索要文

    件。对于发送文本消息,很显然,接受方需要知道用什么编码将字节流进行转换;类似的,上传文件/下载文件,需要知道文件名称编码。对于文件上传下载,我们都采用字节流处理,并不涉及到转换成字符流,所以对于文件可以不用提供文件内容编码了。至于上传下载的路径,我们可以配置即可。另外,需要注意的是,不论对于发送文本消息,还是文件,都需要结束,所以需要发送消息的长度,文件的长度。具体来说,我们可以用1个BYTE来代表sendMsg/sendFile/downloadFile;用1个BYTE来代表charset;用1个LONG来代表长度;其他信息就是字节流了。

    从类的角度出发进行设计

    要提供SOCKET的封装类

    说到底,是SOCKET之间的通信,如果不对SOCKET进行一次封装,那么就会有很多代码

    反复写,而且封装之后,将隐藏流的细节,有利于外部调用。要清楚的是,SOCKET的通信,最终也是反映到IO流的操作上的,那么多JAVA IO流,选择什么流呢?我们应该从协议的角度出发,我们需要读写的协议数据格式是什么,哪些IO流提供的方法多些,方便我们操作呢?DataInputStream/DataOutputStream,这种数据流,提供了众多数据格式的write/read操作。

    22c53115c0c04b3b39a8eae7954e083f.png

    注意到,由于我们设计到3种命令格式,只需要一个BYTE来代表COMMAND TYPE,因此我们

    需要readByte/writeByte方法;由于我们需要消息/文件的长度信息,因此我们需要readLong/writeLong方法;既然涉及到流,必然需要关闭,我们可以给SocketWrapper打上Closeable标签,提供close方法(实际上,InputStream/OutputStream/Reader/Writer都是打上了Closeable标签的);另外,提供了writeString方法,会将String信息以CharsetByte指定的编码格式进行写入;writeFile方法则是针对文件。我们可以先来看看writeFile的实现:

    d9ec9846d1ca83850ed9bb6ecd16acef.png

    2ed13433f828e344c0be70fe353db1b8.png

    这里需要注意的是:

    根据文件大小来选择一次性字节发送,还是分批发送;

    要知道如果一次性将非常大的文件字节流发送到对方,会造成对方内存区域紧张,而

    分批字节发送会很好的缓解压力!

    提供和协议相关的信息类

    字符集信息类:

    d1807040f47e5b664774ee1f91ce80c3.png

    对于服务器,需要知道根据编码BYTE找到字符集,对于客户端,需要根据字符集找到

    对应编码的BYTE。

    fba3cb464d808e270275602126753b36.png

    158f2aa080e0e2e9729255d6631572e5.png

    那么在内存中,应该存在初始化好的字符集!

    56fa65ce391845cafb24dfe17ff2ab6c.png

    命令信息类:

    05d5d30663bffb55c491e4c92e24be68.png

    19ca6d79fb7f079ae509614438addd80.png

    我们可以清楚的看到,通过ENUM,我们轻松完成了字符串命令与命令编码的映射关系!

    更加重要的是见名知意!

    我们来看看getSendableClass()是干嘛的呢?

    ea3f63276a1b36462e1376a038b43d1e.png

    很显然,如果sendMsg,那么是一类处理手段,如果是sendFile将是另一类处理手段。

    同样的,在内存中,我们应该初始化好这类信息:

    9ee14e6b7b823a779f0b3379f13bdf6a.png

    提供客户端处理类

    对于sendMsg,sendFile,downloadFile而言,它们是可以抽象出来的!

    2a803a1bf5cac71ebe78a6da4d9de0f8.png

    56be19208f882e2916d8d0c56047c732.png

    我们可以来具体看一看SendFileable这个类:

    b600c4641148ecfb8d6db90751c805e5.png

    先来看看getCommandType():

    84622872052c3b61eec8b83039e9ef92.png

    其实,就是为了客户端向服务端发送命令类型提供支持!

    String[] token是什么呢?

    对于sendMsg charset=gbk 世界,你好  而言,token就是{“sendMsg” , “gbk” ,

    “世界,你好”}。也就是说,TOKEN其实就是一组逻辑单元!

    看看具体的doTask()是怎么做的:

    f830e9e3a7a0eb41c10c52c7c9bf574a.png

    第一步,发送命令类型;

    第二步,发送文件名称编码以及文件名称对应编码的字节流以及长度

    第三步,等待服务端响应,如果服务端已经存在了此文件,则拒绝;否则开始writeFile

    感悟:

    有些时候,我们需要等待;而不是一股脑的把东西都发送过去,也许是不必要的!

    让客户端运转起来!---》ClientMain

    0391ab3cf75a4b2d25750ad52dbc556f.png

    循环起来:

    d0b63dae668cc0a5f34a1b2186ef2397.png

    客户端在CMD下发送的命令,首先通过LineProcesser预处理下,然后形成TOKEN,根据

    TOKEN找到对应处理类,利用反射实例化处理类,调用doTask方法即可!

    提供服务端处理类:

    6239b80ed4e9d0cbec47401b0365196e.png

    Worker是具体负责和客户端通信的线程,应该持有SocketWrapper的引用,同时通过ID来

    进行Worker的标示,下面我们来看看run()是怎么处理的:

    f98bd24452dcd6febd025461c668e92a.png

    processMsg/processSendFile/processDownloadFile具体实现,很简单了,大家可以

    自己动手去实现!

    ServerMain:

    5ca18e6b19e744b774d1f441e1428e62.png

    通过代码,我们清楚的看到了,每accept一个client socket,服务端就new一个

    Worker进行处理!

    展开全文
  • 一个简单的自定义通信协议(socket) 转自:http://vtrtbb.javaeye.com/blog/849336 这是转自javaeye的一篇文章,作者是vtrtbb。 按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会...

    一个简单的自定义通信协议(socket)

    转自:http://vtrtbb.javaeye.com/blog/849336

    这是转自javaeye的一篇文章,作者是vtrtbb。

    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。

     

     

    一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。

     

    先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体

     

    包类型 byte 型

    包长度 int 型

    消息体 byte[]

     

    包总长度为 1 + 4 +  消息体.getBytes().length

     

    发包方法如下:

    1. private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {    
    2.         byte[] bytes= msg.getBytes();    
    3.         int totalLen = 1 + 4 + bytes.length;    
    4.                                 out.writeByte(1);    
    5.         out.writeInt(totalLen);    
    6.         out.write(bytes);    
    7.         out.flush();    
    8.     }    

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

     

     

    客户端发送消息类为:

    1. import java.io.DataOutputStream;    
    2. import java.io.IOException;    
    3. import java.io.InputStream;    
    4. import java.io.OutputStream;    
    5. import java.net.Socket;    
    6. import java.net.UnknownHostException;    
    7. import java.util.Scanner;    
    8.     
    9. public class MsgClient {    
    10.     
    11.     private DataOutputStream outs;    
    12.         
    13.     public static void main(String[] args) {    
    14.         try {    
    15.             MsgClient client = new MsgClient();    
    16.             client.connServer("127.0.0.1"9292);               
    17.         } catch (UnknownHostException e) {    
    18.             e.printStackTrace();    
    19.         } catch (IOException e) {    
    20.             e.printStackTrace();    
    21.         }    
    22.     }    
    23.         
    24.         
    25.     private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {    
    26.         byte[] bytes= msg.getBytes();    
    27.         int totalLen = 1 + 4 + bytes.length;    
    28.         out.writeByte(1);    
    29.         out.writeInt(totalLen);    
    30.         out.write(bytes);    
    31.         out.flush();    
    32.     }       
    33.         
    34.     public void connServer(String ip,int port) throws UnknownHostException, IOException {    
    35.         Socket client = new Socket(ip,port);    
    36.         InputStream in = client.getInputStream();    
    37.         OutputStream out = client.getOutputStream();    
    38.         outs = new DataOutputStream(out);    
    39.         while(true) {    
    40.             Scanner scaner = new Scanner(System.in);    
    41.             sendTextMsg(outs, "测试消");    
    42.         }           
    43.     }    

    import java.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, "测试消"); } }  

     

     

    服务端接收类为:

    1. import java.io.DataInputStream;    
    2. import java.io.FileOutputStream;    
    3. import java.io.IOException;    
    4. import java.io.InputStream;    
    5. import java.net.ServerSocket;    
    6. import java.net.Socket;    
    7.     
    8. public class MsgServer {    
    9.     public static void main(String[] args) {            
    10.         try {    
    11.             MsgServer server = new MsgServer();    
    12.             server.setUpServer(9090);    
    13.         } catch (IOException e) {    
    14.             e.printStackTrace();    
    15.         }    
    16.     }    
    17.         
    18.     public void setUpServer(int port) throws IOException {    
    19.         ServerSocket server = new ServerSocket(port);    
    20.         while(true) {    
    21.             Socket client = server.accept();    
    22.             System.out.println("客户端IP:"+client.getRemoteSocketAddress());    
    23.             processMesage(client);    
    24.         }    
    25.     }    
    26.         
    27.     private void processMesage(Socket client) throws IOException {    
    28.         InputStream ins = client.getInputStream();          
    29.         DataInputStream dins = new DataInputStream(ins);    
    30.         //服务端解包过程     
    31.         while(true) {    
    32.             int totalLen = dins.readInt();    
    33.             byte flag = dins.readByte();    
    34.             System.out.println("接收消息类型"+flag);    
    35.                 
    36.             byte[] data = new byte[totalLen - 4 - 1];    
    37.             dins.readFully(data);    
    38.             String msg = new String(data);    
    39.             System.out.println("发来的内容是:"+msg);      
    40.         }    
    41.     }    
    42. }    

    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。

     

     

    展开全文
  • 要做什么?我们知道,很多WEB服务器,例如Apache HTTPD,Nginx等都提供类似上面图示的方式进行工作:Server负责Worker的创建,销毁;...要通信,就要约定协议!我们知道计算机发送,接受的都是字节数据,如果...

    要做什么?

    15aaffb1ee496ae74b60b09338257c20.png

    我们知道,很多WEB服务器,例如Apache HTTPD,Nginx等都提供类似上面图示的方式进行工作:

    Server负责Worker的创建,销毁;

    Woker负责具体与客户端的通信,处理请求;

    那么,我们接下来要做的就是一个简单的例子,实现客户端和服务端的交互,例如发送

    文本消息,客户端上传文件到服务器,服务器提供下载文件功能。

    要通信,就要约定协议!

    我们知道计算机发送,接受的都是字节数据,如果A“胡乱”的给B发数据,B能知道是

    什么意思吗?很显然,A应该清楚的告诉B如何接受数据,接受多大的数据,接受完毕后如何处理,数据都是些什么意思,而这些就是协议~

    那么下面,就来约定协议:

    sendMsg charset=gbk 世界,你好

    sendFile charset=gbk JAVA并发编程实战.pdf

    downloadFile charset=utf-8 JAVA编程思想.pdf

    上面的格式,说明了,client可以给server发送消息、文件,还可以向server索要文

    件。对于发送文本消息,很显然,接受方需要知道用什么编码将字节流进行转换;类似的,上传文件/下载文件,需要知道文件名称编码。对于文件上传下载,我们都采用字节流处理,并不涉及到转换成字符流,所以对于文件可以不用提供文件内容编码了。至于上传下载的路径,我们可以配置即可。另外,需要注意的是,不论对于发送文本消息,还是文件,都需要结束,所以需要发送消息的长度,文件的长度。具体来说,我们可以用1个BYTE来代表sendMsg/sendFile/downloadFile;用1个BYTE来代表charset;用1个LONG来代表长度;其他信息就是字节流了。

    从类的角度出发进行设计

    要提供SOCKET的封装类

    说到底,是SOCKET之间的通信,如果不对SOCKET进行一次封装,那么就会有很多代码

    反复写,而且封装之后,将隐藏流的细节,有利于外部调用。要清楚的是,SOCKET的通信,最终也是反映到IO流的操作上的,那么多JAVA IO流,选择什么流呢?我们应该从协议的角度出发,我们需要读写的协议数据格式是什么,哪些IO流提供的方法多些,方便我们操作呢?DataInputStream/DataOutputStream,这种数据流,提供了众多数据格式的write/read操作。

    4f5a2b759b8171629a470879c8e01013.png

    注意到,由于我们设计到3种命令格式,只需要一个BYTE来代表COMMAND TYPE,因此我们

    需要readByte/writeByte方法;由于我们需要消息/文件的长度信息,因此我们需要readLong/writeLong方法;既然涉及到流,必然需要关闭,我们可以给SocketWrapper打上Closeable标签,提供close方法(实际上,InputStream/OutputStream/Reader/Writer都是打上了Closeable标签的);另外,提供了writeString方法,会将String信息以CharsetByte指定的编码格式进行写入;writeFile方法则是针对文件。我们可以先来看看writeFile的实现:

    993f3068817c174fa7ea2aa1e2ed7a22.png

    87e4735cf4f0314d02f68f06e3de1ec2.png

    这里需要注意的是:

    根据文件大小来选择一次性字节发送,还是分批发送;

    要知道如果一次性将非常大的文件字节流发送到对方,会造成对方内存区域紧张,而

    分批字节发送会很好的缓解压力!

    提供和协议相关的信息类

    字符集信息类:

    6794bd6d7fd9db0d9e1e48d837243f10.png

    对于服务器,需要知道根据编码BYTE找到字符集,对于客户端,需要根据字符集找到

    对应编码的BYTE。

    a441f73c22df7243da8c1ab86acf53b5.png

    f6b738a39db7f6122998572a02e4f48c.png

    那么在内存中,应该存在初始化好的字符集!

    c95bf6872f140986bd7ca415cb31a8ee.png

    命令信息类:

    f8dece729129c577a76dc1443b8550e9.png

    627dd6562e392765cf8b6d1d1ed29c66.png

    我们可以清楚的看到,通过ENUM,我们轻松完成了字符串命令与命令编码的映射关系!

    更加重要的是见名知意!

    我们来看看getSendableClass()是干嘛的呢?

    044844c316d3a9165ca6a26af8168c14.png

    很显然,如果sendMsg,那么是一类处理手段,如果是sendFile将是另一类处理手段。

    同样的,在内存中,我们应该初始化好这类信息:

    f20c4121d1f4ba6b980916788146ae3a.png

    提供客户端处理类

    对于sendMsg,sendFile,downloadFile而言,它们是可以抽象出来的!

    bbf8a072285b208e7a48623cee36a861.png

    3d682fea79879923325c9b92bf0d86cd.png

    我们可以来具体看一看SendFileable这个类:

    d00947fbdf7c455bf2c342842fea0208.png

    先来看看getCommandType():

    a6d0d49708e43254a9f0e2d82988537d.png

    其实,就是为了客户端向服务端发送命令类型提供支持!

    String[] token是什么呢?

    对于sendMsg charset=gbk 世界,你好  而言,token就是{“sendMsg” , “gbk” ,

    “世界,你好”}。也就是说,TOKEN其实就是一组逻辑单元!

    看看具体的doTask()是怎么做的:

    9188b8d74c72c1c39b0d3329221bf002.png

    第一步,发送命令类型;

    第二步,发送文件名称编码以及文件名称对应编码的字节流以及长度

    第三步,等待服务端响应,如果服务端已经存在了此文件,则拒绝;否则开始writeFile

    感悟:

    有些时候,我们需要等待;而不是一股脑的把东西都发送过去,也许是不必要的!

    让客户端运转起来!---》ClientMain

    1b2678f0b8bedcfad6ccafd954048d9a.png

    循环起来:

    a8ad4c42abb08ad5bde3aa5d68c382d3.png

    客户端在CMD下发送的命令,首先通过LineProcesser预处理下,然后形成TOKEN,根据

    TOKEN找到对应处理类,利用反射实例化处理类,调用doTask方法即可!

    提供服务端处理类:

    a9a1993020cefebc5a0bb0091a0c554f.png

    Worker是具体负责和客户端通信的线程,应该持有SocketWrapper的引用,同时通过ID来

    进行Worker的标示,下面我们来看看run()是怎么处理的:

    52b9dcb51a53c0b03f295cfde11f3e31.png

    processMsg/processSendFile/processDownloadFile具体实现,很简单了,大家可以

    自己动手去实现!

    ServerMain:

    3fdb888073cae1859a5a750c02bf82d1.png

    通过代码,我们清楚的看到了,每accept一个client socket,服务端就new一个

    Worker进行处理!

    展开全文
  • 按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。     一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。   先来说说数据包的定义,我这里是包头+...

    新博客地址

    转自:http://vtrtbb.javaeye.com/blog/849336

    这是转自javaeye的一篇文章,作者是vtrtbb。

    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。

     

     

    一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。

     

    先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体

     

    包类型 byte 型

    包长度 int 型

    消息体 byte[]

     

    包总长度为 1 + 4 +  消息体.getBytes().length

     

    发包方法如下:

    [java] view plain copy
    1. private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {    
    2.         byte[] bytes= msg.getBytes();    
    3.         int totalLen = 1 + 4 + bytes.length;    
    4.                                 out.writeByte(1);    
    5.         out.writeInt(totalLen);    
    6.         out.write(bytes);    
    7.         out.flush();    
    8.     }    
     

     

    客户端发送消息类为:

    [java] view plain copy
    1. 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;    
    2. import java.io.IOException;    
    3. import java.io.InputStream;    
    4. import java.io.OutputStream;    
    5. import java.net.Socket;    
    6. import java.net.UnknownHostException;    
    7. import java.util.Scanner;    
    8.     
    9. public class MsgClient {    
    10.     
    11.     private DataOutputStream outs;    
    12.         
    13.     public static void main(String[] args) {    
    14.         try {    
    15.             MsgClient client = new MsgClient();    
    16.             client.connServer("127.0.0.1"9292);               
    17.         } catch (UnknownHostException e) {    
    18.             e.printStackTrace();    
    19.         } catch (IOException e) {    
    20.             e.printStackTrace();    
    21.         }    
    22.     }    
    23.         
    24.         
    25.     private void sendTextMsg(DataOutputStream out,String msg ) throws IOException {    
    26.         byte[] bytes= msg.getBytes();    
    27.         int totalLen = 1 + 4 + bytes.length;    
    28.         out.writeByte(1);    
    29.         out.writeInt(totalLen);    
    30.         out.write(bytes);    
    31.         out.flush();    
    32.     }       
    33.         
    34.     public void connServer(String ip,int port) throws UnknownHostException, IOException {    
    35.         Socket client = new Socket(ip,port);    
    36.         InputStream in = client.getInputStream();    
    37.         OutputStream out = client.getOutputStream();    
    38.         outs = new DataOutputStream(out);    
    39.         while(true) {    
    40.             Scanner scaner = new Scanner(System.in);    
    41.             sendTextMsg(outs, "测试消");    
    42.         }           
    43.     }    
     

     

    服务端接收类为:

    [java] view plain copy
    1. import java.io.DataInputStream;    
    2. import java.io.FileOutputStream;    
    3. import java.io.IOException;    
    4. import java.io.InputStream;    
    5. import java.net.ServerSocket;    
    6. import java.net.Socket;    
    7.     
    8. public class MsgServer {    
    9.     public static void main(String[] args) {            
    10.         try {    
    11.             MsgServer server = new MsgServer();    
    12.             server.setUpServer(9090);    
    13.         } catch (IOException e) {    
    14.             e.printStackTrace();    
    15.         }    
    16.     }    
    17.         
    18.     public void setUpServer(int port) throws IOException {    
    19.         ServerSocket server = new ServerSocket(port);    
    20.         while(true) {    
    21.             Socket client = server.accept();    
    22.             System.out.println("客户端IP:"+client.getRemoteSocketAddress());    
    23.             processMesage(client);    
    24.         }    
    25.     }    
    26.         
    27.     private void processMesage(Socket client) throws IOException {    
    28.         InputStream ins = client.getInputStream();          
    29.         DataInputStream dins = new DataInputStream(ins);    
    30.         //服务端解包过程    
    31.         while(true) {    
    32.             int totalLen = dins.readInt();    
    33.             byte flag = dins.readByte();    
    34.             System.out.println("接收消息类型"+flag);    
    35.                 
    36.             byte[] data = new byte[totalLen - 4 - 1];    
    37.             dins.readFully(data);    
    38.             String msg = new String(data);    
    39.             System.out.println("发来的内容是:"+msg);      
    40.         }    
    41.     }    
    42. }    
     

     

     

    这样就基本完成了,但实际还有好多问题,比如说服务端用如何用多线程服务来完成客户端的请求已提高效率,如果是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。

    展开全文
  • 一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。   先来说说数据包的定义,我这里是包头+内容 组成的:其中包头内容分为包类型+包长度, 那就是 消息对象=包类型+包长度+消息体   包...
  • 自定义Socket通信协议

    2015-01-15 22:57:45
    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。 一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。 先来说说数据包的定义,我这里是包头+内容 组成的:...
  • 好多公司都会有最佳实践,今天就说说如何自定义协议,一般自定义协议都是公司内部各个部门定义的,当然了我写的比较简单。注意:本教程是采用netty-all-5.0.0.Alpha2.jar,netty5的版本,不是网上很多的例子都是采用...
  • 自定义socket 协议包头

    2014-06-27 17:54:00
    按照网络通信的传统,我们都会自定义协议,这有很多好处,大家可以自己体会(嘿嘿)。 一直不知道socket通信时候自定义数据包是什么样子的,偶然做了个小例子。 先来说说数据包的定义,我这里是包头+内容 组成的:...
  • Cmake是一套跨平台的工程构建工具sudo apt-get install cmake一个Cmake的例子生成一个demo工程,包括一个hello.cpp文件(在demo工程下)#include int main(int argc, char **argv){printf("Hello world!\n");return 0;...
  • 一个Cmake的例子 生成一个demo工程,包括一个hello.cpp文件(在demo工程下)   #include &lt;stdio.h&gt; int main(int argc, char **argv) { printf("Hello world!\n")...
  • 本文主要介绍注册表的概念与其相关根项的功能,以及浏览器如何通过连接调用自定义协议并与客户端进行数据通信。文中讲及如何通过C#程序、手动修改、安装项目等不同方式对注册表进行修改。其中通过安装项目对注册表...
  • 本文主要介绍注册表的概念与其相关根项的功能,以及浏览器如何通过连接调用自定义协议并与客户端进行数据通信。文中讲及如何通过C#程序、手动修改、安装项目等不同方式对注册表进行修改。其中通过安装项目对注册表...
  • 引言 本文主要介绍注册表的概念与其相关根项的功能,以及浏览器如何...当中最为实用的例子将介绍如何通过"安装项目"修改注册表建立自定义协议,在页面通过ajax方式发送路径请求,并在回调函数中调用自定义协议。...

空空如也

空空如也

1 2 3 4 5 ... 8
收藏数 152
精华内容 60
关键字:

自定义通信协议例子