TCPServices.Mod 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. (* Aos, Copyright 2001, Pieter Muller, ETH Zurich *)
  2. MODULE TCPServices; (** AUTHOR "pjm"; PURPOSE "Abstract TCP services"; *)
  3. IMPORT KernelLog, IP, TCP, Configuration, Strings;
  4. CONST
  5. Ok* = TCP.Ok;
  6. Trace = TRUE;
  7. TYPE
  8. Service* = OBJECT (** TCP service object - handles opening and closing of connections *)
  9. VAR res: LONGINT; service, client: TCP.Connection; root, agent: Agent; new: NewAgent;
  10. (** Start is called indirectly by OpenService. *)
  11. PROCEDURE &Start*(port: LONGINT; new: NewAgent; VAR res: LONGINT);
  12. BEGIN
  13. NEW(service); service.Open(port, IP.NilAdr, TCP.NilPort, res);
  14. IF res = Ok THEN
  15. NEW(root, NIL, NIL); root.next := NIL;
  16. SELF.new := new
  17. ELSE
  18. service := NIL
  19. END;
  20. IF Trace THEN
  21. KernelLog.Enter; KernelLog.String("Service "); KernelLog.Int(port, 1);
  22. KernelLog.String(" open "); KernelLog.Int(res, 1); KernelLog.Exit
  23. END
  24. END Start;
  25. PROCEDURE Remove(a: Agent);
  26. VAR p: Agent;
  27. BEGIN {EXCLUSIVE}
  28. p := root;
  29. WHILE (p.next # NIL) & (p.next # a) DO p := p.next END;
  30. IF p.next = a THEN p.next := a.next END
  31. END Remove;
  32. PROCEDURE Stop*;
  33. VAR p, c: Agent;
  34. BEGIN (* traversal can run concurrently with Remove and may see removed elements *)
  35. service.Close();
  36. p := root.next;
  37. WHILE p # NIL DO
  38. c := p; p := p.next; (* p.next is modified by Remove *)
  39. c.Stop()
  40. END;
  41. BEGIN {EXCLUSIVE}
  42. AWAIT(root.next = NIL); (* wait for all agents to remove themselves *)
  43. AWAIT(new = NIL) (* wait for service to terminate *)
  44. END
  45. END Stop;
  46. BEGIN {ACTIVE}
  47. IF service # NIL THEN
  48. LOOP
  49. service.Accept(client, res);
  50. IF res # Ok THEN EXIT END;
  51. agent := new(client, SELF);
  52. BEGIN {EXCLUSIVE}
  53. agent.next := root.next; root.next := agent
  54. END
  55. END;
  56. IF Trace THEN
  57. KernelLog.Enter; KernelLog.String("Service "); KernelLog.Int(service.lport, 1);
  58. KernelLog.String(" result "); KernelLog.Int(res, 1); KernelLog.Exit
  59. END
  60. END;
  61. BEGIN {EXCLUSIVE}
  62. new := NIL (* signal to Stop *)
  63. END
  64. END Service;
  65. TYPE
  66. Agent* = OBJECT (** abstract TCP agent object - should be extended with an active body using "client". *)
  67. VAR
  68. client-: TCP.Connection; (** TCP connection to the client *)
  69. next: Agent; s-: Service;
  70. (** Start is called indirectly by the Service object. *)
  71. PROCEDURE &Start*(c: TCP.Connection; s: Service);
  72. VAR str: ARRAY 32 OF CHAR;
  73. BEGIN
  74. SELF.client := c; SELF.s := s;
  75. IF Trace & (c # NIL) THEN
  76. IP.AdrToStr(c.fip, str);
  77. KernelLog.Enter; KernelLog.String("Agent "); KernelLog.Int(c.lport, 1);
  78. KernelLog.String(" connected to "); KernelLog.String(str);
  79. KernelLog.Char(" "); KernelLog.Int(c.fport, 1); KernelLog.Exit
  80. END
  81. END Start;
  82. PROCEDURE Stop; (* called from outside to stop an agent *)
  83. BEGIN
  84. client.Close()
  85. END Stop;
  86. (** Terminate is called by the body of the extended object to terminate itself. *)
  87. PROCEDURE Terminate*;
  88. BEGIN
  89. client.Close();
  90. s.Remove(SELF)
  91. END Terminate;
  92. END Agent;
  93. (** A "factory" procedure for agent extensions. Used by a service object. *)
  94. NewAgent* = PROCEDURE {DELEGATE} (c: TCP.Connection; s: Service): Agent;
  95. END TCPServices.
  96. (**
  97. Notes
  98. This module provides a framework for TCP services utilizing active objects as agents. A Service object is responsible for managing incoming connections from clients. It creates one (active) Agent object instance per client, to provide the actual service.
  99. A user of this module should extend the Agent object with an active body. The body can use the client field to access its client connection. The client field is a TCP connection object with the Send and Receive methods for sending and receiving data. When the connection is closed by the client, the Receive method will return an error code (res # 0). In this case the Agent object must call the Terminate method in its base record, to signal to the Service object that it is terminating.
  100. Because the Service object needs to create arbitrary Agent extension objects, it needs a "factory procedure" to allocate such agent extensions. The factory procedure is passed to the Service object when it is allocated, and it is called by the Service object every time it needs to create a new agent, i.e., every time a new client connection arrives. The factory procedure should allocate the extended object instance, and return it. This is perhaps best illustrated by an example.
  101. The following agent implements the TCP discard service. This service accepts connections, and discards everything that arrives on the connection, until it is closed by the client.
  102. TYPE
  103. DiscardAgent = OBJECT (TCPServices.Agent)
  104. VAR len, res: LONGINT; buf: ARRAY 4096 OF CHAR;
  105. BEGIN {ACTIVE}
  106. REPEAT
  107. client.Receive(buf, 0, LEN(buf), LEN(buf), len, res)
  108. UNTIL res # Ok;
  109. Terminate
  110. END DiscardAgent;
  111. PROCEDURE NewDiscardAgent(c: TCP.Connection; s: TCPServices.Service): TCPServices.Agent;
  112. VAR a: DiscardAgent;
  113. BEGIN
  114. NEW(a, c, s); RETURN a
  115. END NewDiscardAgent;
  116. To open the discard service:
  117. VAR discard: TCPServices.Service;
  118. TCPServices.OpenService(discard, 9, NewDiscardAgent); (* use TCP port 9 *)
  119. This creates a Service object, which waits actively for TCP connections on port 9. Every time a connection arrives, it calls NewDiscardAgent to allocate a DiscardAgent active object. The DiscardAgent accesses the client connection through the client field.
  120. Currently there is no limit to the number of connections that can be accepted by a Service object. A simple denial-of-service attack would be to open many connections to an existing port.
  121. To close the discard service:
  122. TCPServices.CloseService(discard);
  123. *)
  124. (*
  125. to do:
  126. o limit number of client connections
  127. o re-use agents?
  128. o clean up "dead" clients periodically (user-specified timeout?)
  129. *)