tcp-server.scm 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. ;; ==========
  2. ;; TCP SERVER
  3. ;; ==========
  4. ;; All this server does is echo the client's messages.
  5. (define-module (tcp-server)
  6. #:export (run-server
  7. echo-message-handler
  8. echo-protocol
  9. make-server-protocol
  10. shutdown-client-connection))
  11. (use-modules (rnrs bytevectors)
  12. (networking-lib helpers)
  13. (ice-9 threads)
  14. (ice-9 textual-ports)
  15. (ice-9 binary-ports)
  16. (json))
  17. #;(define receive-buffer (make-bytevector 1024))
  18. (define (create-server-socket port)
  19. (let ([sock
  20. ;; create TCP socket
  21. (socket PF_INET SOCK_STREAM (protoent:proto (getprotobyname "TCP")))]
  22. [backlog-of-connection-requests
  23. ;; allow at maximum n incoming connections waiting to be accepted at the same time
  24. 10])
  25. (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
  26. ;; bind to address and port
  27. ;; accept from any incoming address of AF_INET
  28. (bind sock (make-socket-address AF_INET INADDR_ANY port))
  29. ;; make the server listen for incoming connections
  30. (listen sock backlog-of-connection-requests)
  31. sock))
  32. ;; What to do with a new connection?
  33. (define (handle-new-connection client-connection protocol)
  34. (let* ([client-details (cdr client-connection)]
  35. [in-out-sock (car client-connection)])
  36. (display
  37. (simple-format #f
  38. "INFO: Got new client connection: ~S\n"
  39. client-details))
  40. (display
  41. (simple-format #f
  42. "INFO: Client address: ~S\n"
  43. (gethostbyaddr (sockaddr:addr client-details))))
  44. ;; say hello to the client
  45. #;(put-string in-out-sock "Hello from server!\n")
  46. ;; let the protocol handle the rest
  47. (protocol client-connection)))
  48. (define (echo-message-handler client-connection message)
  49. "This could be a user supplied procedure."
  50. (let ([in-out-sock (car client-connection)])
  51. (display (simple-format #f "RECEIVED: ~s\n" message))
  52. ;; We need to add a newline character again,
  53. ;; because get-line removes it from the received message.
  54. ;; (get-line's line separator or delimiter is the #\newline,
  55. ;; so it does not think of that as part of the line.)
  56. (let ([answer-message (string-append message "\n")])
  57. (put-string in-out-sock (string-append message "\n"))
  58. (force-output in-out-sock))
  59. (display (simple-format #f "sent message: ~s\n" message))))
  60. (define (shutdown-client-connection client-connection)
  61. (display "shutting down connection wit client ...\n")
  62. (close client-connection)
  63. (display "connection wit client shut down.\n"))
  64. (define (close-connection in-out-sock)
  65. (display (simple-format #f "~s\n" "EOF received. Closing connection ..."))
  66. (close in-out-sock)
  67. (display (simple-format #f "~s\n" "Connection closed.")))
  68. (define* (run-server port #:key (protocol echo-protocol))
  69. (define in-out-sock (create-server-socket port))
  70. (display (simple-format #f "SO_REUSEADDR: ~s\n" (getsockopt in-out-sock SOL_SOCKET SO_REUSEADDR)))
  71. ;; Make a thread.
  72. ;; This is done to be able to control the server program from the REPL.
  73. ;; If it was done in the main thread, the REPL would not accept any new input.
  74. ;; It would "hand" inside the while true loop.
  75. (call-with-new-thread
  76. ;; make-thread wants a thunk, a lambda to run it.
  77. ;; This is delayed evaluation.
  78. (λ ()
  79. (while #t
  80. ;; Accept new connection in main loop.
  81. ;; Since accept is blocking the main loop, this should not lead to high CPU usage.
  82. (let ([client-connection (accept in-out-sock)])
  83. ;; Handle the interactions with a client in a separate thread.
  84. ;; This way the server should be able to handle multiple connections.
  85. ;; (Not the newest way of architecturing the server.)
  86. (call-with-new-thread
  87. (λ ()
  88. ;; Handle the new connection according to a protocol.
  89. (handle-new-connection client-connection protocol)))))))
  90. ;; return in-out-sock to be able to close it from REPL
  91. in-out-sock)
  92. (define* (make-server-protocol #:key
  93. (port-reader get-line)
  94. (message-handler echo-message-handler)
  95. (eof-handler shutdown-client-connection))
  96. "A protocol is initialized with a new connection. The connection cannot be specified, since it is incomming from the client. It will then handle messages from this connection according to its specification."
  97. (λ (client-connection)
  98. (let* ([client-details (cdr client-connection)]
  99. [in-out-sock (car client-connection)])
  100. ;; Handle infinitely many messages.
  101. (while (not (port-closed? in-out-sock))
  102. ;; Receiving a message is blocking.
  103. ;; It should not lead to a high CPU usage.
  104. (let ([received-data (port-reader in-out-sock)])
  105. (display (simple-format #f "INPROTO: data received: ~s, which is: ~s\n"
  106. received-data
  107. (scm->json-string received-data)))
  108. (cond [(eof-object? received-data)
  109. (eof-handler client-connection)
  110. (break)]
  111. [else
  112. (message-handler client-connection received-data)]))))))
  113. (define echo-protocol (make-server-protocol))