【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);增加一个客户端。 |
|