/* * network server/client for ALSA sequencer * ver.0.1 * * Copyright (C) 1999-2000 Takashi Iwai * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include /* * prototypes */ static void usage(void); static void init_buf(void); static void close_files(void); static void init_seq(char *source, char *dest); static int get_port(char *service); static void sigterm_exit(int sig); static void init_server(int port); static void init_client(char *server, int port); static void do_loop(void); static int copy_local_to_remote(void); static int copy_remote_to_local(int fd); /* * default TCP port number */ #define DEFAULT_PORT 40002 /* * local input buffer */ static char *readbuf; static int max_rdlen; static char *writebuf; static int cur_wrlen, max_wrlen; #define MAX_BUF_EVENTS 200 #define MAX_CONNECTION 10 static snd_seq_t *handle; static int seqfd, sockfd, netfd[MAX_CONNECTION] = {[0 ... MAX_CONNECTION-1] = -1}; static int max_connection; static int cur_connected; static int seq_port; static int server_mode; static int verbose = 0; /* * main routine */ static struct option long_option[] = { {"port", 1, NULL, 'p'}, {"source", 1, NULL, 's'}, {"dest", 1, NULL, 'd'}, {"help", 0, NULL, 'h'}, {"verbose", 0, NULL, 'v'}, {NULL, 0, NULL, 0}, }; int main(int argc, char **argv) { int c; int port = DEFAULT_PORT; char *source = NULL, *dest = NULL; while ((c = getopt_long(argc, argv, "p:s:d:v", long_option, NULL)) != -1) { switch (c) { case 'p': if (isdigit(*optarg)) port = atoi(optarg); else port = get_port(optarg); break; case 's': source = optarg; break; case 'd': dest = optarg; break; case 'v': verbose++; break; default: usage(); exit(1); } } signal(SIGINT, sigterm_exit); signal(SIGTERM, sigterm_exit); init_buf(); init_seq(source, dest); if (optind >= argc) { server_mode = 1; max_connection = MAX_CONNECTION; init_server(port); } else { server_mode = 0; max_connection = 1; init_client(argv[optind], port); } do_loop(); close_files(); return 0; } /* * print usage */ static void usage(void) { fprintf(stderr, "aseqnet - network client/server on ALSA sequencer\n"); fprintf(stderr, " Copyright (C) 1999 Takashi Iwai\n"); fprintf(stderr, "usage:\n"); fprintf(stderr, " server mode: aseqnet [-options]\n"); fprintf(stderr, " client mode: aseqnet [-options] server_host\n"); fprintf(stderr, "options:\n"); fprintf(stderr, " -p,--port # : sepcify TCP port (digit or service name)\n"); fprintf(stderr, " -s,--source addr : read from given addr (client:port)\n"); fprintf(stderr, " -d,--dest addr : write to given addr (client:port)\n"); fprintf(stderr, " -v, --verbose : print verbose messages\n"); } /* * allocate and initialize buffers */ static void init_buf(void) { max_wrlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t); max_rdlen = MAX_BUF_EVENTS * sizeof(snd_seq_event_t); writebuf = malloc(max_wrlen); readbuf = malloc(max_rdlen); if (writebuf == NULL || readbuf == NULL) { fprintf(stderr, "can't malloc\n"); exit(1); } memset(writebuf, 0, max_wrlen); memset(readbuf, 0, max_rdlen); cur_wrlen = 0; } /* * parse command line to client:port * NB: the given string will be broken. */ static int parse_address(snd_seq_t *seq, snd_seq_addr_t *addr, char *arg) { char *p; int client, port; if ((p = strpbrk(arg, ":.")) == NULL) return -1; if ((port = atoi(p + 1)) < 0) return -1; addr->port = port; if (isdigit(*arg)) { client = atoi(arg); if (client < 0) return -1; addr->client = client; } else { /* convert from the name */ snd_seq_client_info_t cinfo; int len; *p = 0; len = strlen(arg); if (len <= 0) return -1; cinfo.client = -1; while (snd_seq_query_next_client(seq, &cinfo) >= 0) { if (! strncmp(cinfo.name, arg, len)) { addr->client = cinfo.client; return 0; } } return -1; /* not found */ } return 0; } /* * close all files */ static void close_files(void) { int i; if (verbose) fprintf(stderr, "closing files..\n"); for (i = 0; i < max_connection; i++) { if (netfd[i] >= 0) close(netfd[i]); } if (sockfd >= 0) close(sockfd); } /* * initialize sequencer */ static void init_seq(char *source, char *dest) { snd_seq_addr_t addr; int err; struct pollfd pfd; if (snd_seq_open(&handle, "hw", SND_SEQ_OPEN_DUPLEX, 0) < 0) { perror("snd_seq_open"); exit(1); } err = snd_seq_poll_descriptors(handle, &pfd, 1, POLLIN | POLLOUT); assert(err == 1); seqfd = pfd.fd; snd_seq_nonblock(handle, 0); /* set client info */ if (server_mode) snd_seq_set_client_name(handle, "Net Server"); else snd_seq_set_client_name(handle, "Net Client"); /* create a port */ seq_port = snd_seq_create_simple_port(handle, "Network", SND_SEQ_PORT_CAP_READ | SND_SEQ_PORT_CAP_WRITE | SND_SEQ_PORT_CAP_SUBS_READ | SND_SEQ_PORT_CAP_SUBS_WRITE, SND_SEQ_PORT_TYPE_MIDI_GENERIC); if (seq_port < 0) { perror("create seq port"); exit(1); } if (verbose) fprintf(stderr, "sequencer opened: %d:%d\n", snd_seq_client_id(handle), seq_port); /* explicit subscriptions */ if (source) { /* read subscription */ if (parse_address(handle, &addr, source) < 0) { fprintf(stderr, "invalid source address %s\n", source); exit(1); } if (snd_seq_connect_from(handle, seq_port, addr.client, addr.port)) { perror("read subscription"); exit(1); } } if (dest) { /* write subscription */ if (parse_address(handle, &addr, dest) < 0) { fprintf(stderr, "invalid destination address %s\n", dest); exit(1); } if (snd_seq_connect_to(handle, seq_port, addr.client, addr.port)) { perror("write subscription"); exit(1); } } } /* * convert from string to TCP port number */ static int get_port(char *service) { struct servent *sp; if ((sp = getservbyname(service, "tcp")) == NULL){ fprintf(stderr, "service '%s' is not found in /etc/services\n", service); return -1; } return sp->s_port; } /* * signal handler */ static void sigterm_exit(int sig) { close_files(); exit(1); } /* * initialize network server */ static void init_server(int port) { int i; int curstate = 1; struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_addr.s_addr = INADDR_ANY; addr.sin_port = htons(port); sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("create socket"); exit(1); } setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)); /* the return value is ignored.. */ if (bind(sockfd, &addr, sizeof(addr)) < 0) { perror("can't bind"); exit(1); } if (listen(sockfd, 5) < 0) { perror("can't listen"); exit(1); } cur_connected = 0; for (i = 0; i < max_connection; i++) netfd[i] = -1; } /* * start connection on server */ static void start_connection(void) { struct sockaddr_in addr; int i; int addr_len; for (i = 0; i < max_connection; i++) { if (netfd[i] < 0) break; } if (i >= max_connection) { fprintf(stderr, "too many connections!\n"); exit(1); } memset(&addr, 0, sizeof(addr)); addr_len = sizeof(addr); netfd[i] = accept(sockfd, (struct sockaddr *)&addr, &addr_len); if (netfd[i] < 0) { perror("accept"); exit(1); } if (verbose) fprintf(stderr, "accepted[%d]\n", netfd[i]); cur_connected++; } /* * initialize network client */ static void init_client(char *server, int port) { struct sockaddr_in addr; struct hostent *host; int curstate = 1; int fd; if ((fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0){ perror("create socket"); exit(1); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &curstate, sizeof(curstate)) < 0) { perror("setsockopt"); exit(1); } if ((host = gethostbyname(server)) == NULL){ fprintf(stderr,"can't get address %s\n", server); exit(1); } addr.sin_port = htons(port); addr.sin_family = AF_INET; memcpy(&addr.sin_addr, host->h_addr, host->h_length); if (connect(fd, &addr, sizeof(addr)) < 0) { perror("connect"); exit(1); } if (verbose) fprintf(stderr, "ok.. connected\n"); netfd[0] = fd; cur_connected = 1; } /* * set file descriptor */ static void set_fd(int fd, fd_set *p, int *width) { FD_SET(fd, p); if (fd >= *width) *width = fd + 1; } /* * event loop */ static void do_loop(void) { fd_set rfd; int i, rc, width; for (;;) { FD_ZERO(&rfd); width = 0; set_fd(seqfd, &rfd, &width); if (server_mode) set_fd(sockfd, &rfd, &width); for (i = 0; i < max_connection; i++) { if (netfd[i] >= 0) set_fd(netfd[i], &rfd, &width); } rc = select(width, &rfd, NULL, NULL, NULL); if (rc <= 0) exit(1); if (server_mode) { if (FD_ISSET(sockfd, &rfd)) start_connection(); } if (FD_ISSET(seqfd, &rfd)) { if (copy_local_to_remote()) break; } for (i = 0; i < max_connection; i++) { if (netfd[i] < 0) continue; if (FD_ISSET(netfd[i], &rfd)) { if (copy_remote_to_local(netfd[i])) { netfd[i] = -1; cur_connected--; if (cur_connected <= 0) return; } } } } } /* * flush write buffer - send data to the socket */ static void flush_writebuf(void) { if (cur_wrlen) { int i; for (i = 0; i < max_connection; i++) { if (netfd[i] >= 0) write(netfd[i], writebuf, cur_wrlen); } cur_wrlen = 0; } } /* * get space from write buffer */ static char *get_writebuf(int len) { char *buf; if (cur_wrlen + len >= max_wrlen) flush_writebuf(); buf = writebuf + cur_wrlen; cur_wrlen += len; return buf; } /* * copy events from sequencer to port(s) */ static int copy_local_to_remote(void) { int rc; snd_seq_event_t *ev; char *buf; while ((rc = snd_seq_event_input(handle, &ev)) >= 0 && ev) { if (ev->type >= SND_SEQ_EVENT_CLIENT_START && ! snd_seq_ev_is_variable_type(ev)) { snd_seq_free_event(ev); continue; } if (snd_seq_ev_is_variable(ev)) { int len; len = sizeof(snd_seq_event_t) + ev->data.ext.len; buf = get_writebuf(len); memcpy(buf, ev, sizeof(snd_seq_event_t)); memcpy(buf + sizeof(snd_seq_event_t), ev->data.ext.ptr, ev->data.ext.len); } else { buf = get_writebuf(sizeof(snd_seq_event_t)); memcpy(buf, ev, sizeof(snd_seq_event_t)); } snd_seq_free_event(ev); } flush_writebuf(); return 0; } /* * copy events from a port to sequencer */ static int copy_remote_to_local(int fd) { int count; char *buf; snd_seq_event_t *ev; count = read(fd, readbuf, MAX_BUF_EVENTS * sizeof(snd_seq_event_t)); buf = readbuf; if (count == 0) { if (verbose) fprintf(stderr, "disconnected\n"); return 1; } while (count > 0) { ev = (snd_seq_event_t*)buf; buf += sizeof(snd_seq_event_t); count -= sizeof(snd_seq_event_t); if (snd_seq_ev_is_variable(ev) && ev->data.ext.len > 0) { ev->data.ext.ptr = buf; buf += ev->data.ext.len; count -= ev->data.ext.len; } snd_seq_ev_set_direct(ev); snd_seq_ev_set_source(ev, seq_port); snd_seq_ev_set_subs(ev); snd_seq_event_output(handle, ev); } snd_seq_drain_output(handle); return 0; }