【DIOCP-说明书】常见问题(FAQ)持续更新

1.diocp-v5使用在哪里可以得到帮助
http://www.diocp.org/?page_id=159
  可以浏览官方社区的一些基础说明,该文档会陆续进行整理。

 

2.diocp-v5怎么样编译DEMO 
  <<关于DEMO的编译>>
http://www.diocp.org/?p=16
2.1
Q:在编译DiocpV5DEMO是编译不通过
A:你编译DEMO的时候,把diocpv5\source加入到路径就好
  不要把所有Source下的文件添加到工程,因为有一些文件是给android/ios平台用的当然不能在windows下面编译,
  例如:diocp.core.rawPosixSocket.pas

 

3.服务端避免不了和多线程打交道,请先好好熟悉多线程的基础知识和注意实现。下面有多线程的一些文章,请认真看看。
http://blog.qdac.cc/?p=890

 

 

Q:服务端在线信息显示为何与实际有差异?
Q:服务端许多死链接都显示在线?
A:DIOCP默认关闭了心跳。所以会造成很多死链接,仍然显示在线,详细原因点击查看该文章:http://www.diocp.org/?p=189
(【DIOCP3-说明书】关于服务端的KeepAlive属性(心跳))

 

Q: 在Diocp中我需要另外开线程去处理逻辑吗?
A: 在编码层(TDiocpCoderTcpServer), 逻辑处理事件(OnContextAction), 是由DiocpTask/Qworker(编译开关(QDAC_QWorker)可以进行切换)驱动的,和底层的通信线程是不相干扰。所以在编码层不需要使用另外的线程池,来处理逻辑。

 

Q: 同一个连接的OnContextAction会同时被多个线程触发吗?
A:编码层的OnContextAction是排队处理的,底层接收到数据后交由注册的解码器进行解码, 解码成功后,放到任务队列,然后依次触发OnContextAction,所以同一个连接的OnContextAction同时只会有一个线程调用。

 

Q: Diocp的断开日志
A: Diocp在DEBUG模式下面会记录详细的日志。会记录每个连接断开的原因。如下
   1> xxxx:[2824]投递发送数据请求时出现了错误。错误代码:10054
     说明: 这种日志在发送数据的时候系统返回了错误代码,可以根据错误代码(如10054),查询到相应的错误信息。
   2> xxxx: [720]响应接收请求时出现了错误。错误代码:64!
     说明:这种日志在响应接收请求时系统返回的错误,根据错误代码查询响应的信息。
   3> xxxx: [704]接收到0字节的数据,该连接将断开!
     说明: 服务端接收到长度为0字节的数据,认为对方关闭了连接。服务端相应的会释放连接触发OnDiscconected函数。
   4> xxxx :[812]执行[CheckNextSendRequest::lvRequest.ExecuteSend]失败: 处理投递发送请求数据包时,发现异步关闭请求(Request.Tag = -1)。进行关闭处理!
     说明: 这种日志一般在服务端投递了异步关闭请求(PostWSAClose)时,会出现该日志。
   上面列举的日志都是正常的,DIOCP只是记录详细情况,以便出现问题时能有据可查,请勿惊慌。
   * 中括号[]中的数字是连接对应的SocketHandle,是连接的套接字句柄。

   MSDN上面错误代码说明:

      https://msdn.microsoft.com/en-us/library/ms740668.aspx 

      https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx

 

Q: Diocp如何关闭日志记录。
A: Diocp在DEBUG模式会记录详细日志。可以把DEBUG模式关闭
   D2007工程为例: Project->Options->Directories/Conditionals->Conditional defines 中把DEBUG删除

【DIOCP3-说明书】DIOCP3的输出日志

DIOCP3除了有详细的监控面板之外,还有详细的输出日志,当然需要打开日志编译开关!

在工程选项加入DEBUG编译指令,这样在运行中就可以看到DIOCP3的运行详细日志

日志输出在EXE相同目录的LOG文件夹下面。

 

日志说明:

8276_iocpSVR_2015010712.log

8276      是进程ID

iocpSVR是程序中iocpTcpSERVER的名字

2015010712 日志文件生成时间

 

12:30:24:318[message][PID:8276,ThreadID:5520]:[756]接收到0字节的数据,该连接将断开!

PID是进程ID

ThreadID: 工作线程ID

756 套接字

收到一个0字节长度的数据,diocp认为对方已经断开,diocp将关闭套接字对应的连接。

发生在响应WSARecv的事件中。

 

12:37:45:764[message][PID:8276,ThreadID:5520]:[760]响应接收请求时发现IOCP服务关闭

IOCP服务已经关闭, 一般发生在程序关闭,IOCP服务停止的时候,有客户端进行了连接的时候

发生在响应WSARecv的事件中。

 

12:37:45:764[message][PID:8276,ThreadID:5520]:[760]投递发送数据请求时出现了错误。错误代码:10038

在进行发送数据时出现的异常,在执行底层API时出现的异常,错误代码是系统给出的,可以百度,或者进入CMD运行

net helpmsg 10038 查看对应的错误原因。

错误代码121:  信号灯超时已到

一般发生情况,iocpTcpServer打开了KeepAlive,建议关闭KeepAlive自己进行心跳处理。

 

【DIOCP3-说明书】关于服务端的KeepAlive属性(心跳)

DIOCP3中服务端(IocpTcpServer)中提供一个KeepAlive属性,之前版本默认开启,2014-12-30之后的版本默认是关闭的。

首先要搞明白KeepAlive的作用是什么,服务端开启KeepAlive后在建立连接的时候会设置Socket的SO_KeepAlive选项

 

MSDN的解释是这样的:

http://msdn.microsoft.com/en-us/library/windows/desktop/ee470551(v=vs.85).aspx

SO_KEEPALIVE socket option

The SO_KEEPALIVE socket option is designed to allow an application to enable keep-alive packets for a socket connection.

SO_KEEPALIVE 套接字选项是被设计允许应用程序对一个套接字连接开启keep-alive包(翻得不好,意思基本上对了)

 

开启后,如果客户端关闭(或者网络异常)等情况,服务端就会马上知道。这样服务端就会断开该连接。但是该选项并不是所有的环境下面都能运作的很好,据我所知,如果客户端是Andriod,就不行,还有一些硬件设备,也是不行的,所以我改成默认是关闭状态。

关闭后,问题就来了,客户端如果断线,可能服务器端就无法及时发现,这样服务端会出现很多死链接。只有当进行对socket进行操作(比如发送数据socket.send)时遇到异常时才会发现该连接无效,然后进行断开。

一般实际应用,如果要保持长连接,可以在客户端发送心跳数据包(这个数据包因为要根据自己定义的协议格式进行发送,所以无法在底层完成。)然后在服务端做超时检测,如果超过一定的时间(这个时间自己协定)就把该连接主动T掉。

【DIOCP3-说明书】监控面板说明

IV](4]8GMYH)GSB9~A])TRN

发送队列 sending Queue: 

    push 任务压入<投递的发送任务>,

    pop 任务取出<投递到IOCP队列时弹出>,

    complted: 完成<IOCP投入完成后得到了响应>

    abort:取消<客户端断开,队列中还没有来得及发送的任务取消>

 

acceptex: 投递了100个,响应了1个

context info: 创建了100个连接上下文(对应的客户端连接)实例, 借出去了100次,还回来1次

sendRequest:创建了1个发送请求<TSendRequest>实例,借出去21次,还回来21次。

 

workers: 3个iocp工作线程(点击[数量]可以看出线程的工作状态<阻塞(忙)/等待(空闲)>)

【DIOCP3-说明书】关于DIOCP3的服务申明

近段时间发现明显关注diocp3的人越来越多,首先感谢大家选择使用DIOCP3,虽然我不能保证diocp3底层100%不出问题,但是我会尽量去保障diocp3底层代码的稳定,使他成为一个稳定的通信库。由于作者现在还是一个苦逼的上班族,还得为老板卖命。为了不至于被老板炒鱿鱼。我今天慎重考虑了下,平常大家在使用diocp3的过程中,碰到bug,请大家点击<<【DIOCP3-说明书】关于DIOCP3服务端常见的问题>> (http://diocp.wedelphi.com/?p=115)看看是否有类似的情况。如果还是搞不定不要担心,没有请剥离一份简单的代码(客户端和服务端)请不要使用三方控件,发可以编译的工程文件给我,我尽量安排时间(一般在中午休息和晚上)调试程序。谢谢大家理解!

【DIOCP3-说明书】关于DIOCP3服务端常见的问题

这几天群里面使用DIOCP3的人越来越多,一般或多或少都会碰到一些问题,碰到问题并不可怕,可怕的是不知道如果下手去查找问题,解决问题。服务端的编写都一样,需要小心谨慎,毕竟我们要写的程序是需要长期运行的,不能有半点马虎。每一行代码都要认真对待,我现在把大家碰到的问题整理下,希望大家碰到问题时,好好检查下代码,看看是不是也有类似的问题。

 

第一,也是很多人容易犯的错误,不要在diocp3事件中,直接访问UI

    这个情况几乎我每次都强调,但基本上90%的人都犯这样的错误,因为diocp3中,所有的事件都在线程中触发的,UI资源一般都不是线程安全的,直接访问UI,一般会锁死线程,程序退出时无法退出进程,而且会出现不可预料的错误。
   日志记录方面如果要显示到Memo中,请使用线程日志安全类SafeLogger,可以参考safelogger的demo,虽然Memo是做的SendMessage处理,还是不建议直接在线程池对Memo直接进行操作

 

第二,访问违规错误。

    首先跟踪下代码看看,访问违规的错误代码是哪一行引起的,看看对象是否提前是否,或者是公用的对象,如果是公用的对象是否有做临界处理。如果异常停留在diocp底层的代码,多数情况下面是程序栈帧被破坏,检查下指针操作的位置(move,copyMemory的位置)看看目标指针是否有越界和指定错误的情况。建议应用层,少用指针和结构体,用操作对象或者内存流来代替结构体和指针操作。

 

第三,日志的问题。

    diocp底层记录日志在Debug模式下面记录日志比较详细,每个连接的断开<包括主动和对方断开>都做了日志记录。那些日志一般都是正常存在的。

 

第四,心跳和重连。

    diocp3中有设置KeepAlive的选项,但是这个选项不是万能的,有很多客户端突然断线,或者某些机器上面会不起作用。这个时候需要做业务层的心跳包,一般处理方法是,客户端定时发送很小的数据包,到服务端,服务端做轮询的超时检测,把超时的连接主动在服务端T掉,客户端在发送数据包的过程中也可以触发没有正常连接的异常,然后进行重连。

 

最后再强调下,服务端编程,要处理要多线程资源访问的问题,注意好指针操作。要写出稳健的服务端,还需要逻辑代码和diocp3的配合,需要谨慎对待。

【DIOCP3-说明书】iocpTask中添加信号触发任务功能

【应用场景】

信号触发任务是为了可以重复触发信号对应的任务,而且可以把回调函数的绑定和触发信号分离,比如你可以在主窗体中注册信号对应的事件,然后在任何的线程和单元中安全的触发信号。

 

【使用DEMO】

主窗体中注册信号绑定回调函数

//注册信号,并绑定信号对应的回调函数
iocpTaskManager.registerSignal(1, OnSignalWork);


// 回调事件函数
procedure TfrmMain.OnSignalWork(pvTaskRequest:TIocpTaskRequest);
var
  lvData:TSimpleDataObject;
begin
  lvData:= TSimpleDataObject(pvTaskRequest.TaskData);
  if GetCurrentThreadId = MainThreadID then
  begin
    Memo1.Lines.Add('exeucte signal task in main thead:' + lvData.DataString1);
  end else
  begin
    logMessage('exeucte signal task in thread:' + lvData.DataString1);
  end;
end;

 

 

 // 可以在任何的地方安全的触发信号
 iocpTaskManager.SignalATask(1, TSimpleDataObject.Create('signal param'), ftFreeAsObject);

 

【相关函数】

注册函数:

procedure registerSignal(pvSignalID: Integer; pvTaskWork: TOnTaskWork);

pvSignalID      注册的信号ID, 重复注册时会引发异常。

pvTaskWork   信号对应的回调函数

 

触发函数:

procedure SignalATask(pvSignalID: Integer; pvTaskData: Pointer = nil;
        pvDataFreeType: TDataFreeType = ftNone; pvRunInMainThread: Boolean = False;
        pvRunType: TRunInMainThreadType = rtSync);

 

pvSignalID             信号ID

pvTaskData          触发信号带入的一些参数数据,在回调函数中可以获取(pvTaskRequest.TaskData)

pvDataFreeType   参数数据在任务执行完成或者投递失败后释放的方式(; , 

                             ftNone, 不做任何处理

                             ftFreeAsObject 调用TObject(TaskData).Free

                             ftUseDispose,    调用Dispose(TaskData)

pvRunInMainThread 是否在主线程中执行回调函数

pvRunType         主线程同步的方式,不带包的DLL中不能用rtSync模式

DIOCP3-DIOCP1升级到DIOCP3

DIOCP3兼容DIOCP1的,有些属性做了修改

DIOCP3, uIOCPConsole没有了, uMemPool没有了

 

1.DIOCP1,代码:

9(BIPCH3%YHO3{3A~{{YEWI

DIOCP3中去掉TIOCPContextFactory, 可以直接往TIocpConsole(uIocpCentre单元中)对象上注册。TiocpConsole有对应的方法

这样可以每个TIocpConsole有自己独立的编码和解码器,更灵活,更方便了。

 

2.在线列表

DIOCP1中TIOCPContextFactory.instance.IOCPContextPool.getUsingList(lvList);

DIOCP3中TIocpTcpServer.getOnlineContextList(pvList:TList);

 

3.socket心跳

DIOCP1 设置 FIOCPConsole.setSystemSocketHeartState(false);

DIOCP3 设置 FIOCPConsole.KeepAlive := false;

 

4.断开客户端

clientcontext.disconnect

 

5. 解码器基类解码函数添加了pvContext: TObject参数

<加上该参数可以在解码时保存一些状态信息到连接上下文对象>
DIOCP1申明:
function Decode(const inBuf: TBufferLink): TObject;
DIOCP3申明:
function Decode(const inBuf: TBufferLink; pvContext: TObject): TObject;
virtual; abstract;

 

你不需要调用解码器函数, uIocpCentre.pas中是内部调用的解码器函数,  只要把之前解码器的函数申明加上参数就好

[DIOCP3-IocpTask说明书]基于IOCP引擎的多线程任务的投递和回调处理单元

【说明】

IocpTask是基于Iocp引擎的多线程任务投递和处理单元,可以方便的把任务进行投递到IOCP线程进行统一调度和处理,是模仿QDAC-QWorker的处理方式,支持D7以上的版本。

【使用方法】

使用上很简单,下面解释一种比较全面的方法:

procedure PostATask(pvTaskWork:TOnTaskWork;
       pvTaskData:Pointer = nil;
       pvRunInMainThread:Boolean = False;
       pvRunType:TRunInMainThreadType = rtSync);overload;

参数:

   pvTaskWork

       回调函数,procedure(pvTaskRequest: TIocpTaskRequest) of object;  pvTaskRequest可以访问到传入的taskData,strData等信息。

  pvTaskData

      是传入的数据,为指针类型,可以是任何的数据,在回调函数中可以通过pvTaskRequest对象进行获取得到。

pvRunInMainThread:

      回调函数是否在主线程中运行,考虑到一些需要访问主线程界面的任务,需要在主线程中运行。

pvRunType:

     同步的方式,支持两种,一种是rtSync是使用线程的同步模式,rtPostMessage,使用消息 + Event等待的模式。消息模式考虑到dll中同步方法无法使用时可以采取的同步方式。

【注意事项】

     主窗体销毁时,很多资源都已经被销毁,如果此时有投递需要主线程处理的任务,可能会导致主线程挂起,而整个进程无法结束的情况。

复制代码

destructor TfrmMain.Destroy;
begin
  FLogTask.PostATask(onLogMsg, 'abcd', True, rtPostMessage);
  Sleep(100);
  FLogTask.Active := false;
  FLogTask.Free;
  inherited Destroy;
end;

复制代码

上面的代码会导致程序无法退出,FLogTask.PostATask(onLogMsg, ‘abcd’, True, rtPostMessage); 是投递到主线程执行的任务,这个时候主窗体正在销毁,无法相应PostMessage的消息(Synchronize方式也是一样会堵塞)导致FMessageEvent会一直等待下去,所以需要注意的是在主窗体销毁的时候不要进行主线程任务的投递。可以在主窗体的destroy中,可以禁止相应投递任务, 在主窗体析构函数的开始,设置Enable := false; 这样iocpTask不会再处理任何的任务,注意是析构函数的第一句,不是窗体的FormDestory事件。

复制代码

destructor TfrmMain.Destroy;
begin
  FLogTask.Enable := false;
  iocpTaskManager.Enable := False;

  .....

  inherited Destroy;
end;

复制代码

[DIOCP3-说明书] 关于DEMO的编译

总有些朋友问我,关于DEMO编译的一些问题,每次都回答大概都差不多,我想还是写篇说明书给大家,关于DEMO编译的步骤。

【环境设定】

1.将DIOCP3\source路径添加到Delphi的搜索路径,[tool->options->Environment Options->Delphi Options –> Libaray –>Libaray Path]

2.打开samples\下面的工程可以进行编译

【常见问题】

1.XE2以上版本,有时候提示一些vcl的单元找不到。

image

解决方法:

工程添加vcl域

image

2. XE2以下版本提示找不到System.actions;

image

解决方法

直接删除即可(一些DEMO在XE5和XE6上面完成的,这个是自动添加的,低版本没有,可以直接删除)

3. D7 下面提示, Application.MainFormOnTaskbar := True;编译不过去

image

解决方法:

直接删除即可,D7不认识这段代码,这是2007以上版本自动生成的。