远程调用技术代码追踪之Socket(一)

远程调用技术代码追踪之(socket)
作者:佚名    HACK技术来源:网络    点击数:272    更新时间:2006-8-4
【ChinaBeta.Cn 网络安全】远程调用技术内幕
聊聊远程调用的相应技术。
微软的模型是rpc,  DCOM的封包就是OleVariant型的。经常听到有人说OleVariant的效率不高,其实MIDAS就是采用这种封包的。
有人问方法的调用,秘密都在IAppServer这个接口里。 客户端要想调用服务器端的方法是需要代理DLL,和存根DLL的。具体的可以参考COM原理。进程内的就不说了,因为同一个地址空间,它可以通过指针去访问的。主要说说进程外的实现,这也是我们比较感兴趣的。为什么需要代理DLL和存根DLL,这是由于,不同物理空间的两台机器。对它们来讲,地址是没有用的。  
从A机到B机通过COM传一个字符串,整型,都比较容易。那么对象怎么办呢。  大家知道,传递对象,其实就是传递指针而已。即然地址对两台机器而言,已经失去了意义。那么需要有一种机制把A机的数据包,发到B机,B机能解释,返回一模一样的内存空间出来。
比如: A要调B机器类的一个方法,怎么做?A机器要模访B机器的内存空间。
A就傻BB的把参数压进去。就调用了,对A机而言。是完全透明的。 在DELPHI中,秘密就在tlb中, import type library这一项 , 例:TRDMCardServer = class(TOleServer)  
通过这种方式,不仅可以访问DELPHI写的中间层,而且可以访问其实的中间层。  在SAP应用中,以前同事,就用这种办法引出代理DLL,来访问SAP里面的业务逻辑对象。这个TOleServer对象是干嘛的?这是大家比较关心的问题。其实A机和B机双方使用了COM工厂进行沟通的。
class function CoRDMCardServer.CreateRemote(const MachineName: string): IRDMCardServer;
begin
  Result := CreateRemoteComObject(MachineName, CLASS_RDMCardServer) as IRDMCardServer;
end;

(注册表中)COM组件的localserver32下面正是COM组件的位置。研究一下CreateRemoteComObject这个函数。
CoCreateInstanceEx看到这里,有人可能已经理解了。这个函数是用COM库中的远程调用的API。
 if Ole32 <> 0 then
  begin
    @CoCreateInstanceEx := Windows.GetProcAddress(Ole32, 'CoCreateInstanceEx');
    @CoInitializeEx := Windows.GetProcAddress(Ole32, 'CoInitializeEx');
    @CoAddRefServerProcess := Windows.GetProcAddress(Ole32, 'CoAddRefServerProcess');
    @CoReleaseServerProcess := Windows.GetProcAddress(Ole32, 'CoReleaseServerProcess');
    @CoResumeClassObjects := Windows.GetProcAddress(Ole32, 'CoResumeClassObjects');
    @CoSuspendClassObjects := Windows.GetProcAddress(Ole32, 'CoSuspendClassObjects');
  end;
WINDOW中 ole32.dll中引出的API。
现在已经明白是怎么激活了,这种方式是用DCOM连接的。



现在来看看borland socket server怎么来接收数据,并创建远程数据模块的。

先看scktsvr.dpr中
SocketForm.Initialize(False);
procedure TSocketForm.Initialize(FromService: Boolean);
…  ReadSettings;…..
看看ReadSettings函数:
if CompareText(Sections[i], csSettings) <> 0 then
CreateItem(StrToInt(Sections[i])); //定位这一句


procedure CreateItem(ID: Integer);
  var
    SH: TSocketDispatcher;
  begin
    SH := TSocketDispatcher.Create(nil);  //定位这一句:
    SH.ReadSettings(ID, Reg);
    PortList.Items.AddObject(IntToStr(SH.Port), SH);
    try
      SH.Open;                 //定位这一句,注意:
    except
      on E: Exception do
        raise Exception.CreateResFmt(@SOpenError, [SH.Port, E.Message]);
    end;
  end;
TSocketDispatcher = class(TServerSocket)

由此创建serversocket服务器
constructor TSocketDispatcher.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ServerType := stThreadBlocking;
  OnGetThread := GetThread;
end;
服务器端用线程阻塞模式。

继续往下看:
constructor TServerSocket.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FServerSocket := TServerWinSocket.Create(INVALID_SOCKET);
  InitSocket(FServerSocket);
  FServerSocket.ThreadCacheSize := 10;
end;
创建了一个TserverWinSocket(服务器端)。
接下来调用InitSocket(FServerSocket);这是ServerSocket的祖先类TAbstractSocket的一个方法,传入参数是成员FserverSocket完成的功能是将ServerSocket的事件指针指向TServerWinSocket的事件,使其能处理Socket触发的事件。
最后,设置FServerSocket允许连接的线程数。
procedure TAbstractSocket.InitSocket(Socket: TCustomWinSocket);
begin
  Socket.OnSocketEvent := DoEvent;
  Socket.OnErrorEvent := DoError;
end;
procedure TAbstractSocket.DoEvent(Sender: TObject; Socket: TCustomWinSocket;
  SocketEvent: TSocketEvent);
begin
  Event(Socket, SocketEvent);
end;
里面是调用Event,再看看Event的声明:
procedure Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);virtual; abstract;
这是一个抽象函数,由此可以知道,TAbstractSocket是一个抽象的类,它只是封装了一般的操作,具体地都留到了它的子类中去了,所以它的子类一定覆盖了这个Event方法,而DoEvent中调用的Event实际上就是调用它子类的Event方法,这个就是典型的模板模式。
好,看看它的子类有没有覆盖这个方法,果然在TCustomSocket中看到了这个方法,并实现了它,看看它的源代码:
procedure TCustomSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);
begin
  case SocketEvent of
    seLookup: if Assigned(FOnLookup) then FOnLookup(Self, Socket);
    seConnecting: if Assigned(FOnConnecting) then FOnConnecting(Self, Socket);
    seConnect:
      begin
        FActive := True;
        if Assigned(FOnConnect) then FOnConnect(Self, Socket);
      end;
    seListen:
      begin
        FActive := True;
        if Assigned(FOnListen) then FOnListen(Self, Socket);
      end;
    seDisconnect:
      begin
        FActive := False;
        if Assigned(FOnDisconnect) then FOnDisconnect(Self, Socket);
      end;
    seAccept: if Assigned(FOnAccept) then FOnAccept(Self, Socket);
    seRead: if Assigned(FOnRead) then FOnRead(Self, Socket);
    seWrite: if Assigned(FOnWrite) then FOnWrite(Self, Socket);
  end;
end;
其中FonAccept等这些都是TCusotmSocket的成员,都是TSocketNotifyEvent类型的,TCustomSocket还通过下面这些属性,使该类拥有了这些事件处理的能力:
property OnLookup: TSocketNotifyEvent read FOnLookup write FOnLookup;
    property OnConnecting: TSocketNotifyEvent read FOnConnecting write FOnConnecting;
    property OnConnect: TSocketNotifyEvent read FOnConnect write FOnConnect;
    property OnDisconnect: TSocketNotifyEvent read FOnDisconnect write FOnDisconnect;
    property OnListen: TSocketNotifyEvent read FOnListen write FOnListen;
    property OnAccept: TSocketNotifyEvent read FOnAccept write FOnAccept;
    property OnRead: TSocketNotifyEvent read FOnRead write FOnRead;
property OnWrite: TSocketNotifyEvent read FOnWrite write FOnWrite;
而上面的Event实现,我们可以看到它是根据SocketEvent的类型来调用相应的事件指针的,而它的子类ServerSocket和ClientSocket也拥有了这些事件的处理能力,到这里我们终于明白了,它们的事件是怎么样出来的,但如果想得知这些事件是怎么触发的,还要看这一句:Socket.OnSocketEvent,OnSocketEvent到后面再说,这里先略。

constructor TServerWinSocket.Create(ASocket: TSocket);
begin
  FConnections := TList.Create;
  FActiveThreads := TList.Create;
  FListLock := TCriticalSection.Create;
  inherited Create(ASocket);  //定位这里:
  FAsyncStyles := [asAccept];
end;
在这里,创建了用来管理客户端连接的Fconnections, 代表各个处理客户连接的Socket,
通过这个属性对各个客户连接进行操作. 线程池FactiveThreads, 管理由Connections 数组确定的的客户端连接线程返回当前正在使用的TServerClientThread对象的个数 。TcriticalSection ,Critical Section使得多线程程序中的一个线程能够暂时阻塞所有其他线程尝试使用同一个Critical Section, Critical Section可以用来确保一次只有一个线程正在执行那一块代码。因此,受到Critical Section保护的那块代码应当尽可能的小因为如果使用不当的话它们可能严重影响性能。所以,每块代码都应当使用它们自己的TCriticalSection,而不是重用全程序共享的TCriticalSection。

继续:
constructor TCustomWinSocket.Create(ASocket: TSocket);
begin
  inherited Create;
  Startup;
  FSocketLock := TCriticalSection.Create;
  FASyncStyles := [asRead, asWrite, asConnect, asClose];
  FSocket := ASocket;
  FAddr.sin_family := PF_INET;
  FAddr.sin_addr.s_addr := INADDR_ANY;
  FAddr.sin_port := 0;
  FConnected := FSocket <> INVALID_SOCKET;
end;
这里看到TcustomWinSocket内部也使用了TcriticalSection.
Asocket 的值为:INVALID_SOCKET;


procedure Startup;
var
  ErrorCode: Integer;
begin
  ErrorCode := WSAStartup($0101, WSAData);
  if ErrorCode <> 0 then
    raise ESocketError.CreateResFmt(@sWindowsSocketError,
      [SysErrorMessage(ErrorCode), ErrorCode, 'WSAStartup']);
end;
这个函数是加戴WINSOCKET库(1.0版本),并初始化,地址,端口等信息。


返回OPEN处:
procedure TAbstractSocket.SetActive(Value: Boolean);
begin
  if Value <> FActive then
  begin
    if (csDesigning in ComponentState) or (csLoading in ComponentState) then
      FActive := Value;
    if not (csLoading in ComponentState) then
      DoActivate(Value);
  end;
end;

procedure TCustomServerSocket.DoActivate(Value: Boolean);
begin
  if (Value <> FServerSocket.Connected) and not (csDesigning in ComponentState) then
  begin
    if FServerSocket.Connected then
      FServerSocket.Disconnect(FServerSocket.SocketHandle)
    else FServerSocket.Listen(FHost, FAddress, FService, FPort, SOMAXCONN);
  end;
end;

当Value不等它的成员FServerSocket.Connected时(我们可先认为这个表示当前的监听状态),而如果此时FServerSocket.Connected为True,则表示Value为False,,那么这时要断开连接,调用FServerSocket.Disconnect(FServerSocket.SocketHandle)
很多时候ServerSocket都是调用它的成员FServerSocket来完成操作的,这一点我已经看到了,下面还有更多这样的情况。否则,则Value为True,调用
FServerSocket.Listen(FHost, FAddress, FService, FPort, SOMAXCONN);来开始进行监视。好的,现在我们就要打开FServerSocket的源码,看看它是怎么样完成断开连接和开始连接的了

procedure TServerWinSocket.Listen(var Name, Address, Service: string; Port: Word;
  QueueSize: Integer);
begin
  inherited Listen(Name, Address, Service, Port, QueueSize, ServerType = stThreadBlocking);
  if FConnected and (ServerType = stThreadBlocking) then
    FServerAcceptThread := TServerAcceptThread.Create(False, Self);
end;

这里调用其父类的Listen方法,如果它是阻塞方式的,则还要生成一个线程类。
而BORLAND这里用了阻塞方式。
 
看看到底做了什么:
procedure TCustomWinSocket.Listen(const Name, Address, Service: string; Port: Word;
  QueueSize: Integer; Block: Boolean);
begin
  if FConnected then raise ESocketError.CreateRes(@sCannotListenOnOpen);
  FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
  if FSocket = INVALID_SOCKET then raise ESocketError.CreateRes(@sCannotCreateSocket);
  try
    Event(Self, seLookUp);
    if Block then
    begin
      FAddr := InitSocket(Name, Address, Service, Port, False);
      DoListen(QueueSize);
    end else
      AsyncInitSocket(Name, Address, Service, Port, QueueSize, False);
  except
    Disconnect(FSocket);
    raise;
  end;
end;
原来,创建了一个SOCKET套接字。
FSocket := socket(PF_INET, SOCK_STREAM, IPPROTO_IP);
记得上面Fsockeet曾被赋过值吗,是INVALID_SOCKET;,而这里终于被替回了一个可用的套接字了。
Event(Self, seLookUp);
不出所料,调用事件指针了,这时ServerSocket的事件就会触发了,回过头去看看,procedure TCustomWinSocket.Event(Socket: TCustomWinSocket; SocketEvent: TSocketEvent);
begin
  if Assigned(FOnSocketEvent) then FOnSocketEvent(Self, Socket, SocketEvent);
end;

seLookup: if Assigned(FOnLookup) then FOnLookup(Self, Socket);
这里CustomSocket的OnLookup事件发生,但ServerSocket并没有显化它,ClientSocket就有这个事件。
继续:
FAddr := InitSocket(Name, Address, Service, Port, False);
function TCustomWinSocket.InitSocket(const Name, Address, Service: string; Port: Word;
  Client: Boolean): TSockAddrIn;
begin
  Result.sin_family := PF_INET;
  if Name <> '' then
    Result.sin_addr := LookupName(name)
  else if Address <> '' then
    Result.sin_addr.s_addr := inet_addr(PChar(Address))
  else if not Client then
    Result.sin_addr.s_addr := INADDR_ANY
  else raise ESocketError.CreateRes(@sNoAddress);
  if Service <> '' then
    Result.sin_port := htons(LookupService(Service))
  else
    Result.sin_port := htons(Port);
end;

说明:htons(将16位主机字符顺序转换成网络字符顺序)

procedure TCustomWinSocket.DoListen(QueueSize: Integer);
begin
  CheckSocketResult(bind(FSocket, FAddr, SizeOf(FAddr)), 'bind');
  DoSetASyncStyles;
  if QueueSize > SOMAXCONN then QueueSize := SOMAXCONN;
  Event(Self, seListen);
  CheckSocketResult(Winsock.listen(FSocket, QueueSize), 'listen');
  FLookupState := lsIdle;
  FConnected := True;
end;

listen()用来等待参数s 的socket连线。参数backlog指定同时能处理的最大连接要求,如果连接数目达此上限则client端将收到ECONNREFUSED的错误。Listen()并未开始接收连线,只是设置socket为listen模式,真正接收client端连线的是accept()。通常listen()会在socket(),bind()之后调用,接着才调用accept()。
狐狸尾巴终于出来了,这里调用了SOCKET的API 绑定一个地址,并开始侦听。

继续上面的Listen事件
FServerAcceptThread := TServerAcceptThread.Create(False, Self);
winsocket侦听,并创建了一个AcceptThread线程。
多线程TserverAcceptThread来响应客户端的accept.

具体的线程执行代码如下:
procedure TServerWinSocket.Accept(Socket: TSocket);
var
  ClientSocket: TServerClientWinSocket;
  ClientWinSocket: TSocket;
  Addr: TSockAddrIn;
  Len: Integer;
  OldOpenType, NewOpenType: Integer;
begin
  Len := SizeOf(OldOpenType);
  if getsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, PChar(@OldOpenType),
    Len) = 0 then
  try
    if FServerType = stThreadBlocking then
    begin
      NewOpenType := SO_SYNCHRONOUS_NONALERT;
      setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, PChar(@NewOpenType), Len);
    end;
    Len := SizeOf(Addr);
    ClientWinSocket := WinSock.accept(Socket, @Addr, @Len);
    if ClientWinSocket <> INVALID_SOCKET then
    begin
      ClientSocket := GetClientSocket(ClientWinSocket);
      if Assigned(FOnSocketEvent) then
        FOnSocketEvent(Self, ClientSocket, seAccept);
      if FServerType = stThreadBlocking then
      begin
        ClientSocket.ASyncStyles := [];
        GetServerThread(ClientSocket);
      end;
    end;
  finally
    Len := SizeOf(OldOpenType);
    setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_OPENTYPE, PChar(@OldOpenType), Len);
  end;
end;

getsockopt()会将参数s所指定的socket状态返回。

ClientWinSocket := WinSock.accept(Socket, @Addr, @Len);

accept()用来接受参数s的socket连线。参数s的socket必需先经bind()、listen()函数处理过,当有连线进来时accept()会返回一个新的socket处理代码,往后的数据传送与读取就是经由新的socket处理,而原来参数s的socket能继续使用accept()来接受新的连线要求。连线成功时,参数addr所指的结构会被系统填入远程主机的地址数据,参数addrlen为scokaddr的结构长度。

设置与指定套接口相关的属性选项。
setsockopt()用来设置参数s所指定的socket状态。

服务器端侦听客户连接。
然后服务器就就accept等待客户端连接




现在我们来追踪一下TsocketConnection源码分析一下。

大家知道TsocketConnection在open后,就会启动一个TremoteDataModule。为什么呢?
TsocketConnection.Open 看底层是怎么实现的。
procedure TCustomConnection.Open;
begin
  SetConnected(True);
end;

procedure TCustomConnection.SetConnected(Value: Boolean);
….
if Value = GetConnected then Exit;
    if Value then
    begin
      if Assigned(BeforeConnect) then BeforeConnect(Self);
      DoConnect;
      SendConnectEvent(True);
      if Assigned(AfterConnect) then AfterConnect(Self);
end
..
BeforeConnect,和AfterConnect可让由用户在连接前和连接后做些什么事情。

看一下DoConnect;
procedure TSocketConnection.DoConnect;  ->>>>>
procedure TStreamedConnection.DoConnect;

InternalOpen


procedure TStreamedConnection.InternalOpen;  
begin
  if FSupportCallbacks then
  begin
    FTransport := TTransportThread.Create(Handle, CreateTransport);
    FTransport.OnTerminate := TransportTerminated;
    WaitForSingleObject(FTransport.Semaphore, INFINITE);
  end else
  begin
    FTransIntf := CreateTransport;
    FTransIntf.SetConnected(True);
  end;
end;

是否支持回调

function TSocketConnection.CreateTransport: ITransport;
var
  SocketTransport: TSocketTransport;
begin
  if SupportCallbacks then
    if not LoadWinSock2 then raise Exception.CreateRes(@SNoWinSock2);
  if (FAddress = '') and (FHost = '') then
    raise ESocketConnectionError.CreateRes(@SNoAddress);
  SocketTransport := TSocketTransport.Create;
  SocketTransport.Host := FHost;
  SocketTransport.Address := FAddress;
  SocketTransport.Port := FPort;
  SocketTransport.InterceptGUID := InterceptGUID;
  Result := SocketTransport as ITransport;
end;

到这里,发现,原来真正干活的是TsocketTransport这个类。继续往下看。
SocketTransport := TSocketTransport.Create;
里面只是计数器加1。
继续执行的操作, FTransIntf.SetConnected(True);前面只是把一些类的信息初始化,比如IP地址,端口号等。
下面才是最重要的:



procedure TSocketTransport.SetConnected(Value: Boolean);
begin
  if GetConnected = Value then Exit;
  if Value then
  begin
    if (FAddress = '') and (FHost = '') then
      raise ESocketConnectionError.CreateRes(@SNoAddress);
    FClientSocket := TClientSocket.Create(nil);
    FClientSocket.ClientType := ctBlocking;
    FSocket := FClientSocket.Socket;
    FClientSocket.Port := FPort;
    if FAddress <> '' then
      FClientSocket.Address := FAddress else
      FClientSocket.Host := FHost;
    FClientSocket.Open;
  end else
  begin
    if FSocket <> nil then FSocket.Close;
    FSocket := nil;
    FreeAndNil(FClientSocket);
    if FEvent <> 0 then WSACloseEvent(FEvent);
    FEvent := 0;
  end;
end;

分析一下,竟然在内部创建了一个TclientSocket,说明它的底层原原本本用SOCKET通讯来完成的,这里还没有远程创建数据模块。

继续回到前面的
procedure TStreamedConnection.DoConnect;
var
  TempStr: string;
begin
  try
    if ServerGUID <> '' then
      TempStr := ServerGUID else
      TempStr := ServerName;
    if TempStr = '' then
      raise Exception.CreateResFmt(@SServerNameBlank, [Name]);
    InternalOpen;
    SetAppServer(Interpreter.CallCreateObject(TempStr));   ////
  except
    InternalClose;
    raise;
  end;
end;


InternalOpen; 由于和前面的代码相似,所以不去理会,一直跟踪到
procedure TCustomWinSocket.DoOpen;
begin
  DoSetASyncStyles;
  Event(Self, seConnecting);
  CheckSocketResult(WinSock.connect(FSocket, FAddr, SizeOf(FAddr)), 'connect');
  FLookupState := lsIdle;
  if not (asConnect in FAsyncStyles) then
  begin
    FConnected := FSocket <> INVALID_SOCKET;
    Event(Self, seConnect);
  end;
end;

CheckSocketResult(WinSock.connect(FSocket, FAddr, SizeOf(FAddr)), 'connect');
API调用连接服务器。先气撇下SetAppServer,到服务器端跟踪一下。
服务器端得到Tsocket套接字之后
在accept函数里。

ClientWinSocket := WinSock.accept(Socket, @Addr, @Len);
    if ClientWinSocket <> INVALID_SOCKET then
    begin
      ClientSocket := GetClientSocket(ClientWinSocket);
      if Assigned(FOnSocketEvent) then
        FOnSocketEvent(Self, ClientSocket, seAccept);
      if FServerType = stThreadBlocking then
      begin
        ClientSocket.ASyncStyles := [];
        GetServerThread(ClientSocket);
      end;
end;
………………
..
ClientSocket := GetClientSocket(ClientWinSocket);

function TServerWinSocket.GetClientSocket(Socket: TSocket): TServerClientWinSocket;
begin
  Result := nil;
  if Assigned(FOnGetSocket) then FOnGetSocket(Self, Socket, Result);
  if Result = nil then
    Result := TServerClientWinSocket.Create(Socket, Self);
end;


创建了一个TserverClientWinSocket对象。用来管理客户端连接。

constructor TServerClientWinSocket.Create(Socket: TSocket; ServerWinSocket: TServerWinSocket);
begin
  FServerWinSocket := ServerWinSocket;
  if Assigned(FServerWinSocket) then
  begin
    FServerWinSocket.AddClient(Self);
    if FServerWinSocket.AsyncStyles <> [] then
    begin
      OnSocketEvent := FServerWinSocket.ClientEvent;
      OnErrorEvent := FServerWinSocket.ClientError;
    end;
  end;
  inherited Create(Socket);
  if FServerWinSocket.ASyncStyles <> [] then DoSetAsyncStyles;
  if FConnected then Event(Self, seConnect);
end;

FServerWinSocket.AddClient(Self);增加一个客户端。

posted on 2007-01-22 16:59  Peter.zhou  阅读(485)  评论(0)    收藏  举报

导航