unit uPackage;
// 应用协议
// cxg 2016-9-23
// 包=包头+包体

interface

uses
SysUtils, Classes, PeachCtrl.Net.IocpTcpServer, System.Generics.Collections,
Winapi.Windows, System.SyncObjs, PeachCtrl.Net.BlockingTcpClient, untLog,
System.Math;

const // 包长
pack_len = 8192;

const // 版本号
ver_1 = 1;

const // 命令分类
cmd_qry_req = 1;
cmd_qry_res = 2;
cmd_post_req = 3;
cmd_post_res = 4;
cmd_up_file_req = 5;
cmd_up_file_res = 6;
cmd_down_file_req = 7;
cmd_down_file_res = 8;
cmd_data = 9;

type
THead = packed record // 包头
cmd: Byte;
len: Integer;
packNo: Integer;
packQty: Integer;
ver: Byte;
end;

type
TTask = record // 任务
context: TCustomIocpTcpServer.TPerHandleData;
body: TBytes;
cmd: Byte;
ver: Byte;
end;

PTTask = ^TTask;

type
TTaskClient = record
Recved: Boolean;
body: TBytes;
end;

var
g_tmpList: TList<PTTask>; // 临时队列
g_task_client: TTaskClient;
g_iocp_handle: THandle;

function ValidHead(AHead: THead): Boolean;

function GetTask(AContext: TCustomIocpTcpServer.TPerHandleData): PTTask;

procedure ServerProcessRecved(AContext: TCustomIocpTcpServer.TPerHandleData);

procedure ClientProcessRecved(ARecvThread: TCustomBlockingTcpClient.TRecvThread);

procedure ServerSendStream(const AStream: TStream; AContext: TCustomIocpTcpServer.TPerHandleData; cmd, ver: Byte);

function StreamToBytes(aStream: TStream): TBytes;

function BytesToStream(aBytes: TBytes): TStream;

procedure QuerySQL(const aSQL: string; aClient: TBlockingTcpClient);

procedure InitTaskClient;

function GetBodyLen: Integer;

function GetHeadLen: Integer;

function GetPackQty(AStream: TStream): Integer;

function GetLastPackLen(AStream: TStream): Integer;

implementation

var
FCS: TCriticalSection;

function GetLastPackLen(AStream: TStream): Integer;
begin
Result := AStream.Size mod (pack_len - SizeOf(THead));
if Result = 0 then
Result := pack_len
else
Result := Result + SizeOf(THead);
end;

function GetPackQty(AStream: TStream): Integer;
begin
Result := Ceil(AStream.Size / (pack_len - SizeOf(THead)));
end;

function GetHeadLen: Integer;
begin
Result := SizeOf(THead);
end;

function GetBodyLen: Integer;
begin
Result := pack_len - SizeOf(THead);
end;

procedure InitTaskClient;
begin
g_task_client.Recved := False;
SetLength(g_task_client.body, 0);
end;

procedure QuerySQL(const aSQL: string; aClient: TBlockingTcpClient);
var
head: THead;
headLen, bodyLen, packLen: integer;
buf, body: TBytes;
begin
if aSQL = '' then
Exit;
try
head.cmd := cmd_qry_req;
headLen := SizeOf(THead);
bodyLen := Length(aSQL);
packLen := headLen + bodyLen;
head.len := packLen;
head.packNo := 1;
head.packQty := 1;
head.ver := 1;
SetLength(buf, packLen);
Move(head, buf[0], headLen);
body := BytesOf(aSQL);
Move(body[0], buf[headLen], bodyLen);
aClient.SendBuffer(buf[0], packLen); // 发送请求
except
on e: Exception do
begin
Log.WriteLog('QuerySQL ' + e.Message);
end;
end;
end;

function BytesToStream(aBytes: TBytes): TStream;
begin
Result := TMemoryStream.Create;
Result.Write(aBytes[0], length(aBytes));
Result.Position := 0;
end;

function StreamToBytes(aStream: TStream): TBytes;
begin
try
SetLength(Result, aStream.Size);
aStream.Position := 0;
aStream.Read(Result[0], aStream.Size);
finally
if aStream <> nil then
aStream.Free;
end;
end;

function ValidHead(AHead: THead): Boolean;
begin
Result := (AHead.cmd >= 1) and (AHead.len > SizeOf(THead)) and (AHead.packNo >= 1) and (AHead.packQty >= 1);
end;

function GetTask(AContext: TCustomIocpTcpServer.TPerHandleData): PTTask;
var
i: Integer;
begin
Result := nil;
if AContext = nil then
Exit;
FCS.Enter;
try
try
for i := 0 to g_tmpList.Count - 1 do
begin
if g_tmpList.Items[i].context = AContext then
begin
Result := g_tmpList.Items[i];
Exit;
end;
end;
except
on e: Exception do
begin
Log.WriteLog('GetTask ' + e.Message);
end;
end;
finally
FCS.Leave;
end;
end;

procedure ClientProcessRecved(ARecvThread: TCustomBlockingTcpClient.TRecvThread);
var
buf: TBytes;
head: THead;
bodyLen: Integer;
headLen: Integer;
begin
headLen := GetHeadLen;
if ARecvThread.RingBuffer.NoProcessBufLen < headLen then
Exit;
ARecvThread.RingBuffer.Peep(head, headLen); // 取包头
if not ValidHead(head) then // 校验包头
Exit;
try
if head.packQty = 1 then // 一批次只有一个包
begin
if ARecvThread.RingBuffer.NoProcessBufLen < head.len then
Exit;
InitTaskClient; // 初始化
bodyLen := head.len - headLen;
SetLength(g_task_client.body, bodyLen);
ZeroMemory(g_task_client.body, bodyLen);
SetLength(buf, head.len);
ZeroMemory(buf, head.len);
ARecvThread.RingBuffer.Pop(buf[0], head.len);
Move(buf[headLen], g_task_client.body[0], bodyLen);
SetLength(buf, 0);
g_task_client.Recved := True; // 包都收齐了
end
else if head.packQty > 1 then // 一批次有多个包
begin
if head.packNo = 1 then // 首包
begin
if ARecvThread.RingBuffer.NoProcessBufLen < pack_len then
Exit;
InitTaskClient; // 初始化
SetLength(g_task_client.body, head.len - head.packQty * headLen); // 一次分好缓存
ZeroMemory(g_task_client.body, head.len - head.packQty * headLen);
SetLength(buf, pack_len);
ZeroMemory(buf, pack_len);
ARecvThread.RingBuffer.Pop(buf[0], pack_len);
bodyLen := pack_len - headLen;
Move(buf[headLen], g_task_client.body[0], bodyLen);
SetLength(buf, 0);
end
else if head.packNo = head.packQty then // 尾包
begin
if ARecvThread.RingBuffer.NoProcessBufLen < head.len then
exit;
SetLength(buf, head.len);
ZeroMemory(buf, head.len);
ARecvThread.RingBuffer.Pop(buf[0], head.len);
bodyLen := pack_len - headLen;
Move(buf[headLen], g_task_client.body[(head.packNo - 1) * bodyLen], head.len - headLen);
SetLength(buf, 0);
g_task_client.Recved := True;
end
else
begin // 夹在首包和尾包中间的包
if ARecvThread.RingBuffer.NoProcessBufLen < head.len then
Exit;
SetLength(buf, pack_len);
ZeroMemory(buf, pack_len);
ARecvThread.RingBuffer.Pop(buf[0], pack_len);
bodyLen := pack_len - headLen;
Move(buf[headLen], g_task_client.body[(head.packNo - 1) * bodyLen], bodyLen);
SetLength(buf, 0);
end;
end;
except
on e: Exception do
begin
Log.WriteLog('ClientProcessRecved ' + e.Message);
end;
end;
end;

procedure ServerProcessRecved(AContext: TCustomIocpTcpServer.TPerHandleData);
var
pTask: PTTask;
buf: TBytes;
head: THead;
bodyLen: Integer;
headLen: Integer;
begin
if AContext = nil then
Exit;
headLen := GetHeadLen;
if AContext.RingBuffer.NoProcessBufLen < headLen then // 校验
Exit;
AContext.RingBuffer.Peep(head, headLen); // 取包头
if not ValidHead(head) then // 校验包头
Exit;
try
bodyLen := GetBodyLen; // 包体长
if head.packQty = 1 then // 一批次只有一个包
begin
if AContext.RingBuffer.NoProcessBufLen < head.len then // 校验
Exit;
New(pTask); // 新任务
pTask.context := AContext;
pTask.cmd := head.cmd;
pTask.ver := head.ver;
SetLength(pTask.body, head.len - headLen);
SetLength(buf, head.len);
AContext.RingBuffer.Pop(buf[0], head.len); // 包头数据
Move(buf[headLen], pTask.body[0], head.len - headLen); // 包体数据
SetLength(buf, 0);
PostQueuedCompletionStatus(g_iocp_handle, 0, 0, POverlapped(pTask));
end
else if head.packQty > 1 then // 一批次有多个包
begin
if head.packNo = 1 then // 首包
begin
if AContext.RingBuffer.NoProcessBufLen < pack_len then // 校验
Exit; // 新任务
New(pTask);
pTask.context := AContext;
pTask.cmd := head.cmd;
pTask.ver := head.ver;
SetLength(pTask.body, head.len - head.packQty * headLen); // 一次分好缓存
SetLength(buf, pack_len);
AContext.RingBuffer.Pop(buf[0], pack_len);
Move(buf[headLen], pTask.body[0], bodyLen);
SetLength(buf, 0);
FCS.Enter;
g_tmpList.Add(pTask); // 提交临时队列
end
else if head.packNo > 1 then // 非首包
begin
if AContext.RingBuffer.NoProcessBufLen < head.len then
Exit;
pTask := GetTask(AContext);
if pTask = nil then
Exit;
SetLength(buf, head.len);
AContext.RingBuffer.Pop(buf[0], head.len);
Move(buf[headLen], pTask.body[(head.packNo - 1) * bodyLen], bodyLen);
SetLength(buf, 0);
if head.packNo = head.packQty then // 包都收齐了
begin
PostQueuedCompletionStatus(g_iocp_handle, 0, 0, POverlapped(pTask));
FCS.Enter;
try
g_tmpList.Delete(g_tmpList.IndexOf(pTask)); // 从临时队列中删除
finally
FCS.Leave;
end;
end;
end;
end;
except
on e: Exception do
begin
log.WriteLog('ServerProcessRecved ' + e.Message);
end;
end;
end;

procedure ServerSendStream(const AStream: TStream; AContext: TCustomIocpTcpServer.TPerHandleData; cmd, ver: Byte);
var
buf: TBytes;
head: THead;
headLen, bodyLen: integer;
packQty, i, lastPackLen: Integer;
begin
if (AStream = nil) or (AContext = nil) then
Exit;
try
headLen := GetHeadLen;
bodyLen := GetBodyLen;
packQty := GetPackQty(AStream);
lastPackLen := GetLastPackLen(AStream);
if packQty > 1 then
begin
for i := 1 to packQty do // 要分包传输
begin
if i = 1 then // 首包
begin
head.cmd := cmd;
head.len := (packQty - 1) * pack_len + lastPackLen; // 总长
head.packNo := 1;
head.packQty := packQty;
head.ver := ver;
SetLength(buf, pack_len);
ZeroMemory(buf, pack_len);
Move(head, buf[0], headLen);
AStream.Position := 0; // 指针定位
AStream.Read(buf[headLen], bodyLen);
AContext.SendBuffer(buf[0], pack_len);
SetLength(buf, 0);
end
else if i = packQty then // 尾包
begin
head.cmd := cmd;
head.len := lastPackLen;
head.packNo := packQty;
head.packQty := packQty;
head.ver := ver;
SetLength(buf, lastPackLen);
ZeroMemory(buf, lastPackLen);
Move(head, buf[0], headLen);
AStream.Read(buf[headLen], lastPackLen - headLen);
AContext.SendBuffer(buf[0], head.len);
SetLength(buf, 0);
end
else // 夹在包头和包尾中间的包
begin
head.cmd := cmd;
head.len := pack_len;
head.packNo := i;
head.packQty := packQty;
head.ver := ver;
SetLength(buf, pack_len);
ZeroMemory(buf, pack_len);
Move(head, buf[0], headLen);
AStream.Read(buf[headLen], bodyLen);
AContext.SendBuffer(buf[0], head.len);
SetLength(buf, 0);
end;
end;
end
else if packQty = 1 then
begin // 只要传输一包
head.cmd := cmd;
head.len := AStream.Size + headLen;
head.packNo := 1;
head.packQty := 1;
head.ver := ver;
SetLength(buf, head.len);
ZeroMemory(buf, head.len);
Move(head, buf[0], headLen);
AStream.Position := 0;
AStream.Read(buf[headLen], AStream.Size);
AContext.SendBuffer(buf[0], head.len);
SetLength(buf, 0);
end;
except
on e: Exception do
begin
Log.WriteLog('uPackage.ServerSendStream ' + e.Message);
Exit;
end;
end;
end;

initialization
FCS := TCriticalSection.Create;

finalization
FCS.Free;

end.

异步SOCKET分包和组包的一种通用算法的更多相关文章

  1. Qt通过UDP传图片,实现自定义分包和组包

    一.包头结构体 //包头 struct PackageHeader { //包头大小(sizeof(PackageHeader)) unsigned int uTransPackageHdrSize; ...

  2. Socket之UDP分包组包

    一般传输大的文件和信息的时候需要涉及到分包和组包,方法有很多,下面一种是借鉴了别人的思路,供大家参考哈 分包 1.取出需要传输的文件和字符的长度和大小放入缓存区里面: 2.设定固定传输的长度,用需要传 ...

  3. C#高性能大容量SOCKET并发(五):粘包、分包、解包

    原文:C#高性能大容量SOCKET并发(五):粘包.分包.解包 粘包 使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一 ...

  4. C# 实现的多线程异步Socket数据包接收器框架

    转载自Csdn : http://blog.csdn.net/jubao_liang/article/details/4005438 几天前在博问中看到一个C# Socket问题,就想到笔者2004年 ...

  5. c#Udp分包组包方法

    udp通信协议,相信大家都知道这个.由于是无连接的协议,所有udp的传输效率比tcp高.但是udp协议传输较大的数据文件得分包 最近写了个分包组包的方法,拿来和大家分享,如果有什么不妥的地方,欢迎点评 ...

  6. Qt Socket 收发图片——图像拆包、组包、粘包处理

    之前给大家分享了一个使用python发图片数据.Qt server接收图片的Demo.之前的Demo用于传输小字节的图片是可以的,但如果是传输大的图片,使用socket无法一次完成发送该怎么办呢?本次 ...

  7. TCP的组包、半包、粘包与分包

    一.概念 1)组包.简单的说就是tcp协议把过大的数据包分成了几个小的包传输,接收方要把同一组的数据包重新组合成一个完整的数据包. 2)半包.指接受方没有接受到一个完整的包,只接受了部分,这种情况主要 ...

  8. 可扩展多线程异步Socket服务器框架EMTASS 2.0 续

    转载自Csdn:http://blog.csdn.net/hulihui/article/details/3158613 (原创文章,转载请注明来源:http://blog.csdn.net/huli ...

  9. 一个高性能异步socket封装库的实现思路 (c#)

    前言 socket是软件之间通讯最常用的一种方式.c#实现socket通讯有很多中方法,其中效率最高就是异步通讯. 异步通讯实际是利用windows完成端口(IOCP)来处理的,关于完成端口实现原理, ...

随机推荐

  1. OpenJudge_2757:最长上升子序列

    描述一个数的序列bi,当b1 < b2 < ... < bS的时候,我们称这个序列是上升的.对于给定的一个序列(a1, a2, ..., aN),我们可以得到一些上升的子序列(ai1 ...

  2. windwos .bat脚本大全

    记录一个很有用比较全面的windows .bat脚本网站 https://www.cnblogs.com/zhaoqingqing/p/4620402.html

  3. HTML页面中解决内容元素随窗口变化布局变乱问题

    1.给body加上一个min-width最小宽度,以px为单位,这样当页面变小时,当达到你所设置的最小宽度,body的宽度不再改变,超出的部分会用横向滚动条显示,其内所有元素的布局也不会受影响. 2. ...

  4. 5.1 qbxt 一测 T2

    求和[问题描述] 组合数 C(n,m)是从 n 个物品中取 m 个的方案数. C(n,m)=(n!)/(m!(n-m)!) 斐波那契数列 F 满足,F[0]=F[1]=1,n≥2 时 F[n]=F[n ...

  5. POJ-1163 递推

    代码很容易看明白,就不详解了. 这个是空间优化的代码. #include <iostream> #include <algorithm> #define MAX 101 usi ...

  6. centos7 ftp 500 OOPS: cannot change directory:/var/ftp/xutong/

    在设置多用户登录的时候 该指定的用户xutong对于上级目录/var/ftp 没有访问权限 修改一下上级目录的权限 chmod /var/ftp 对于ftp多用户访问的配置修改也做一个记录 以是设置F ...

  7. Vijos1144 皇宫看守 (0/1/2三种状态的普通树形Dp)

    题意: 给出一个树以及一些覆盖每个点的花费,求每个点都能被自己被覆盖,或者相邻的点被覆盖的最小价值. 细节: 其实我乍一眼看过去还以为是 战略游戏 的复制版 可爱的战略游戏在这里QAQ(请原谅这波广告 ...

  8. jmespath库解析json

    在测试过程中,经常会去JSON中的某个值,jmespath可以是除了jsonpath的另外一种选择. 下面通过几个例子来说明jmespath在python的使用 jmespath python安装 非 ...

  9. @locked_cached_property ---flask.helpers模块

    源码: class locked_cached_property(object): """A decorator that converts a function int ...

  10. HDU1021-Fibonacci Again,,找规律就好了~~~

    Fibonacci Again Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) ...