aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSteve Bennett <steveb@workware.net.au>2010-08-12 12:21:18 +1000
committerSteve Bennett <steveb@workware.net.au>2010-10-15 11:02:50 +1000
commit0f4cb39eb1ebaf3cc931b450b517a177beb8c05e (patch)
tree288021a44441ecc21e785641c507eff7dbe707d6
parent2077c587650b0ff0332ceaacece892e38e1c436a (diff)
downloadjimtcl-0f4cb39eb1ebaf3cc931b450b517a177beb8c05e.zip
jimtcl-0f4cb39eb1ebaf3cc931b450b517a177beb8c05e.tar.gz
jimtcl-0f4cb39eb1ebaf3cc931b450b517a177beb8c05e.tar.bz2
Make udp sockets useful
Separate socket types: udp client and udp server Allow client sockets to 'connect' to an address Allow server sockets to 'bind' to an address Add support for 'sendto' and 'recvfrom' Add socket client and server examples Document new udp sockets Signed-off-by: Steve Bennett <steveb@workware.net.au>
-rw-r--r--doc/jim_tcl.txt21
-rw-r--r--examples/tcp.client9
-rw-r--r--examples/tcp.server36
-rw-r--r--examples/udp.client11
-rw-r--r--examples/udp.server25
-rw-r--r--examples/udp2.client13
-rw-r--r--jim-aio.c122
7 files changed, 216 insertions, 21 deletions
diff --git a/doc/jim_tcl.txt b/doc/jim_tcl.txt
index c7adfc2..46484fb 100644
--- a/doc/jim_tcl.txt
+++ b/doc/jim_tcl.txt
@@ -3824,6 +3824,18 @@ aio
+$handle *accept*+::
Server socket only: Accept a connection and return stream
++$handle *sendto* 'str ?hostname:?port'+::
+ Sends the string, *str*, to the given address via the socket using sendto(2).
+ This is intended for udp sockets and may give an error or behave in unintended
+ ways for other handle types.
+ Returns the number of bytes written.
+
++$handle *recvfrom* 'maxlen ?addrvar?'+::
+ Receives a message from the handle via recvfrom(2) and returns it.
+ At most *maxlen* bytes are read.
+ If *addrvar* is specified, the sending address of the message is stored in
+ the named variable in the form 'hostname:port'.
+
eventloop: after, vwait
~~~~~~~~~~~~~~~~~~~~~~~
@@ -3867,8 +3879,13 @@ Various socket types may be created.
+*socket stream.server* '?hostname:?port'+::
A TCP socket server (*hostname* defaults to 0.0.0.0).
-+*socket dgram* 'hostname:port'+::
- A UDP socket client.
++*socket dgram* ?'hostname:port'?+::
+ A UDP socket client. If the address is not specified,
+ the client socket will be unbound and 'sendto' must be used
+ to indicated the destination.
+
++*socket dgram.server* 'hostname:port'+::
+ A UDP socket server.
+*socket pipe*+::
A pipe. Note that unlike all other socket types, this command returns
diff --git a/examples/tcp.client b/examples/tcp.client
new file mode 100644
index 0000000..ed8582b
--- /dev/null
+++ b/examples/tcp.client
@@ -0,0 +1,9 @@
+# Example of sending via a connected tcp socket
+
+set s [socket stream 127.0.0.1:20000]
+
+foreach i [range 1 20] {
+ $s puts "1 << $i"
+
+ puts [$s gets]
+}
diff --git a/examples/tcp.server b/examples/tcp.server
new file mode 100644
index 0000000..ef71fe5
--- /dev/null
+++ b/examples/tcp.server
@@ -0,0 +1,36 @@
+# Example of a udp server which sends a response
+
+# Listen on port 20000. No host specified means 0.0.0.0
+set s [socket stream.server 20000]
+
+$s readable {
+ set sock [$s accept]
+
+ # Make this server forking so we can accept multiple
+ # simultaneous connections
+ if {[os.fork] == 0} {
+ $s close
+
+ # Get the request (max 80 chars) - need the source address
+ while {[$sock gets buf] >= 0} {
+ set buf [string trim $buf]
+ puts -nonewline "read '$buf'"
+
+ try {
+ set result "$buf = [expr $buf]"
+ } on error {msg} {
+ set result "Error: $buf => $msg"
+ }
+
+ puts ", sending '$result'"
+
+ # Send the result back to where it came from
+ $sock puts $result
+ $sock flush
+ }
+ }
+
+ $sock close
+}
+
+vwait done
diff --git a/examples/udp.client b/examples/udp.client
new file mode 100644
index 0000000..32dbc02
--- /dev/null
+++ b/examples/udp.client
@@ -0,0 +1,11 @@
+# Example of sending from an unconnected socket
+
+set s [socket dgram]
+
+foreach i [range 1 20] {
+ # Specify the address and port with sendto
+ $s sendto "$i + $i + 10" 127.0.0.1:20000
+
+ # Receive the response - max length of 100
+ puts [$s recvfrom 100]
+}
diff --git a/examples/udp.server b/examples/udp.server
new file mode 100644
index 0000000..c462647
--- /dev/null
+++ b/examples/udp.server
@@ -0,0 +1,25 @@
+# Example of a udp server which sends a response
+
+# Listen on port 20000. No host specified means 0.0.0.0
+set s [socket dgram.server 20000]
+
+# For each request...
+$s readable {
+ # Get the request (max 80 chars) - need the source address
+ set buf [$s recvfrom 80 addr]
+
+ puts -nonewline "read '$buf' from $addr"
+
+ try {
+ set result "$buf = [expr $buf]"
+ } on error {msg} {
+ set result "Error: $buf => $msg"
+ }
+
+ puts ", sending '$result'"
+
+ # Send the result back to where it came from
+ $s sendto $result $addr
+}
+
+vwait done
diff --git a/examples/udp2.client b/examples/udp2.client
new file mode 100644
index 0000000..776dc2c
--- /dev/null
+++ b/examples/udp2.client
@@ -0,0 +1,13 @@
+# Example of sending via a connected udp socket
+
+set s [socket dgram 127.0.0.1:20000]
+
+foreach i [range 1 20] {
+ # Socket is connected, so can just use puts here
+ # But remember to flush to ensure that each message is separate
+ $s puts -nonewline "$i * $i"
+ $s flush
+
+ # Receive the response - max length of 100
+ puts [$s recvfrom 100]
+}
diff --git a/jim-aio.c b/jim-aio.c
index b46019b..04af7e9 100644
--- a/jim-aio.c
+++ b/jim-aio.c
@@ -77,6 +77,7 @@ typedef struct AioFile {
} AioFile;
static int JimAioSubCmdProc(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
+static int JimParseIpAddress(Jim_Interp *interp, const char *hostport, struct sockaddr_in *sa);
static void JimAioSetError(Jim_Interp *interp, Jim_Obj *name)
{
@@ -180,6 +181,44 @@ static int aio_cmd_read(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_OK;
}
+static int aio_cmd_recvfrom(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ char *buf;
+ struct sockaddr_in sa;
+ long len;
+ socklen_t salen = sizeof(sa);
+ int rlen;
+
+ if (Jim_GetLong(interp, argv[0], &len) != JIM_OK) {
+ return JIM_ERR;
+ }
+
+ buf = Jim_Alloc(len + 1);
+
+ rlen = recvfrom(fileno(af->fp), buf, len, 0, (struct sockaddr *)&sa, &salen);
+ if (rlen < 0) {
+ Jim_Free(buf);
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ Jim_SetResult(interp, Jim_NewStringObjNoAlloc(interp, buf, rlen));
+
+ if (argc > 1) {
+ char buf[50];
+
+ inet_ntop(sa.sin_family, &sa.sin_addr, buf, sizeof(buf) - 7);
+ snprintf(buf + strlen(buf), 7, ":%d", ntohs(sa.sin_port));
+
+ if (Jim_SetVariable(interp, argv[1], Jim_NewStringObj(interp, buf, -1)) != JIM_OK) {
+ return JIM_ERR;
+ }
+ }
+
+ return JIM_OK;
+}
+
+
static int aio_cmd_gets(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
AioFile *af = Jim_CmdPrivData(interp);
@@ -264,6 +303,29 @@ static int aio_cmd_puts(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
return JIM_ERR;
}
+static int aio_cmd_sendto(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+ AioFile *af = Jim_CmdPrivData(interp);
+ int wlen;
+ int len;
+ const char *wdata;
+ struct sockaddr_in sa;
+
+ if (JimParseIpAddress(interp, Jim_GetString(argv[1], NULL), &sa) != JIM_OK) {
+ return JIM_ERR;
+ }
+ wdata = Jim_GetString(argv[0], &wlen);
+
+ /* Note that we don't validate the socket type. Rely on sendto() failing if appropriate */
+ len = sendto(fileno(af->fp), wdata, wlen, 0, (struct sockaddr*)&sa, sizeof(sa));
+ if (len < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ Jim_SetResultInt(interp, len);
+ return JIM_OK;
+}
+
static int aio_cmd_flush(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
{
AioFile *af = Jim_CmdPrivData(interp);
@@ -492,6 +554,13 @@ static const jim_subcmd_type command_table[] = {
.maxargs = 2,
.description = "Read and return bytes from the stream. To eof if no len."
},
+ { .cmd = "recvfrom",
+ .args = "len ?addrvar?",
+ .function = aio_cmd_recvfrom,
+ .minargs = 1,
+ .maxargs = 2,
+ .description = "Receive up to 'len' bytes on the socket. Sets 'addrvar' with receive address, if set"
+ },
{ .cmd = "gets",
.args = "?var?",
.function = aio_cmd_gets,
@@ -506,6 +575,13 @@ static const jim_subcmd_type command_table[] = {
.maxargs = 2,
.description = "Write the string, with newline unless -nonewline"
},
+ { .cmd = "sendto",
+ .args = "str address",
+ .function = aio_cmd_sendto,
+ .minargs = 2,
+ .maxargs = 2,
+ .description = "Send 'str' to the given address (dgram only)"
+ },
{ .cmd = "flush",
.function = aio_cmd_flush,
.description = "Flush the stream"
@@ -723,6 +799,7 @@ static int JimAioSockCommand(Jim_Interp *interp, int argc,
"unix",
"unix.server",
"dgram",
+ "dgram.server",
"stream",
"stream.server",
"pipe",
@@ -732,6 +809,7 @@ static int JimAioSockCommand(Jim_Interp *interp, int argc,
SOCK_UNIX,
SOCK_UNIX_SERV,
SOCK_DGRAM_CL,
+ SOCK_DGRAM_SERV,
SOCK_STREAM_CL,
SOCK_STREAM_SERV,
SOCK_STREAM_PIPE,
@@ -753,25 +831,29 @@ wrongargs:
JIM_ERRMSG) != JIM_OK)
return JIM_ERR;
- if (socktype != SOCK_STREAM_PIPE) {
- if (argc == 2) {
- goto wrongargs;
- }
+ if (argc == 3) {
hostportarg = Jim_GetString(argv[2], NULL);
}
+ else if (argc != 2 || (socktype != SOCK_STREAM_PIPE && socktype != SOCK_DGRAM_CL)) {
+ goto wrongargs;
+ }
Jim_SetResultString(interp, "", 0);
+ hdlfmt = "aio.sock%ld" ;
+
switch (socktype) {
case SOCK_DGRAM_CL:
- sock = socket(PF_INET,SOCK_DGRAM,0);
- if (sock < 0) {
- JimAioSetError(interp, NULL);
- return JIM_ERR;
+ if (argc == 2) {
+ /* No address, so an unconnected dgram socket */
+ sock = socket(PF_INET,SOCK_DGRAM,0);
+ if (sock < 0) {
+ JimAioSetError(interp, NULL);
+ return JIM_ERR;
+ }
+ break;
}
- hdlfmt = "aio.sockdgram%ld" ;
- break;
-
+ /* fall through */
case SOCK_STREAM_CL:
{
struct sockaddr_in sa;
@@ -779,7 +861,7 @@ wrongargs:
if (JimParseIpAddress(interp, hostportarg, &sa) != JIM_OK) {
return JIM_ERR;
}
- sock = socket(PF_INET,SOCK_STREAM,0);
+ sock = socket(PF_INET,socktype == SOCK_DGRAM_CL ? SOCK_DGRAM : SOCK_STREAM,0);
if (sock < 0) {
JimAioSetError(interp, NULL);
return JIM_ERR;
@@ -790,11 +872,11 @@ wrongargs:
close(sock);
return JIM_ERR;
}
- hdlfmt = "aio.sockstream%ld" ;
}
break;
case SOCK_STREAM_SERV:
+ case SOCK_DGRAM_SERV:
{
struct sockaddr_in sa;
@@ -802,7 +884,7 @@ wrongargs:
JimAioSetError(interp, argv[2]);
return JIM_ERR;
}
- sock = socket(PF_INET,SOCK_STREAM,0);
+ sock = socket(PF_INET,socktype == SOCK_DGRAM_SERV ? SOCK_DGRAM : SOCK_STREAM,0);
if (sock < 0) {
JimAioSetError(interp, NULL);
return JIM_ERR;
@@ -817,11 +899,13 @@ wrongargs:
close(sock);
return JIM_ERR;
}
- res = listen(sock,5);
- if (res) {
- JimAioSetError(interp, NULL);
- close(sock);
- return JIM_ERR;
+ if (socktype != SOCK_DGRAM_SERV) {
+ res = listen(sock,5);
+ if (res) {
+ JimAioSetError(interp, NULL);
+ close(sock);
+ return JIM_ERR;
+ }
}
hdlfmt = "aio.socksrv%ld" ;
}