The Curl Language provides APIs to use TCP sockets and UDP
sockets. Both kinds of sockets use an address/port pair to
uniquely identify a socket location. For a client to talk to a
server, it must know the address and port that the server is
listening on. The Curl socket API can look up an address, given a
hostname.
Although there are synchronous socket calls for each operation,
you should use them only for testing, because while a socket call
is waiting for the server to respond, the UI of your applet is
unresponsive. The step by step explanations in this section
discuss only the asynchronous calls. These calls send an event
that calls an
EventHandler from the event loop when an
operation has completed, failed, timed out, or been
canceled. Asynchronous operations can be canceled by calling
AsyncWorker.cancel on the
AsyncWorker that is
returned from each asynchronous method.
The IDE documentation provides extended examples that illustrate
the Curl API for TCP and UDP sockets. See
Extended
Examples for information on using extended examples.
The extended examples are in the file:
d:\automated-build-temp\build\win32-atom\docs\default\examples\dguide\sockets.zip
This archive file contains the following applet files:
- socket-http-client.curl illustrates a TCP socket
client, discussed in TCP
Socket Client.
- socket-http-server.curl illustrates a TCP socket
server, discussed in TCP
Socket Server.
- udp-client.curl illustrates a UDP socket client,
discussed in UDP Sockets.
- udp-server.curl illustrates a UDP socket server,
discussed in UDP Sockets.
You must run these example applets with privilege. The following
discussion refers to code in the applet files.
The sample applet socket-http-client.curl shows how to
use either the synchronous or asynchronous TCP socket API to make
an HTTP request to a web server and get back the response.
The sample first gets from the
Url the hostname and port
that identify the HTTP web server that it is supposed to connect
to, and creates a
DataTCPSocket with that
information. This action is performed in the procedure
create-socket-and-command-bytes in the class
SocketHttpRequest. The following line returns the
DataTCPSocket.
{DataTCPSocket remote-name = host, remote-port = port}
If the request succeeds, the applet gets an output stream from the
socket and writes the HTTP request using
OutputStream-of.async-write.
{socket.async-connect
{on e:AsyncConnectSocketEvent do
{if-non-null ex = e.exception then
{cleanup-proc}
{callback ex, null, null, false}
elseif e.canceled? then
{cleanup-proc}
{callback null, null, null, true}
else
set output-stream = {socket.to-OutputStream}
set worker.worker =
{output-stream.async-write
bytes,
{on e:AsyncStreamWriteEvent do
...
}
}
}
}
}
{output-stream.async-write
bytes,
{on e:AsyncStreamWriteEvent do
{if-non-null ex = e.exception then
{cleanup-proc}
{callback ex, null, null, false}
elseif e.canceled? then
{cleanup-proc}
{callback null, null, null, true}
else
set input-stream = {socket.to-InputStream}
set worker.worker =
{input-stream.async-read
{on e:AsyncStreamReadEvent do
...
}
}
}
}
}
The last code fragment shows the event handler passed to
InputStream-of.async-read, which is called when the read
succeeds, fails or is canceled. The applet cleans up the socket
and both of the streams made from the socket, and handles either
the read data or the exception.
This applet only does a single HTTP request on this connection,
and the headers included in the HTTP request told the HTTP web
server to close the connection when it was done, so the applet
reads until is sees the EOF from the server closing the socket. If
this was a client that was going to continue using the connection,
then you would need to either know how much data to read, and pass
in the
n parameter with a byte count, or pass in
partial? = true and parse the data in each
AsyncStreamReadEvent as it was received.
{on e:AsyncStreamReadEvent do
{if-non-null ex = e.exception
then
{cleanup-proc}
{callback ex, null, null, false}
elseif e.canceled? then
{cleanup-proc}
{callback null, null, null, true}
else
{callback
null,
e.data asa ByteArray,
socket.local-address.address-as-String & ":" & socket.local-port,
false
}
{cleanup-proc}
}
}
The sample applet socket-http-server.curl shows how
to use either the synchronous or asynchronous TCP socket API to
implement a simple HTTP 1.0 web server.
The sample first creates the
AcceptorTCPSocket. It
creates it with no local-port, and allows the system to assign
one, but it could request a port if needed. It then calls
AcceptorTCPSocket.bind to bind the socket to a particular local
port which it can then return so that a client can be told where
to connect to. Also
AcceptorTCPSocket.bind makes the
socket start listening for incoming connections,
The following code fragment is from the procedure make-server-socket in the class HttpServer.
def server-socket =
{AcceptorTCPSocket
||-- local-port = 80
}
{server-socket.bind}
{return server-socket,
server-socket.local-port,
"http://localhost:" & server-socket.local-port & "/"
}
{server-socket.async-accept
{on e:AsyncAcceptSocketEvent do
{if e.exception != null or e.canceled? then
{if e.exception != null then
{status-callback "Failed accepting " & e.exception.message}
}
{server-socket.close}
{return}
}
def socket = e.socket
def remote-id =
socket.remote-address.address-as-String & ":" &
socket.remote-port
{status-callback "Connection from " & remote-id}
def input-stream =
{socket.to-InputStream}
def read-worker =
{input-stream.async-read
partial? = true,
append? = true,
{on e:AsyncStreamReadEvent do
...
}
}
}
}
The server must keep reading the HTTP request until it has gotten
the whole request, but there is no way to know how long the
request is except to check the data as it arrives. So the call to
InputStream-of.async-read passes
partial? = true
to indicate that it wants to get
AsyncStreamReadEvents as
the data arrives, and it passes
append? = true to indicate
that all of the data should just be appended to the same
ByteArray. The event handler checks for failures or canceling. If
it did get data, it tries to parse it to see if it has received
the whole HTTP request. If it has not and
AsyncStreamReadEvent.done? is
false, then it just returns
and waits for another
AsyncStreamReadEvent. But if it did
not receive the whole HTTP request and
AsyncStreamReadEvent.done? is
true, then it got an EOF
without getting the whole HTTP request. If it has gotten the whole
HTTP request then it prepares an answer and cancels the
InputStream-of.async-read because it has all of the data that it
needs.
The following code fragment illustrates this process.
def read-worker =
{input-stream.async-read
partial? = true,
append? = true,
{on e:AsyncStreamReadEvent do
{if e.exception != null or e.canceled? then
{if e.exception != null then
{status-callback
remote-id & e.exception.message
}
}
{socket.close}
{input-stream.close}
{return}
}
{status-callback
remote-id & " read " &
(e.data asa ByteArray).size
}
def (http-command, request-path, http-version,
request-headers) =
{HttpServer.parse-request (e.data asa ByteArray)}
{if request-path != null then
{read-worker.cancel}
{status-callback
remote-id & " got command " &
http-command & " " & request-path & " " & http-version
}
def response-data =
{HttpServer.make-response
http-command,
request-path,
http-version,
request-headers
}
{status-callback
remote-id & " writing " &
response-data.size
}
def output-stream =
{socket.to-OutputStream}
{output-stream.async-write
response-data,
{on e:AsyncStreamWriteEvent do
...
}
}
elseif e.done? then
{status-callback
remote-id & " got incomplete command."
}
}
}
}
def output-stream =
{socket.to-OutputStream}
{output-stream.async-write
response-data,
{on e:AsyncStreamWriteEvent do
{if e.exception != null then
{status-callback
remote-id &
e.exception.message
}
elseif e.canceled? then
}
{socket.close}
{input-stream.close}
{output-stream.close}
{status-callback remote-id & " done."}
}
}
UDP sockets allow for sending packets from one UDP socket to any
other one, with no guarantees of reliability, and with limits on
the size of the packets. First create a
UDPSocket. You
can supply a
local-port to either the
UDPSocket.default constructor or provide it to
UDPSocket.bind. If you do not supply a local port, then the
system chooses a port randomly to listen on when you call
UDPSocket.bind. Next you call
UDPSocket.read-packet or
UDPSocket.async-read-packet to read incoming packets, or
call
UDPSocket.write-packet or
UDPSocket.async-write-packet to start writing packets. UDP
sockets are not connected to any particular server or client,
although you can provide a
remote-port and
remote-name to
UDPSocket.default that are used as
the default destination to write packets to, and to filter packets
that are read so that only ones from the specified
remote-name and
remote-port are seen. In any case the address and port are
always returned when a packet is read. When the socket is no
longer needed, call
UDPSocket.close.
The sample applet udp-server.curl shows how to use
both synchronous and asynchronous APIs, to implement a server that
reads a packet, changes everything in it to upper-case letters,
and sends it back to whomever sent it. There is also a client
sample called udp-client.curl that is similar to the
server sample except that it sends a packet to the server and
waits for a response.
The following code fragments are from
udp-server.curl.
As the first fragment illustrates, the sample first creates the
UDPSocket. It creates it with no local-port, and allows
the system to assign one, but it could request a port if
needed. It then calls
UDPSocket.bind to bind the socket
to a particular local port which it can then return so that a
client can be told where to send packets. Also
UDPSocket.bind makes the socket start listening for incoming
packets. A UDP socket that does not need to initially wait for
packets from somewhere else could skip calling
UDPSocket.bind and start by writing a packet.
def socket = {UDPSocket}
{socket.bind}
The next fragment shows how the sample waits for packets to arrive
from wherever else, by calling
UDPSocket.async-read-packet. With the default parameters
UDPSocket.async-read-packet continues calling the
EventHandler with an
AsyncReadPacketSocketEvent for each
incoming packet until it is canceled or a failure occurs. Once the
AsyncReadPacketSocketEvent is received, the code checks
to see if there was a failure or if it was canceled, and if not,
it does whatever it needs to do to the packet data, and sends a
response to the address and port that sent the packet. It does
this by adding the packet, address and port to an array of those
that are ready to be sent, and then calls the code that will make
sure that a packet from that array is in the process of being
sent.
def read-worker =
{socket.async-read-packet
{on e:AsyncReadPacketSocketEvent do
{if e.exception != null or e.canceled? then
{socket.close}
else
def packet = {non-null e.packet-data}
{UDPServer.upcase-packet packet}
{packet-array.append packet}
{address-array.append {non-null e.remote-address}}
{port-array.append e.remote-port}
{status
e.remote-address.address-as-String & ":" &
e.remote-port & " read " & packet.size & " bytes"
}
{write-proc}
}
}
}
Finally, we have the code that sees if it needs to start writing
one of the packets that is ready to be sent. If there is not one
being sent already, it calls
UDPSocket.async-write-packet
with the packet and the address and port that it needs to be sent
to. When that write completes or fails or is canceled it recurses
to write the next one, or shuts down if there was an error of it
was canceled. While this writing is in progress, additional
incoming packets will also be read with the continuing
UDPSocket.async-read-packet call, processed, and adding to the
list of reply packets to be sent out. Note that with UDP sockets,
packets can get dropped by the various network layers, and may not
reach their destination.
def write-proc =
{proc {}:void
{if packet-array.size > 0 and socket.open? and
write-worker == null
then
def packet-size = packet-array[0].size
def remote-address = address-array[0]
def remote-port = port-array[0]
set write-worker =
{socket.async-write-packet
packet-array[0],
remote-address = remote-address,
remote-port = remote-port,
{on e:AsyncWritePacketSocketEvent do
{status
remote-address.address-as-String & ":" &
remote-port & " wrote " & packet-size & " bytes"
}
set write-worker = null
{if e.exception != null or e.canceled? then
{socket.close}
else
{write-proc}
}
}
}
{packet-array.remove 0}
{address-array.remove 0}
{port-array.remove 0}
}
}
API version 8.0 adds support for IPv6 addresses to the socket, HTTP
and security systems. The older APIs continue to work as they always
have, except that most calls will also accept IPv6 addresses now.
IPv6 addresses can be returned by the operating system or passed in a
String or
Url if the operating system supports IPv6.
An application that wants to do things with IPv6 addresses may need to
use the new APIs, but most applications that simply pass around host names
or Urls will not need to do anything specil.
API version 8.0 extends the
SocketInetAddress
class with new methods and getters than can use both IPv4 and IPv6
addresses depending on which address families the host operating system
supports. Also when possible
DataTCPSocket.start-connect
and
UDPSocket.write-packet try to convert addresses to
which ever address family will work. But other calls will fail when
passed an address that the operating system does not support.
The socket APIs accept IPv6 addresses in the following formats:
- "fe80::1"
- "[fe80::2%1]"
- "[fe80::2%251]"
And the
Url APIs accept the formats that have
'[' ']'
around the address.
Hostnames are more portable than numeric addresses, but
if your application only uses hostnames or
Strings with addresses
in them, it should work without modification for IPv4 and IPv6 servers and
clients, as long as they are properly configured, and they both support
the appropriate address family.
Copyright © 1998-2019 SCSK Corporation.
All rights reserved.
Curl, the Curl logo, Surge, and the Surge logo are trademarks of SCSK Corporation.
that are registered in the United States. Surge
Lab, the Surge Lab logo, and the Surge Lab Visual Layout Editor (VLE)
logo are trademarks of SCSK Corporation.