mirror of
https://github.com/EiffelSoftware/eiffel-org.git
synced 2025-12-06 06:42:03 +01:00
Author:halw
Date:2008-12-03T22:51:35.000000Z git-svn-id: https://svn.eiffel.com/eiffel-org/trunk@116 abb3cda0-5349-4a8f-a601-0c33ac3a8c38
This commit is contained in:
@@ -8,40 +8,43 @@ As before, we have a client and a server class, still called <code>OUR_CLIENT</c
|
||||
|
||||
===A client and a server on the same machine===
|
||||
|
||||
{{note|The example classes discussed in this section appear in the subdirectory ''same_mach ''of the example directory }}
|
||||
{{note|The example classes discussed in this section appear in the subdirectory ''same_mach'' of the example directory }}
|
||||
|
||||
First, let us assume that the client and server run on the same machine, so that we will use the UNIX_ versions of the classes (the next example will be multi-machine). The communication protocol is also the same as before: the client sends a list of strings, the server returns it extended.
|
||||
|
||||
Here we will create and manipulate sockets directly. For that reason, both classes inherit from the EiffelNet class <code>SOCKET_RESOURCES</code> which introduces a number of constants and other useful socket-related features.
|
||||
|
||||
The two sockets must be able to refer to a common address. For a communication within a single machine, as noted, this address is a path name, again ''/tmp/here'' for this example. The address will be an argument of the creation procedure used to obtain a socket ''soc1 ''on either side:
|
||||
The two sockets must be able to refer to a common address. For a communication within a single machine, as noted, this address is a path name, again ''/tmp/here'' for this example. The address will be an argument of the creation procedure used to obtain a socket <eiffel>soc1</eiffel> on either side:
|
||||
<code>
|
||||
create soc1.make_client ("/tmp/here")
|
||||
create soc1.make_server ("/tmp/here")
|
||||
create soc1.make_client ("/tmp/here")
|
||||
create soc1.make_server ("/tmp/here")
|
||||
</code>
|
||||
The ''make_'' procedures take care of all the hassles of establishing a socket for a client or a server: creating an address object, setting it to the given path name, binding the socket to the address, and in the client case establishing the connection. For finer control, these procedures are still available: you can create a bare socket by using the basic creation procedure ''make'' (rather than the more sophisticated ''make_client ''and ''make_server''), then create a separate address object, associate the two, and call the ''bind'' and ''connect'' procedures.
|
||||
The <eiffel>make_</eiffel> procedures take care of all the hassles of establishing a socket for a client or a server: creating an address object, setting it to the given path name, binding the socket to the address, and in the client case establishing the connection. For finer control, these procedures are still available: you can create a bare socket by using the basic creation procedure <eiffel>make</eiffel> (rather than the more sophisticated <eiffel>make_client</eiffel>
|
||||
and <eiffel>make_server</eiffel>), then create a separate address object, associate the two, and call the <eiffel>bind</eiffel> and <eiffel>connect</eiffel> procedures.
|
||||
|
||||
Because communication is bidirectional, the distinction between client and server is not between who sends and who receives, although here the server only sends messages of acknowledgment. The client is the party that initiates the communication; the server is the party which stands ready to accept the communication. This difference justifies the presence of two creation procedures ''make_client'' and ''make_server ''as illustrated above. To initiate the communication, the client will execute:
|
||||
<code> soc1.connect</code>
|
||||
Because communication is bidirectional, the distinction between client and server is not between who sends and who receives, although here the server only sends messages of acknowledgment. The client is the party that initiates the communication; the server is the party which stands ready to accept the communication. This difference justifies the presence of two creation procedures <eiffel>make_client</eiffel> and <eiffel>make_server</eiffel> as illustrated above. To initiate the communication, the client will execute:
|
||||
<code>
|
||||
soc1.connect</code>
|
||||
|
||||
To make itself ready for the communication, the server will execute:
|
||||
<code> soc1.listen (n)</code>
|
||||
<code>
|
||||
soc1.listen (n)</code>
|
||||
|
||||
where ''n'' is a positive integer indicating the maximum number of client connection attempts that may be queued. The value 5, used in the example, is typical for ''n''.
|
||||
where <eiffel>n</eiffel> is a positive integer indicating the maximum number of client connection attempts that may be queued. The value 5, used in the example, is typical for <eiffel>n</eiffel>.
|
||||
|
||||
When you use the <code>_SERVER </code>classes of the predefined level, as in the earlier example, 5 is indeed the default; you can change the value to a positive integer ''n'' through the call ''set_queued ''( ''n'').
|
||||
When you use the <code>_SERVER </code>classes of the predefined level, as in the earlier example, 5 is indeed the default; you can change the value to a positive integer <eiffel>n</eiffel> through the call <eiffel>set_queued (n)</eiffel> .
|
||||
|
||||
Whenever the server needs to exchange objects with one of the clients, it obtains access to the socket through the following sequence:
|
||||
<code>
|
||||
soc1.accept
|
||||
soc2 := soc1.accepted
|
||||
... Storage and retrieval operations using soc2 (not soc1) ...
|
||||
soc2.close
|
||||
|
||||
soc1.accept
|
||||
soc2 := soc1.accepted
|
||||
... Storage and retrieval operations using soc2 (not soc1) ...
|
||||
soc2.close
|
||||
</code>
|
||||
|
||||
Procedure ''accept ''ensures synchronization with the client. When communication is established, ''accept'' creates a new socket which will be accessible through attribute ''accepted'', whose value is here assigned to the local entity ''soc2''. To receive objects, the server will use operations of the form introduced earlier ( [[An overview of EiffelNet Mechanisms|An overview of EiffelNet, sending and receiving object structures]] ):
|
||||
<code> struct ?= soc2.retrieved</code>
|
||||
<code>
|
||||
struct ?= soc2.retrieved</code>
|
||||
|
||||
applying to ''soc2'', not ''soc1''; this makes ''soc1 ''available to accept connections with other clients, a fundamental feature of client-server mechanisms.
|
||||
|
||||
@@ -51,185 +54,188 @@ At the end of the processing it is necessary to close the original socket ''soc1
|
||||
|
||||
Here is the server class based on these principles. The actual processing has been put aside in a procedure ''process''.
|
||||
<code>
|
||||
class
|
||||
OUR_SERVER
|
||||
inherit
|
||||
class
|
||||
OUR_SERVER
|
||||
inherit
|
||||
|
||||
SOCKET_RESOURCES
|
||||
SOCKET_RESOURCES
|
||||
|
||||
STORABLE
|
||||
STORABLE
|
||||
|
||||
creation
|
||||
make
|
||||
creation
|
||||
make
|
||||
|
||||
feature
|
||||
feature
|
||||
|
||||
soc1, soc2: UNIX_STREAM_SOCKET
|
||||
soc1, soc2: UNIX_STREAM_SOCKET
|
||||
|
||||
make
|
||||
-- Accept communication with client and exchange messages
|
||||
local
|
||||
count: INTEGER
|
||||
do
|
||||
create soc1.make_server ("/tmp/here")
|
||||
from
|
||||
soc1listen (5)
|
||||
count := 0
|
||||
until
|
||||
count = 3
|
||||
loop
|
||||
process -- See below
|
||||
count := count + 1
|
||||
end
|
||||
soc1.cleanup
|
||||
rescue
|
||||
soc1.cleanup
|
||||
end
|
||||
make
|
||||
-- Accept communication with client and exchange messages
|
||||
local
|
||||
count: INTEGER
|
||||
do
|
||||
create soc1.make_server ("/tmp/here")
|
||||
from
|
||||
soc1listen (5)
|
||||
count := 0
|
||||
until
|
||||
count = 3
|
||||
loop
|
||||
process -- See below
|
||||
count := count + 1
|
||||
end
|
||||
soc1.cleanup
|
||||
rescue
|
||||
soc1.cleanup
|
||||
end
|
||||
|
||||
process
|
||||
-- Receive a message, extend it, and send it back
|
||||
local
|
||||
our_new_list: OUR_MESSAGE
|
||||
do
|
||||
soc1.accept
|
||||
soc2 ?= soc1.accepted
|
||||
our_new_list ?= retrieved (soc2)
|
||||
from
|
||||
our_new_list.start
|
||||
until
|
||||
our_new_list.after
|
||||
loop
|
||||
io.putstring (our_new_list.item)
|
||||
io.new_line
|
||||
our_new_list.forth
|
||||
end
|
||||
our_new_list.extend ("%N I'm back.%N")
|
||||
our_new_list.general_store (soc2)
|
||||
soc2.close
|
||||
end
|
||||
process
|
||||
-- Receive a message, extend it, and send it back
|
||||
local
|
||||
our_new_list: OUR_MESSAGE
|
||||
do
|
||||
soc1.accept
|
||||
soc2 ?= soc1.accepted
|
||||
our_new_list ?= retrieved (soc2)
|
||||
from
|
||||
our_new_list.start
|
||||
until
|
||||
our_new_list.after
|
||||
loop
|
||||
io.putstring (our_new_list.item)
|
||||
io.new_line
|
||||
our_new_list.forth
|
||||
end
|
||||
our_new_list.extend ("%N I'm back.%N")
|
||||
our_new_list.general_store (soc2)
|
||||
soc2.close
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
Note that at the end the server should not only closes the original socket ''soc1 ''but also unlinks it. It is recommended to have a Rescue clause which, as here, ensures that the socket will be closed and unlinked if the system terminates abnormally before its term.
|
||||
Note that at the end the server should not only closes the original socket <eiffel>soc1</eiffel> but also unlinks it. It is recommended to have a Rescue clause which, as here, ensures that the socket will be closed and unlinked if the system terminates abnormally before its term.
|
||||
Here now is the client class:
|
||||
<code>
|
||||
class
|
||||
OUR_CLIENT
|
||||
class
|
||||
OUR_CLIENT
|
||||
|
||||
inherit
|
||||
inherit
|
||||
|
||||
SOCKET_RESOURCES
|
||||
SOCKET_RESOURCES
|
||||
|
||||
creation
|
||||
make
|
||||
creation
|
||||
make
|
||||
|
||||
feature
|
||||
feature
|
||||
|
||||
soc1: UNIX_STREAM_SOCKET
|
||||
soc1: UNIX_STREAM_SOCKET
|
||||
|
||||
make
|
||||
-- Establish communication with server, and exchange messages
|
||||
do
|
||||
create soc1.make_client ("/tmp/here")
|
||||
soc1.connect
|
||||
process -- See below
|
||||
soc1.cleanup
|
||||
rescue
|
||||
soc1.cleanup
|
||||
end
|
||||
make
|
||||
-- Establish communication with server, and exchange messages
|
||||
do
|
||||
create soc1.make_client ("/tmp/here")
|
||||
soc1.connect
|
||||
process -- See below
|
||||
soc1.cleanup
|
||||
rescue
|
||||
soc1.cleanup
|
||||
end
|
||||
|
||||
process
|
||||
-- Build a message to server, receive answer, build
|
||||
-- modified message from that answer, and print it.
|
||||
local
|
||||
our_list, our_new_list: OUR_MESSAGE
|
||||
do
|
||||
create our_list.make
|
||||
our_list.extend("This"); our_list.extend (" is")
|
||||
our_list.extend (" our"); our_list.extend (" test")
|
||||
our_list.general_store (soc1)
|
||||
our_new_list ?= soc1.retrieved
|
||||
from
|
||||
our_new_list.start
|
||||
until
|
||||
our_new_list.after
|
||||
loop
|
||||
io.putstring (our_new_list.item)
|
||||
our_new_list.forth
|
||||
end
|
||||
io.new_line
|
||||
end
|
||||
process
|
||||
-- Build a message to server, receive answer, build
|
||||
-- modified message from that answer, and print it.
|
||||
local
|
||||
our_list, our_new_list: OUR_MESSAGE
|
||||
do
|
||||
create our_list.make
|
||||
our_list.extend("This"); our_list.extend (" is")
|
||||
our_list.extend (" our"); our_list.extend (" test")
|
||||
our_list.general_store (soc1)
|
||||
our_new_list ?= soc1.retrieved
|
||||
from
|
||||
our_new_list.start
|
||||
until
|
||||
our_new_list.after
|
||||
loop
|
||||
io.putstring (our_new_list.item)
|
||||
our_new_list.forth
|
||||
end
|
||||
io.new_line
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
===Communication between two different machines===
|
||||
{{note|The example classes discussed in this section appear in the subdirectory ''two_mach ''of the example directory }}
|
||||
{{note|The example classes discussed in this section appear in the subdirectory ''two_mach'' of the example directory }}
|
||||
|
||||
|
||||
Let us now assume that the client and the server will run on two separate machines. Instead of UNIX_ sockets, we must now use sockets of type <code>NETWORK_STREAM_SOCKET</code>.
|
||||
|
||||
The available creation procedures are slightly different. The server will be set up so as to listen to clients from any machine; it designates a '''port''', identified by an integer, on which it will listen. The socket creation on the server side is then
|
||||
<code>create soc1.make_server_port (2000)</code>
|
||||
<code>
|
||||
create soc1.make_server_port (2000)</code>
|
||||
|
||||
For the client, the creation will specify two elements of information: the port number and the server. The server argument, a string, identifies the machine used as a server; it may be the host name of that machine, for example ''"serverhost" ''as used in the example; or it can be the machine's internet address, made of a sequence of numbers separated by periods, such as ''"127.0.0.1"''.
|
||||
|
||||
The rest of the classes is as before.
|
||||
<code>
|
||||
class
|
||||
OUR_SERVER
|
||||
class
|
||||
OUR_SERVER
|
||||
|
||||
inherit
|
||||
inherit
|
||||
|
||||
SOCKET_RESOURCES
|
||||
SOCKET_RESOURCES
|
||||
|
||||
STORABLE
|
||||
STORABLE
|
||||
|
||||
creation
|
||||
make
|
||||
creation
|
||||
make
|
||||
|
||||
feature
|
||||
feature
|
||||
|
||||
soc1, soc2: NETWORK_STREAM_SOCKET
|
||||
soc1, soc2: NETWORK_STREAM_SOCKET
|
||||
|
||||
make
|
||||
-- Accept communication with client and exchange messages.
|
||||
do
|
||||
create soc1.make_server_by_port (2000)
|
||||
... The rest as before...
|
||||
end
|
||||
make
|
||||
-- Accept communication with client and exchange messages.
|
||||
do
|
||||
create soc1.make_server_by_port (2000)
|
||||
... The rest as before...
|
||||
end
|
||||
|
||||
process
|
||||
... As before ...
|
||||
end
|
||||
process
|
||||
... As before ...
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
class
|
||||
OUR_CLIENT
|
||||
|
||||
class
|
||||
OUR_CLIENT
|
||||
|
||||
inherit
|
||||
inherit
|
||||
|
||||
SOCKET_RESOURCES
|
||||
SOCKET_RESOURCES
|
||||
|
||||
creation
|
||||
make
|
||||
creation
|
||||
make
|
||||
|
||||
feature
|
||||
feature
|
||||
|
||||
soc1: NETWORK_STREAM_SOCKET
|
||||
soc1: NETWORK_STREAM_SOCKET
|
||||
|
||||
make
|
||||
do
|
||||
create soc1.make_client_by_port (2000, "serverhost")
|
||||
... The rest as before ...
|
||||
end
|
||||
make
|
||||
do
|
||||
create soc1.make_client_by_port (2000, "serverhost")
|
||||
... The rest as before ...
|
||||
end
|
||||
|
||||
process
|
||||
... As before ...
|
||||
end
|
||||
process
|
||||
... As before ...
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
</code>
|
||||
|
||||
|
||||
@@ -239,7 +245,3 @@ The rest of the classes is as before.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user