一点TTcpServer和TTcpClient的心得。如有错误的地方请指出。

时间:2022-07-27 04:36:41
1.对事件的理解
TTcpClient和TTcpServer组件中有一系列事件(OnReceive, OnSend, OnError,OnAccept...)。这些事件是通知你某件事件发生,并没有通知你该干什么的意思。比如,OnSend事件的发生不是通知你应该调用Send??方法,相反,该事件的发生意味着Send??方法已经被调用了。

2.TTcpServer,TServerSocketThread和TClientSocketThread之间的关系。 
    先定义一下“内部”,在这里“内部”指的是的隐藏在你的代码之后的代码,机制。
    首先,从TTcpServer的OnAccept事件响应说起,该事件是在一个客户端的连接被内部接受后发生的。通过debug windows/Threads观察,会发现在进入该事件响应之前,一个新的线程被已经生成,这是内部为这个新的连接生成的一个新线程。
    TTcpServer还有一个事件响应OnGetThread,通过debug调试会发现这个事件是在OnAccept时间之前发生。该事件的有一个传入参数 var ClientSocketThread: TClientSocketThread。在这里很容易想到这个ClientSocketThread对象应该由你创建,然后被内部使用。暂且打住。

    下面先跳到TServerSocketThread和TClientSocketThread看看。

procedure TServerSocketThread.Execute;
var
  T: TClientSocketThread;
begin
  while not Terminated and Assigned(FServerSocket) and FServerSocket.Listening do
  begin
    if FServerSocket.WaitForConnection then
      if not Terminated then
      begin
        T := FetchClientSocketThread;
        if not Assigned(T) then
          T := AddClientSocketThread;
        if Assigned(T) then
          T.Resume;
        Sleep(0);
      end;
  end;
end;
    暂且不管TServerSocketThread.Execute是在什么地方被调用的。先看看它干了些什么。FServerSocket.WaitForConnection有可能被阻塞,不过这没关系,因为反正在这里是循环检测。(有兴趣可以跳到WaitForConnection看看)。当一定条件满足,调用FetchClientSocketThread。大概看看FetchClientSocketThread的代码,它是先看线程缓冲池中是否有可用的线程。如果有就返回这个线程,如果没有就返回空。再回到Execute,如果Fetch***没有找到可用的线程,就调用AddClientSocketThread,再进去看看,

function TServerSocketThread.AddClientSocketThread: TClientSocketThread;
begin
  Result := nil;
  if Assigned(FServerSocket) and (FThreadPool.Count < FThreadCacheSize) then
  begin
    if Assigned(FOnGetThread) then
      FOnGetThread(Self, Result);
    if not Assigned(Result) then
      Result := CreateThread;
    if Assigned(Result) then
      FThreadPool.Add(Result);
  end
end; 

    很显然,AddClientSocketThread先看看自己的OnGetThread事件是否有相应函数,这和我们开始提到的TTcpServer(继承自TCustomIpServer)的OnGetThread事件太像了,那到底这两个事件之间有什么联系呢?用查找的方法会发现(过程就不说了),
procedure TCustomTcpServer.Open;
begin
  inherited Open;

  if Bind then
    if Listen then
      if BlockMode = bmThreadBlocking then
      begin
        GetServerSocketThread;                    //注意!!!!!!!!!!!!
        if Assigned(FServerSocketThread) and FServerSocketThread.Suspended then
          FServerSocketThread.Resume;
      end;
end;

function TCustomTcpServer.GetServerSocketThread: TServerSocketThread;
begin
  if not Assigned(FServerSocketThread) then
    FServerSocketThread := TServerSocketThread.Create(Self);
  if Assigned(FServerSocketThread) then
    FServerSocketThread.OnGetThread := GetThread; //注意!!!!!!!!!!!!
  Result := FServerSocketThread;

procedure TCustomTcpServer.GetThread(Sender: TObject; var ClientSocketThread: TClientSocketThread);
begin
  if Assigned(FOnGetThread) then
    FOnGetThread(Self, ClientSocketThread); //注意!!!!!!!!!!!!
end;

    最后这个FOnGetThread(Self, ClientSocketThread)函数就是我们的TTcpServer的OnGetThread事件响应函数,所以,TServerSocketThread最终会调用我们的线程创建函数。
    再回到TServerSocketThread.AddClientSocketThread,如果因为我们没有响应OnGetThread事件或者我们创建的线程为空,那么TServerSocketThread.CreateThread会被调用,该函数只是简单的创建一个默认属性的线程。
    现在,新线程已经被创建,回到TServerSocketThread.Execute,(T.Resume)新线程会很快得到执行。
    新线程是TClientSocketThread类型的实例,我们再来看看TClientSocketThread.Execute地执行过程,
procedure TClientSocketThread.Execute;
begin
  ThreadObject := Self;
  while not Terminated do
  begin
    if Assigned(FServerSocketThread) and not FServerSocketThread.Terminated and
       Assigned(FServerSocketThread.ServerSocket) then
    begin
      FClientSocket := TCustomIpClient.Create(nil);
      try
        FServerSocketThread.ServerSocket.Accept(FClientSocket);
      finally
        FClientSocket.Free;
        FClientSocket := nil;
      end;
    end;
    if not Terminated then
      Suspend;
  end;
end;
    注意其中的FServerSocketThread.ServerSocket.Accept(FClientSocket),我们再进去看看,
function TCustomTcpServer.Accept(var ClientSocket: TCustomIpClient): Boolean;
var
  sock: TSocket;
  addr: TSockAddr;
  len: Integer;
begin
  Result := False;
  len := sizeof(addr);
  Fillchar(addr, sizeof(addr), 0);
  try
{$IFDEF MSWINDOWS}
    Sock := ErrorCheck(WinSock.accept(FSocket, @addr, @len));
{$ENDIF}
{$IFDEF LINUX}
    Sock := ErrorCheck(Libc.accept(FSocket, @addr, @len));
{$ENDIF}
  except
    Sock := INVALID_SOCKET;
  end;
  if Sock <> INVALID_SOCKET then
  begin
    Result := True;
    ClientSocket.FActive := True;
    ClientSocket.FConnected := True;
    ClientSocket.FSocket := Sock;
    ClientSocket.FDomain := FDomain;
    ClientSocket.SockType := FSockType;
    ClientSocket.FProtocol := FProtocol;
    ClientSocket.FBlockMode := FBlockMode;
    ClientSocket.FRemoteHost := inet_ntoa(addr.sin_addr);
    ClientSocket.FRemotePort := IntToStr(ntohs(addr.sin_port));
    DoAccept(ClientSocket);
  end;
end;
    容易发现,它首先调用windowsAPI accept,接受一个连接(具体干了些什么事,请看帮助),然后,初始化ClientSocket,再然后,再然后调用了关键的DoAccept(ClientSocket),
procedure TCustomTcpServer.DoAccept(ClientSocket: TCustomIpClient);
begin
  if Assigned(FOnAccept) then
    FOnAccept(Self, ClientSocket);
end;
    在DoAccept中,最开始我们提到的TTcpServer的OnAccept事件响应函数被调用。并且把ClientSocket作为参数传入,通过这个参数,我们就可以往socket读,写数据了。
    从TClientSocketThread.Execute到TTcpServer(TCustomIpServer)的DoAccept的事件响应函数,会发现,一旦我们的DoAccept事件响应函数执行完毕,那么该连接对应的整个线程的任务就完成了,根据设置的不同,可能被结束或是其他。
    最后,还剩下一个问题,TServerSocketThread.Execute是在什么地方被调用的?还记得TCustomIpServer.Open方法吗?其中调用了它自己的GetServerSocketThread函数,该函数中会检查TCustomIpServer.FServerSocketThread是否被赋值,如果没有,则创建一个新的TServerSocketThread线程,这也就是为什么,如果在应用程序中包含了TTcpServer组件后,一旦调用TTcpServer.Open方法后就会新增一个线程,这是一个专门用于监听的(TServerSocketThread)线程,它看是否有客户端的连接,如果有,就再创建一个专门用于该连接的(TClientSocketThread)线程。
    现在该看看TTcpServer,TServerSocketThread和TClientSocketThread之间的关系了。
    TTcpServer中有一个属性,它指向一个TServerSocketThread实例,这个实例负责创建,管理,和释放TClientSocketThread实例。一个TServerSocketThread对象可以管理多个TClientSocketThread。

3 个解决方案

#1


是不是D7里的Indy控件啊?

#2


好像是由一点眉目了(为什么我就是联不上)
平时我用惯了VB+Winsock.ocx
改用D7+TcpServer+TcpClient时直接不知道北了
看来要想联上还需要很大的努力才行....(汗!!!)

有一点不太明白,我用D7+TcpClient联VB+Winsock时可以连接上
但是为什么我收到的是乱码??(很有规律的乱码)用Java 联VB没有出现问题。

可不可以给个D7+ TcpServer+TcpClient的例子(越简单越好,主要看怎么收发信息)

#3


看D7自带的例子吧。demos/internet/NetChat
这个例子还比较简单。

#1


是不是D7里的Indy控件啊?

#2


好像是由一点眉目了(为什么我就是联不上)
平时我用惯了VB+Winsock.ocx
改用D7+TcpServer+TcpClient时直接不知道北了
看来要想联上还需要很大的努力才行....(汗!!!)

有一点不太明白,我用D7+TcpClient联VB+Winsock时可以连接上
但是为什么我收到的是乱码??(很有规律的乱码)用Java 联VB没有出现问题。

可不可以给个D7+ TcpServer+TcpClient的例子(越简单越好,主要看怎么收发信息)

#3


看D7自带的例子吧。demos/internet/NetChat
这个例子还比较简单。