From f4fcbe8a8d35f57acb8d5c924ab49009c1381012 Mon Sep 17 00:00:00 2001 From: Martin Peach Date: Wed, 26 Feb 2014 20:25:10 +0000 Subject: [udpsndrcv] can specify its own sending port and outputs bytes received on that port. Thanks to Dennis Engdahl. svn path=/trunk/externals/mrpeach/; revision=17273 --- net/udpsndrcv-help.pd | 73 +++++++++ net/udpsndrcv.c | 398 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 471 insertions(+) create mode 100644 net/udpsndrcv-help.pd create mode 100644 net/udpsndrcv.c diff --git a/net/udpsndrcv-help.pd b/net/udpsndrcv-help.pd new file mode 100644 index 0000000..d2ae4bd --- /dev/null +++ b/net/udpsndrcv-help.pd @@ -0,0 +1,73 @@ +#N canvas 193 98 901 628 12; +#X declare -lib mrpeach; +#X msg 166 354 status; +#X text 18 334 get status on right outlet:; +#N canvas 510 620 494 344 META 0; +#X text 12 155 HELP_PATCH_AUTHORS "pd meta" information added by Jonathan +Wilkes for Pd version 0.42.; +#X text 12 25 LICENSE GPL v2 or later; +#X text 12 5 KEYWORDS control network; +#X text 12 45 DESCRIPTION receive datagrams over a udp connection and +outputs them as raw bytes; +#X text 12 95 OUTLET_0 anything; +#X text 12 135 AUTHOR Martin Peach \, Dennis Engdahl; +#X text 12 75 INLET_0 status port connect disconnect float(s); +#X text 12 115 OUTLET_1 received total; +#X restore 821 567 pd META; +#X obj 208 416 udpsndrcv; +#X msg 197 385 disconnect; +#X msg 105 260 send /info; +#X text -12 3 [udpsndrcv] sends datagrams over udp and receives datagrams +over a udp connection and outputs them as raw bytes. It uses the same +port for both send and receive.; +#X text 168 134 IP address to send to/listen on (required); +#X text 323 447 Total bytes received.; +#X text 242 544 Data received.; +#X text -3 60 This was cadged together by Dennis Engdahl (engdahl@snowcrest.net) +to allow communication with a Behringer X32. The program below demonstrates +this.; +#X obj 105 293 packOSC; +#X obj 238 481 unpackOSC; +#X text 169 149 Port number to send to (required); +#X text 31 132 Creation Arguments:; +#X obj 490 78 import mrpeach; +#X text 202 176 (Must be unique among instances - see below); +#X msg 25 213 connect 192.168.1.101 10023 10023; +#X text 584 598 Dennis Engdahl \, Martin Peach 20104/02/26; +#X obj 268 447 print a; +#X obj 238 518 print Instance_1; +#X obj 208 455 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X msg 496 354 status; +#X text 348 334 get status on right outlet:; +#X obj 538 416 udpsndrcv; +#X msg 527 385 disconnect; +#X msg 435 260 send /info; +#X text 653 447 Total bytes received.; +#X text 572 544 Data received.; +#X obj 435 293 packOSC; +#X obj 568 481 unpackOSC; +#X obj 538 455 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0 +1; +#X msg 355 213 connect 192.168.1.101 10023 10024; +#X obj 598 447 print b; +#X obj 568 518 print Instance_2; +#X text 168 163 Port number to send from and listen on (required); +#X connect 0 0 3 0; +#X connect 3 0 21 0; +#X connect 3 1 12 0; +#X connect 3 2 19 0; +#X connect 4 0 3 0; +#X connect 5 0 11 0; +#X connect 11 0 3 0; +#X connect 12 0 20 0; +#X connect 17 0 3 0; +#X connect 22 0 24 0; +#X connect 24 0 31 0; +#X connect 24 1 30 0; +#X connect 24 2 33 0; +#X connect 25 0 24 0; +#X connect 26 0 29 0; +#X connect 29 0 24 0; +#X connect 30 0 34 0; +#X connect 32 0 24 0; diff --git a/net/udpsndrcv.c b/net/udpsndrcv.c new file mode 100644 index 0000000..dee0f1a --- /dev/null +++ b/net/udpsndrcv.c @@ -0,0 +1,398 @@ +/* udpsndrcv.c 20140221. Dennis Engdahl did it based on: 20060424. Martin Peach did it based on x_net.c. x_net.c header follows: */ +/* Copyright (c) 1997-1999 Miller Puckette. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* network */ + +#include "m_pd.h" +#include "s_stuff.h" + +#include +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#include +#include +#include +#include // for SIOCGIFCONF +#include // for SIOCGIFCONF +#include +#include +#include +#endif // _WIN32 + +/* support older Pd versions without sys_open(), sys_fopen(), sys_fclose() */ +#if PD_MAJOR_VERSION == 0 && PD_MINOR_VERSION < 44 +#define sys_open open +#define sys_fopen fopen +#define sys_fclose fclose +#endif + +static t_class *udpsndrcv_class; + +#define MAX_UDP_RECEIVE 65536L // longer than data in maximum UDP packet + +typedef struct _udpsndrcv +{ + t_object x_obj; + int x_fd; /* the socket */ + t_outlet *x_msgout; + t_outlet *x_addrout; + long x_total_received; + t_atom x_addrbytes[5]; + t_atom x_msgoutbuf[MAX_UDP_RECEIVE]; + char x_msginbuf[MAX_UDP_RECEIVE]; + char x_addr_name[256]; // a multicast address or 0 +} t_udpsndrcv; + +void udpsndrcv_setup(void); +static void udpsndrcv_free(t_udpsndrcv *x); +static void udpsndrcv_send(t_udpsndrcv *x, t_symbol *s, int argc, t_atom *argv); +static void udpsndrcv_disconnect(t_udpsndrcv *x); +static void udpsndrcv_connect(t_udpsndrcv *x, t_symbol *hostname, t_floatarg fportno, t_floatarg ffromport); +static void udpsndrcv_sock_err(t_udpsndrcv *x, char *err_string); +static void *udpsndrcv_new(void); +static void udpsndrcv_sock_err(t_udpsndrcv *x, char *err_string); +static void udpsndrcv_status(t_udpsndrcv *x); +static void udpsndrcv_read(t_udpsndrcv *x, int sockfd); + +static void *udpsndrcv_new(void) +{ + t_udpsndrcv *x; + int i; + + x = (t_udpsndrcv *)pd_new(udpsndrcv_class); /* if something fails we return 0 instead of x. Is this OK? */ + if (NULL == x) return x; + x->x_addr_name[0] = '\0'; + /* 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 < 5; ++i) + { + x->x_addrbytes[i].a_type = A_FLOAT; + x->x_addrbytes[i].a_w.w_float = 0; + } + + outlet_new(&x->x_obj, &s_float); + x->x_msgout = outlet_new(&x->x_obj, &s_anything); + x->x_addrout = outlet_new(&x->x_obj, &s_anything); + + x->x_fd = -1; + return (x); +} + +static void udpsndrcv_read(t_udpsndrcv *x, int sockfd) +{ + int i, read = 0; + struct sockaddr_in from; + socklen_t fromlen = sizeof(from); + t_atom output_atom; + long addr; + unsigned short port; + + read = recvfrom(sockfd, x->x_msginbuf, MAX_UDP_RECEIVE, 0, (struct sockaddr *)&from, &fromlen); +#ifdef DEBUG + post("udpsndrcv_read: read %lu x->x_fd = %d", + read, x->x_fd); +#endif + /* get the sender's ip */ + addr = ntohl(from.sin_addr.s_addr); + port = ntohs(from.sin_port); + + x->x_addrbytes[0].a_w.w_float = (addr & 0xFF000000)>>24; + x->x_addrbytes[1].a_w.w_float = (addr & 0x0FF0000)>>16; + x->x_addrbytes[2].a_w.w_float = (addr & 0x0FF00)>>8; + x->x_addrbytes[3].a_w.w_float = (addr & 0x0FF); + x->x_addrbytes[4].a_w.w_float = port; + outlet_anything(x->x_addrout, gensym("from"), 5L, x->x_addrbytes); + + if (read < 0) + { + udpsndrcv_sock_err(x, "udpsndrcv_read"); + sys_closesocket(x->x_fd); + return; + } + if (read > 0) + { + for (i = 0; i < read; ++i) + { + /* convert the bytes in the buffer to floats in a list */ + x->x_msgoutbuf[i].a_w.w_float = (float)(unsigned char)x->x_msginbuf[i]; + } + x->x_total_received += read; + SETFLOAT(&output_atom, read); + outlet_anything(x->x_addrout, gensym("received"), 1, &output_atom); + /* send the list out the outlet */ + if (read > 1) outlet_list(x->x_msgout, &s_list, read, x->x_msgoutbuf); + else outlet_float(x->x_msgout, x->x_msgoutbuf[0].a_w.w_float); + } +} + +static void udpsndrcv_connect(t_udpsndrcv *x, t_symbol *hostname, t_floatarg fportno, t_floatarg ffromport) +{ + struct sockaddr_in server; + struct hostent *hp; + int sockfd; + int portno = fportno; + int fromport = ffromport; + + if (x->x_fd >= 0) + { + pd_error(x, "udpsndrcv: already connected"); + return; + } + + /* create a socket */ + sockfd = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef DEBUG + post("udpsndrcv_connect: socket %d port %d fromport %d", sockfd, portno, fromport); +#endif + if (sockfd < 0) + { + udpsndrcv_sock_err(x, "udpsndrcv socket"); + return; + } + + /* assign client port number */ + if (fromport == 0) fromport = portno; + server.sin_family = AF_INET; + server.sin_port = htons((u_short)fromport); + + /* bind the socket to INADDR_ANY and port as specified */ + server.sin_addr.s_addr = INADDR_ANY; + if (bind(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + udpsndrcv_sock_err(x, "udpsndrcv: bind"); + sys_closesocket(sockfd); + return; + } + + /* connect socket using hostname provided in command line */ + hp = gethostbyname(hostname->s_name); + if (hp == 0) + { + post("udpsndrcv: bad host?\n"); + return; + } + memcpy((char *)&server.sin_addr, (char *)hp->h_addr, hp->h_length); + server.sin_port = htons((u_short)portno); + + post("udpsndrcv: connecting to port %d from %d", portno, fromport); + /* try to connect. */ + if (connect(sockfd, (struct sockaddr *) &server, sizeof (server)) < 0) + { + udpsndrcv_sock_err(x, "udpsndrcv connect"); +#ifdef _WIN32 + closesocket(sockfd); +#else + close(sockfd); +#endif + return; + } + x->x_fd = sockfd; + x->x_total_received = 0L; + sys_addpollfn(x->x_fd, (t_fdpollfn)udpsndrcv_read, x); + outlet_float(x->x_obj.ob_outlet, 1); + return; +} + +static void udpsndrcv_sock_err(t_udpsndrcv *x, char *err_string) +{ +/* prints the last error from errno or WSAGetLastError() */ +#ifdef _WIN32 + void *lpMsgBuf; + unsigned long errornumber = WSAGetLastError(); + int len = 0, i; + char *cp; + + if ((len = FormatMessageA((FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS), + NULL, errornumber, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&lpMsgBuf, 0, NULL))) + { + cp = (char *)lpMsgBuf; + for(i = 0; i < len; ++i) + { + if (cp[i] < 0x20) + { /* end string at first weird character */ + cp[i] = 0; + break; + } + } + pd_error(x, "%s: %s (%ld)", err_string, (char *)lpMsgBuf, errornumber); + LocalFree(lpMsgBuf); + } +#else + pd_error(x, "%s: %s (%d)", err_string, strerror(errno), errno); +#endif +} + +static void udpsndrcv_status(t_udpsndrcv *x) +{ + t_atom output_atom; + + SETFLOAT(&output_atom, x->x_total_received); + outlet_anything(x->x_addrout, gensym("total"), 1, &output_atom); +} + +static void udpsndrcv_disconnect(t_udpsndrcv *x) +{ + if (x->x_fd >= 0) + { + post("udpsndrcv: disconnecting."); +#ifdef _WIN32 + closesocket(x->x_fd); +#else + close(x->x_fd); +#endif + sys_rmpollfn(x->x_fd); + x->x_fd = -1; + outlet_float(x->x_obj.ob_outlet, 0); + } +} + +static void udpsndrcv_send(t_udpsndrcv *x, t_symbol *s, int argc, t_atom *argv) +{ +#define BYTE_BUF_LEN 65536 // arbitrary maximum similar to max IP packet size + static char byte_buf[BYTE_BUF_LEN]; + int d; + int i, j; + 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; + char fpath[FILENAME_MAX]; + FILE *fptr; + +#ifdef DEBUG + post("s: %s", s->s_name); + post("argc: %d", argc); +#endif + for (i = j = 0; i < argc; ++i) + { + if (argv[i].a_type == A_FLOAT) + { + f = argv[i].a_w.w_float; + d = (int)f; + e = f - d; + if (e != 0) + { + pd_error(x, "udpsndrcv_send: item %d (%f) is not an integer", i, f); + return; + } + c = (unsigned char)d; + if (c != d) + { + pd_error(x, "udpsndrcv_send: item %d (%f) is not between 0 and 255", i, f); + return; + } +#ifdef DEBUG + post("udpsndrcv_send: argv[%d]: %d", i, c); +#endif + byte_buf[j++] = c; + } + else if (argv[i].a_type == A_SYMBOL) + { + + atom_string(&argv[i], fpath, FILENAME_MAX); +#ifdef DEBUG + post ("udpsndrcv fname: %s", fpath); +#endif + fptr = sys_fopen(fpath, "rb"); + if (fptr == NULL) + { + post("udpsndrcv: unable to open \"%s\"", fpath); + return; + } + rewind(fptr); + while ((d = fgetc(fptr)) != EOF) + { +#ifdef DEBUG + post("udpsndrcv: d is %d", d); +#endif + byte_buf[j++] = (char)(d & 0x0FF); +#ifdef DEBUG + post("udpsndrcv: byte_buf[%d] = %d", j-1, byte_buf[j-1]); +#endif + if (j >= BYTE_BUF_LEN) + { + post ("udpsndrcv: file too long, truncating at %lu", BYTE_BUF_LEN); + break; + } + } + fclose(fptr); + fptr = NULL; + post("udpsndrcv: read \"%s\" length %d byte%s", fpath, j, ((d==1)?"":"s")); + } + else + { + pd_error(x, "udpsndrcv_send: item %d is not a float or a file name", i); + return; + } + } + + length = j; + if ((x->x_fd >= 0) && (length > 0)) + { + for (bp = byte_buf, sent = 0; sent < length;) + { + timebefore = sys_getrealtime(); + result = send(x->x_fd, byte_buf, length-sent, 0); + timeafter = sys_getrealtime(); + late = (timeafter - timebefore > 0.005); + if (late || pleasewarn) + { + if (timeafter > lastwarntime + 2) + { + post("udpsndrcv blocked %d msec", + (int)(1000 * ((timeafter - timebefore) + pleasewarn))); + pleasewarn = 0; + lastwarntime = timeafter; + } + else if (late) pleasewarn += timeafter - timebefore; + } + if (result <= 0) + { + udpsndrcv_sock_err(x, "udpsndrcv send"); + udpsndrcv_disconnect(x); + break; + } + else + { + sent += result; + bp += result; + } + } + } + else pd_error(x, "udpsndrcv: not connected"); +} + +static void udpsndrcv_free(t_udpsndrcv *x) +{ + udpsndrcv_disconnect(x); +} + +void udpsndrcv_setup(void) +{ + udpsndrcv_class = class_new(gensym("udpsndrcv"), (t_newmethod)udpsndrcv_new, (t_method)udpsndrcv_free, sizeof(t_udpsndrcv), 0, 0); + class_addmethod(udpsndrcv_class, (t_method)udpsndrcv_connect, gensym("connect"), A_SYMBOL, A_FLOAT, A_FLOAT, 0); + class_addmethod(udpsndrcv_class, (t_method)udpsndrcv_disconnect, gensym("disconnect"), 0); + class_addmethod(udpsndrcv_class, (t_method)udpsndrcv_send, gensym("send"), A_GIMME, 0); + class_addmethod(udpsndrcv_class, (t_method)udpsndrcv_status, + gensym("status"), 0); + class_addlist(udpsndrcv_class, (t_method)udpsndrcv_send); +} + +/* end udpsndrcv.c*/ -- cgit v1.2.1