diff options
author | Steve Bennett <steveb@workware.net.au> | 2010-08-12 12:21:18 +1000 |
---|---|---|
committer | Steve Bennett <steveb@workware.net.au> | 2010-10-15 11:02:50 +1000 |
commit | 0f4cb39eb1ebaf3cc931b450b517a177beb8c05e (patch) | |
tree | 288021a44441ecc21e785641c507eff7dbe707d6 | |
parent | 2077c587650b0ff0332ceaacece892e38e1c436a (diff) | |
download | jimtcl-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.txt | 21 | ||||
-rw-r--r-- | examples/tcp.client | 9 | ||||
-rw-r--r-- | examples/tcp.server | 36 | ||||
-rw-r--r-- | examples/udp.client | 11 | ||||
-rw-r--r-- | examples/udp.server | 25 | ||||
-rw-r--r-- | examples/udp2.client | 13 | ||||
-rw-r--r-- | jim-aio.c | 122 |
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] +} @@ -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" ; } |