'ping. The server, upon receipt of this request, responds with the symbol
'pong. One time. The client prints the server's response. That's all. First, the two programs must agree on a common port where the server will listen so the client can connect. 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) To test your programs, pick a
"random"port number between 1025 and 65535. The bigger numbers are more barren, so you are less likely to interfere with another service that already exists on your machine. The client is easier to write. It simply uses
TCP-CONNECTto establish a connection to the server. 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) This defines
CLIENTas a procedure of no arguments (so the body doesn't evaluate until we invoke the procedure).
TCP-CONNECTreturns two values (look up
"multiple return values"in HelpDesk). The first is an input port, to which the server writes data, and the second an output port, from which the server reads data. The naming convention used above helps me keep them straight. The WRITE statement writes
'pingto the port being read by the server. Having written the message, the client closes its ports and exits. In the above example, we assume both client and server reside on the same machine (hence the use of the hostname
"localhost"). The client can reside on an entirely different machine, however. The server's definition is slightly more complex. Here's the server: 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) The server must first create a
"listener". The listener is woken up when a network connection comes in on the chosen port.
TCP-ACCEPTaccepts responses queued at the server. If we combine these three code fragments (constants, client and server) and run them in a single Scheme session, we ... can't. There's a problem. If we run the client first, it tries to connect with the server, which isn't yet running, and we get an error saying there's no response from the common port. If we run the server first, it creates a listener, then executes the
TCP-ACCEPTexpression. This blocks on a request before it can continue. But we need it to return control to the prompt so we can start the client. In short, we can't run either one first. There are three ways out of this jam. First, we use two separate copies of Scheme (i.e., separate processes). The first process runs the server. The second one runs the client. Note that each process must have the definition of its procedure and the constant definitions. When run after starting the server, the client will return the value
pong. Second, we can just run client and server on different machines. This is really just a special case of the first solution, but it also lets you experiment with connecting to different machines. To do this, you'd have to edit the value associated with
SERVER-HOSTto be the name of the machine running the server. Third, we can use threads. (Read up about
"threads"in HelpDesk; also see ThreadChapter.) We can thus invoke both the client and server in the same Scheme process by running 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused)
THREADexpects a procedure of no arguments as its first argument, which is exactly what
SERVERis. 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) In both cases,
SERVERexits as soon as it has serviced its request. If you invoke
THREADyou won't notice this. If you run the client and server in two separate processes, you will. That concludes our first example.
TCP-LISTENfails because there is already a listener on that port -- created by this very server!
TOKEN-CLIENTestablishes a fresh connection with the server. Besides being somewhat inefficient, this also means that a different client may connect between two consecutive connections by your client, and may thus grab one or more of the intermediate numbers. So you might see an interaction like 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) (You probably won't for small numbers of tokens, but if you set off two processes each requesting a large number of tokens -- say 1,000 each -- from the same server, you ought to find that they aren't all consecutive.) A related problem is that the client connects to the server each time through its loop. This wastes network resources by repeatedly re-establishing connections. A better solution is for the client and server to have a "dialog": once connected, the server keeps providing the client with tokens until the client has exhausted its demand. In this rewrite, the client sends the server one of two messages:
'more, as long as it needs more numbers, and
'enough, when it no longer needs any more numbers. The server responds appropriately. 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) The
FLUSH-OUTPUTare necessary to make the buffers to be flushed to the network device and transmitted. This program also guarantees that the tokens will be consecutive, because the server does not service a new client until it is done responding to the current one. Another way to make more effective use of a connection and to ensure consecutive tokens is to have your server return several (consecutive) tokens at once. How to package up the tokens? All our examples thusfar have transmitted only symbols and numbers. In fact, however, as with i/o operations on any other port, you may use any readable and writeable datum [NOTE: insert HelpDesk reference here]. As a simple example, here's a rewrite of the token server and client where the client simply informs the server of how many tokens it needs, and the server returns a list of that many tokens. 500 Can't connect to 127.0.0.1:8778 (connect: Connection refused) Notice that the first argument to
WRITEis a loop that computes a sequence of tokens. Both servers in this section come at a price. One long request can tie this server down and make it unable to service other requests. Eventually, the number of pending requests may become too large and new client requests may be denied. (See the second parameter to
TCP-LISTEN.) That concludes our short tour of a ping-pong server.