在网络技术中, 前后端通讯是一个难点, 也是一个服务器程序员必须攻克的技术瓶颈. 在两台计算机要实现信息交互, 就需要一种技术来说, 而两台计算机网卡来实现, 无非就是两种技术 UDP 和 TCP, 其中, 两种技术由于不同的特性, 使用在不同的地方, 在一些不要求高到达型传输领域 (如: 视频) 使用 UDP 之外, 90% 的通讯都是通过 TCP 协议来传输, 由于其可靠的安全达到性.
在程序员眼里, 不用太想了解网卡是通过 5 层协议 (或者说 3 层协议) 怎么实现 tcp 通讯, 在编程语言里 socket 就是 tcp 的代名词(可能有点不准备哈, 但是我个人是这样认为的).
我们先用一个简单的 http 协议中 get 案例来说, 让大家更好理解游戏前后端的通讯协议指定.
以下为 java 代码:
- public class testHttpClient {
- public static void main(String[] arg){
- Socket socket=new Socket();
- try {
- socket.connect(new InetSocketAddress("www.baidu.com", 80), 300);
- OutputStream o=socket.getOutputStream();
- //http 协议字符串 包括包头和包体, http 采用 "\r\n" 做为分割, 包头除了第一排之外, 都是采用 key:value 方式来保存信息, 一把服务器解锁提取
- String requestStr="GET / HTTP/1.1\r\n" // 第一排三个信息 分别是 1, 请求方式(一把都会用两张 get 和 post) 2, 请求路径 3, 采用 http1.1 协议
- +"Host: www.baidu.com\r\n" // 申明 Host
- // 如果有其它 hearder 字段信息可以防止在这里
- +"\r\n" // 这里表示包头结束
- +"\r\n"; // 这里表示包体结束(由于这个 get 没有任何的结构体, 如果需要向 post 那样传参 key1=value1&key2=value2, 注意一定要在包头中增加 "Content-Length:***" 申明包体长度)
- o.write(requestStr.getBytes());
- o.flush();
- BufferedReader i= new BufferedReader(new InputStreamReader (socket.getInputStream()));
- String response="";
- String readOneStr=null;
- while ((readOneStr=i.readLine())!=null){
- response+=readOneStr+"\r\n";
- }
- System.out.println(response);
- }catch (IOException ioe){
- ioe.printStackTrace();
- }
- }
重点说明一下整个 socket 发送内容: http 协议采用 "\r\n" 作为解码器分割符号, 这个协议的二进制流包括包头和包体两个部分, 请求协议具体内容:
包头:
GET / HTTP/1.1\r\n
使用空格分割成三个信息 1, 请求方式(一把都会用两张 get 和 post) 2, 请求路径 3, 采用 http1.1 协议
- Host: www.baidu.com\r\n
- Content-Length: ***\r\n
- Content-type:application/JSON\r\n
- Cookie:************\r\n
采用 key:value 的方式向服务器提交信息. Content-Length 很重要, 用于服务器读取包体的长度.
\r\n
再用 "\r\n" 来说明 http 包头信息结束, 后面的字节流是包头内容
\r\n
这个说明请求包体结束
这里基本上一个 http 协议请求协议结束, 然后我们再来看看服务器 http 响应信息, 也是包括包头和包体, 包体有点变化的是第一个排 "HTTP/1.1 200 OK\r\n" 也是三部分信息, 返回协议, 状态码和状态(http 中 200 就是成功响应的意思), 除此以外也是也是 key:value 值了. 也是采用连续的两个 "\r\n" 来分割包头和包体字节流. 包体就没有什么好说了, 就是一些 html,CSS,JS 代码, 也就我们实际用户能看见的网页代码.
附图如下:
看完了上面的 http 协议, 我们就可以理解我接下来要说明我在所以游戏项目服务器设计的协议, 我们游戏项目由于功能需要, 都是采用 tcp 长连接, 服务器需要定时向游戏前端发送不同指令和响应前端请求, 所以需要一个严谨高效的通讯协议:
包头
包头开始识别码(int)4+ 命令编号(shot)2 + 消息唯一编号(int)4 + 内容长度(int)4
包体
内容 bytes[]
用意说明: 包头含有四个重要信息, 第一个是包头开始识别码, 用于服务器在循环读取 socket 管道信息时, 发现新包开始. 第二个是命令编号, 就是前来请求服务器的动作指令(如:"1" 用户登入验证,"2" 为击杀命令), 服务器会自动去调用响应模块代码. 第三部分是这个包唯一码, 用于防止前端重复提交包, 造成服务器业务逻辑错误. 第四部分, 就是包体字节流长度. 这样, 每一个包头固定长度为 14 字节, 而包体是可有可无的, 取决于调用命令是否是需要前端传递参数.
附上分包器的代码(java 代码):
- public class EncodedMessage {
- public static int code=44434533;
- /*
- codeUnid 命令
- messageUnid 消息唯一码
- body 包体字节流
- */
- public static byte[] encoded(int codeUnid,int messageUnid,byte[] body){
- //44434533 / 包头(int)4+ 命令编号(shot)2 + 消息唯一编号(int)4 + 内容长度(int)4 + 内容 bytes /
- ByteBuffer buffer= ByteBuffer.allocate(14+body.length);
- buffer.putInt(code);
- short code=(short)codeUnid;
- buffer.putShort(code);
- buffer.putInt(messageUnid);
- buffer.putInt(body.length);
- buffer.put(body);
- return buffer.array();
- }
- public static int byteArrayToInt(byte[] b) {
- return b[3] & 0xFF | (b[2] & 0xFF) << 8 | (b[1] & 0xFF) << 16 | (b[0] & 0xFF) << 24;
- }
- public static short bytesToShort(byte[] b) {
- return (short) (b[1] & 0xff | (b[0] & 0xff) << 8);
- }
- }
这一套通讯协议都是我在所以游戏服务器采用, 服务器可以很好处理分包, 不会出现连包情况.
来源: http://server.51cto.com/sOS-600671.htm