From f29e5ba61aebdda221b201e35bb47a4a9c885735 Mon Sep 17 00:00:00 2001 From: Martin Peach Date: Wed, 16 Aug 2006 20:22:22 +0000 Subject: Added the net, osc and sqosc~ directories svn path=/trunk/externals/mrpeach/; revision=5629 --- net/x_net_tcp_server.c | 600 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 600 insertions(+) create mode 100755 net/x_net_tcp_server.c (limited to 'net/x_net_tcp_server.c') diff --git a/net/x_net_tcp_server.c b/net/x_net_tcp_server.c new file mode 100755 index 0000000..4438215 --- /dev/null +++ b/net/x_net_tcp_server.c @@ -0,0 +1,600 @@ +/* x_net_tcpserver.c Martin Peach 20060511 working version 20060512 */ +/* x_net_tcpserver.c is based on netserver: */ +/* -------------------------- netserver ------------------------------------- */ +/* */ +/* A server for bidirectional communication from within Pd. */ +/* Allows to send back data to specific clients connected to the server. */ +/* Written by Olaf Matthes */ +/* Get source at http://www.akustische-kunst.org/puredata/maxlib */ +/* */ +/* This program is free software; you can redistribute it and/or */ +/* modify it under the terms of the GNU General Public License */ +/* as published by the Free Software Foundation; either version 2 */ +/* of the License, or (at your option) any later version. */ +/* */ +/* This program is distributed in the hope that it will be useful, */ +/* but WITHOUT ANY WARRANTY; without even the implied warranty of */ +/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */ +/* GNU General Public License for more details. */ +/* */ +/* You should have received a copy of the GNU General Public License */ +/* along with this program; if not, write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/* */ +/* Based on PureData by Miller Puckette and others. */ +/* */ +/* ---------------------------------------------------------------------------- */ +//define DEBUG + +#include "m_pd.h" +#include "m_imp.h" +#include "s_stuff.h" + +//#include +//#include +//#include +//#include +//#include +//#include +//#include +//#include +#if defined(UNIX) || defined(unix) +#include +#include +#include +#include +#include +#include +#include +#include +#define SOCKET_ERROR -1 +#else +//#include +//#include +#include +#endif + +#define MAX_CONNECT 32 /* maximum number of connections */ +#define INBUFSIZE 65536L /* was 4096: size of receiving data buffer */ +#define MAX_UDP_RECEIVE 65536L /* longer than data in maximum UDP packet */ + +/* ----------------------------- tcpserver ------------------------- */ + +static t_class *tcpserver_class; +static t_binbuf *inbinbuf; +static char objName[] = "tcpserver"; + +typedef void (*t_tcpserver_socketnotifier)(void *x); +typedef void (*t_tcpserver_socketreceivefn)(void *x, t_binbuf *b); + +typedef struct _tcpserver +{ + t_object x_obj; + t_outlet *x_msgout; + t_outlet *x_connectout; + t_outlet *x_sockout; + t_outlet *x_addrout; + t_symbol *x_host[MAX_CONNECT]; + t_int x_fd[MAX_CONNECT]; + u_long x_addr[MAX_CONNECT]; + t_atom x_addrbytes[4]; + t_int x_sock_fd; + t_int x_connectsocket; + t_int x_nconnections; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; +} t_tcpserver; + +typedef struct _tcpserver_socketreceiver +{ + unsigned char *sr_inbuf; + int sr_inhead; + int sr_intail; + void *sr_owner; + t_tcpserver_socketnotifier sr_notifier; + t_tcpserver_socketreceivefn sr_socketreceivefn; +} t_tcpserver_socketreceiver; + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn); +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x); +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd); +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x); +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcp_server_send_bytes(int sockfd, t_tcpserver *x, int argc, t_atom *argv); +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv); +static void tcpserver_notify(t_tcpserver *x); +static void tcpserver_connectpoll(t_tcpserver *x); +static void tcpserver_print(t_tcpserver *x); +static void *tcpserver_new(t_floatarg fportno); +static void tcpserver_free(t_tcpserver *x); +#ifdef MSW +__declspec(dllexport) +#endif +void tcpserver_setup(void); + +static t_tcpserver_socketreceiver *tcpserver_socketreceiver_new(void *owner, t_tcpserver_socketnotifier notifier, + t_tcpserver_socketreceivefn socketreceivefn) +{ + t_tcpserver_socketreceiver *x = (t_tcpserver_socketreceiver *)getbytes(sizeof(*x)); + if (!x) + { + error("%s_socketreceiver: unable to allocate %d bytes", objName, sizeof(*x)); + } + else + { + x->sr_inhead = x->sr_intail = 0; + x->sr_owner = owner; + x->sr_notifier = notifier; + x->sr_socketreceivefn = socketreceivefn; + if (!(x->sr_inbuf = malloc(INBUFSIZE))) + { + freebytes(x, sizeof(*x)); + x = NULL; + error("%s_socketreceiver: unable to allocate %d bytes", objName, INBUFSIZE); + } + } + return (x); +} + +/* this is in a separately called subroutine so that the buffer isn't + sitting on the stack while the messages are getting passed. */ +static int tcpserver_socketreceiver_doread(t_tcpserver_socketreceiver *x) +{ + char messbuf[INBUFSIZE]; + char *bp = messbuf; + int indx, i; + int inhead = x->sr_inhead; + int intail = x->sr_intail; + unsigned char c; + t_tcpserver *y = x->sr_owner; + unsigned char *inbuf = x->sr_inbuf; + + if (intail == inhead) return (0); +#ifdef DEBUG + post ("%s_socketreceiver_doread: intail=%d inhead=%d", objName, intail, inhead); +#endif + + for (indx = intail, i = 0; indx != inhead; indx = (indx+1)&(INBUFSIZE-1), ++i) + { + c = *bp++ = inbuf[indx]; + y->x_msgoutbuf[i].a_w.w_float = (float)c; + } + if (i > 1) outlet_list(y->x_msgout, &s_list, i, y->x_msgoutbuf); + else outlet_float(y->x_msgout, y->x_msgoutbuf[0].a_w.w_float); + + // intail = (indx+1)&(INBUFSIZE-1); + x->sr_inhead = inhead; + x->sr_intail = indx;//intail; + return (1); +} + +static void tcpserver_socketreceiver_read(t_tcpserver_socketreceiver *x, int fd) +{ + char *semi; + int readto = (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1); + int ret, i; + t_tcpserver *y = x->sr_owner; + + y->x_sock_fd = fd; + /* the input buffer might be full. If so, drop the whole thing */ + if (readto == x->sr_inhead) + { + post("%s: dropped message", objName); + x->sr_inhead = x->sr_intail = 0; + readto = INBUFSIZE; + } + else + { + ret = recv(fd, x->sr_inbuf + x->sr_inhead, + readto - x->sr_inhead, 0); + if (ret < 0) + { + sys_sockerror("tcpserver: recv"); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else if (ret == 0) + { + post("%s: connection closed on socket %d", objName, fd); + if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); + sys_rmpollfn(fd); + sys_closesocket(fd); + } + else + { +#ifdef DEBUG + post ("%s_socketreceiver_read: ret = %d", objName, ret); +#endif + x->sr_inhead += ret; + if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0; + /* output client's IP and socket no. */ + for(i = 0; i < y->x_nconnections; i++) /* search for corresponding IP */ + { + if(y->x_fd[i] == y->x_sock_fd) + { +// outlet_symbol(x->x_connectionip, x->x_host[i]); + /* find sender's ip address and output it */ + y->x_addrbytes[0].a_w.w_float = (y->x_addr[i] & 0xFF000000)>>24; + y->x_addrbytes[1].a_w.w_float = (y->x_addr[i] & 0x0FF0000)>>16; + y->x_addrbytes[2].a_w.w_float = (y->x_addr[i] & 0x0FF00)>>8; + y->x_addrbytes[3].a_w.w_float = (y->x_addr[i] & 0x0FF); + outlet_list(y->x_addrout, &s_list, 4L, y->x_addrbytes); + break; + } + } + outlet_float(y->x_sockout, y->x_sock_fd); /* the socket number */ + tcpserver_socketreceiver_doread(x); + } + } +} + +static void tcpserver_socketreceiver_free(t_tcpserver_socketreceiver *x) +{ + free(x->sr_inbuf); + freebytes(x, sizeof(*x)); +} + +/* ---------------- main tcpserver (send) stuff --------------------- */ + +static void tcp_server_send_bytes(int client, t_tcpserver *x, int argc, t_atom *argv) +{ + static char byte_buf[MAX_UDP_RECEIVE];// arbitrary maximum similar to max IP packet size + int i, d; + unsigned char c; + float f, e; + char *bp; + int length, sent; + int result; + static double lastwarntime; + static double pleasewarn; + double timebefore; + double timeafter; + int late; + int sockfd = x->x_fd[client]; + + /* process & send data */ + if(sockfd >= 0) + { + for (i = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; +#ifdef DEBUG + post("%s: argv[%d]: float:%f int:%d delta:%f", objName, i, f, d, e); +#endif + if (e != 0) + { + error("%s: item %d (%f) is not an integer", objName, i, f); + return; + } + if ((d < 0) || (d > 255)) + { + error("%s: item %d (%f) is not between 0 and 255", objName, i, f); + return; + } + c = (unsigned char)d; /* make sure it doesn't become negative; this only matters for post() */ +#ifdef DEBUG + post("%s: argv[%d]: %d", objName, i, c); +#endif + byte_buf[i] = c; + } + else + { + error("%s: item %d is not a float", objName, i); + return; + } + } + length = i; + if (length > 0) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(sockfd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("%s: send blocked %d msec", objName, + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + sys_sockerror("tcpserver: send"); + post("%s: could not send data to client %d", objName, client); + break; + } + else + { + sent += result; + bp += result; + } + } + } + } + else post("%s: not a valid socket number (%d)", objName, sockfd); +} + +/* send message to client using socket number */ +static void tcpserver_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int i, sockfd; + int client = -1; + + if(x->x_nconnections < 0) + { + post("%s: no clients connected", objName); + return; + } + if(argc < 2) + { + post("%s: nothing to send", objName); + return; + } + /* get socket number of connection (first element in list) */ + if(argv[0].a_type == A_FLOAT) + { + sockfd = atom_getfloatarg(0, argc, argv); + for(i = 0; i < x->x_nconnections; i++) /* check if connection exists */ + { + if(x->x_fd[i] == sockfd) + { + client = i; /* the client we're sending to */ + break; + } + } + if(client == -1) + { + post("%s: no connection on socket %d", objName, sockfd); + return; + } + } + else + { + post("%s: no socket specified", objName); + return; + } + tcp_server_send_bytes(client, x, argc-1, &argv[1]); +} + +/* send message to client using client number + note that the client numbers might change in case a client disconnects! */ +/* clients start at 1 but our index starts at 0 */ +static void tcpserver_client_send(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int sockfd, client; + + if(x->x_nconnections < 0) + { + post("%s: no clients connected", objName); + return; + } + if(argc < 2) + { + post("%s: nothing to send", objName); + return; + } + /* get number of client (first element in list) */ + if(argv[0].a_type == A_FLOAT) + client = atom_getfloatarg(0, argc, argv); + else + { + post("%s: no client specified", objName); + return; + } + if (!((client > 0) && (client < MAX_CONNECT))) + { + post("%s: client %d out of range [1..%d]", objName, client, MAX_CONNECT); + return; + } + --client;/* zero based index*/ + tcp_server_send_bytes(client, x, argc-1, &argv[1]); +} + +/* broadcasts a message to all connected clients */ +static void tcpserver_broadcast(t_tcpserver *x, t_symbol *s, int argc, t_atom *argv) +{ + int client; + + /* enumerate through the clients and send each the message */ + for(client = 0; client < x->x_nconnections; client++) /* check if connection exists */ + { + if(x->x_fd[client] >= 0) + { /* socket exists for this client */ + tcp_server_send_bytes(client, x, argc, argv); + break; + } + } +} + +/* ---------------- main tcpserver (receive) stuff --------------------- */ + +static void tcpserver_notify(t_tcpserver *x) +{ + int i, k; + + /* remove connection from list */ + for(i = 0; i < x->x_nconnections; i++) + { + if(x->x_fd[i] == x->x_sock_fd) + { + x->x_nconnections--; + post("%s: \"%s\" removed from list of clients", objName, x->x_host[i]->s_name); + x->x_host[i] = NULL; /* delete entry */ + x->x_fd[i] = -1; + /* rearrange list now: move entries to close the gap */ + for(k = i; k < x->x_nconnections; k++) + { + x->x_host[k] = x->x_host[k + 1]; + x->x_fd[k] = x->x_fd[k + 1]; + } + } + } + outlet_float(x->x_connectout, x->x_nconnections); +} + +static void tcpserver_connectpoll(t_tcpserver *x) +{ + struct sockaddr_in incomer_address; + int sockaddrl = (int) sizeof( struct sockaddr ); + int fd = accept(x->x_connectsocket, (struct sockaddr*)&incomer_address, &sockaddrl); + int i; + + if (fd < 0) post("%s: accept failed", objName); + else + { + t_tcpserver_socketreceiver *y = tcpserver_socketreceiver_new((void *)x, + (t_tcpserver_socketnotifier)tcpserver_notify, NULL);/* MP tcpserver_doit isn't used I think...*/ + if (!y) + { +#ifdef MSW + closesocket(fd); +#else + close(fd); +#endif + return; + } + sys_addpollfn(fd, (t_fdpollfn)tcpserver_socketreceiver_read, y); + x->x_nconnections++; + i = x->x_nconnections - 1; + x->x_host[i] = gensym(inet_ntoa(incomer_address.sin_addr)); + x->x_fd[i] = fd; + post("%s: accepted connection from %s on socket %d", + objName, x->x_host[i]->s_name, x->x_fd[i]); + outlet_float(x->x_connectout, x->x_nconnections); + outlet_float(x->x_sockout, x->x_fd[i]); /* the socket number */ + x->x_addr[i] = ntohl(incomer_address.sin_addr.s_addr); + x->x_addrbytes[0].a_w.w_float = (x->x_addr[i] & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (x->x_addr[i] & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (x->x_addr[i] & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (x->x_addr[i] & 0x0FF); + outlet_list(x->x_addrout, &s_list, 4L, x->x_addrbytes); + } +} + +static void tcpserver_print(t_tcpserver *x) +{ + int i; + + if(x->x_nconnections > 0) + { + post("%s: %d open connections:", objName, x->x_nconnections); + for(i = 0; i < x->x_nconnections; i++) + { + post(" \"%s\" on socket %d", + x->x_host[i]->s_name, x->x_fd[i]); + } + } + else post("%s: no open connections", objName); +} + +static void *tcpserver_new(t_floatarg fportno) +{ + t_tcpserver *x; + int i; + struct sockaddr_in server; + int sockfd, portno = fportno; + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_STREAM, 0); +#ifdef DEBUG + post("%s: receive socket %d", objName, sockfd); +#endif + if (sockfd < 0) + { + sys_sockerror("tcpserver: socket"); + return (0); + } + server.sin_family = AF_INET; + server.sin_addr.s_addr = INADDR_ANY; +#ifdef IRIX + /* this seems to work only in IRIX but is unnecessary in + Linux. Not sure what NT needs in place of this. */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, 0, 0) < 0) + post("setsockopt failed\n"); +#endif + /* assign server port number */ + server.sin_port = htons((u_short)portno); + /* name the socket */ + if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) + { + sys_sockerror("tcpserver: bind"); + sys_closesocket(sockfd); + return (0); + } + x = (t_tcpserver *)pd_new(tcpserver_class); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); /* 1st outlet for received data */ + /* streaming protocol */ + if (listen(sockfd, 5) < 0) + { + sys_sockerror("tcpserver: listen"); + sys_closesocket(sockfd); + sockfd = -1; + } + else + { + sys_addpollfn(sockfd, (t_fdpollfn)tcpserver_connectpoll, x); + x->x_connectout = outlet_new(&x->x_obj, &s_float); /* 2nd outlet for number of connected clients */ + x->x_sockout = outlet_new(&x->x_obj, &s_float); /* 3rd outlet for socket number of current client */ + x->x_addrout = outlet_new(&x->x_obj, &s_list); /* 4th outlet for ip address of current client */ + inbinbuf = binbuf_new(); + } + x->x_connectsocket = sockfd; + x->x_nconnections = 0; + for(i = 0; i < MAX_CONNECT; i++) x->x_fd[i] = -1; + /* prepare to convert the bytes in the buffer to floats in a list */ + for (i = 0; i < MAX_UDP_RECEIVE; ++i) + { + x->x_msgoutbuf[i].a_type = A_FLOAT; + x->x_msgoutbuf[i].a_w.w_float = 0; + } + for (i = 0; i < 4; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + + return (x); +} + +static void tcpserver_free(t_tcpserver *x) +{ + int i; + + for(i = 0; i < x->x_nconnections; i++) + { + sys_rmpollfn(x->x_fd[i]); + sys_closesocket(x->x_fd[i]); + } + if (x->x_connectsocket >= 0) + { + sys_rmpollfn(x->x_connectsocket); + sys_closesocket(x->x_connectsocket); + } + binbuf_free(inbinbuf); +} + +#ifdef MSW +__declspec(dllexport) +#endif +void tcpserver_setup(void) +{ + tcpserver_class = class_new(gensym(objName),(t_newmethod)tcpserver_new, (t_method)tcpserver_free, + sizeof(t_tcpserver), 0, A_DEFFLOAT, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_print, gensym("print"), 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_send, gensym("send"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_client_send, gensym("client"), A_GIMME, 0); + class_addmethod(tcpserver_class, (t_method)tcpserver_broadcast, gensym("broadcast"), A_GIMME, 0); +} -- cgit v1.2.1