# define INCLUDE_FILE_IO # include "dgd.h" # include <sys/time.h> # include <sys/socket.h> # include <netinet/in.h> # include <arpa/inet.h> # include <netdb.h> # include <errno.h> # include "str.h" # include "array.h" # include "object.h" # include "comm.net.h" # define EXT_SIZE 16384 /* size requested when extending a buffer */ struct _connection_ { int fd; /* file descriptor */ struct sockaddr_in sin; /* peer's address */ struct _connection_ *next; /* next in list */ int extended; /* output buffer has been extended */ char host[16]; /* ascii ip number of peer */ char hostlen; /* length of host string */ }; static connection *connections; /* connections array */ static connection *flist; /* list of free connections */ static fd_set rfds; /* read file descriptor bitmap */ static fd_set wfds; /* write file descriptor bitmap */ static fd_set readfds; /* fds selected for reading */ static fd_set writefds; /* fds selected for writing */ static fd_set xxxfds; static fd_set ufds; /* identical to rfds, minus those that are blocked */ static int maxfd; /* largest fd opened yet */ /* * NAME: conn->init() * DESCRIPTION: initialize connections */ void conn_init(maxusers) int maxusers; { register int n; register connection *conn; connections = ALLOC(connection, maxusers); for (n = maxusers, conn = connections; n > 0; --n, conn++) { conn->fd = -1; conn->next = flist; flist = conn; } FD_ZERO(&rfds); FD_ZERO(&wfds); FD_ZERO(&ufds); } /* * NAME: conn->finish() * DESCRIPTION: terminate connections */ void conn_finish() { } /* * NAME: conn->new() * DESCRIPTION: initialize a new connection struct */ static connection *conn_new(fd, sin) int fd; struct sockaddr_in *sin; { register connection *conn; conn = flist; flist = conn->next; conn->fd = fd; memcpy(&conn->sin, sin, sizeof(conn->sin)); strcpy(conn->host, inet_ntoa(sin->sin_addr)); conn->hostlen = strlen(conn->host); if (fd >= 0) { FD_SET(fd, &xxxfds); if (fd > maxfd) { maxfd = fd; } } return conn; } /* * NAME: conn->extend() * DESCRIPTION: enlarge a socket's output buffer */ static void conn_extend(conn) connection *conn; { int bufsize; int sz = sizeof(bufsize); conn->extended = 1; if (getsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF, (void *)&bufsize, &sz) >= 0 && sz == sizeof(bufsize) && bufsize < EXT_SIZE) { bufsize = EXT_SIZE; setsockopt(conn->fd, SOL_SOCKET, SO_SNDBUF, (void *)&bufsize, sizeof(bufsize)); } } /* * NAME: conn->listen() * DESCRIPTION: bind a connection to a local port */ connection *conn_listen(port, protocol) int port, protocol; { int fd; static struct sockaddr_in sin; int size; int on = 1; if (port < 0) { /* Request automatic port assignment */ port = 0; } /* No free connections: see the comment about nusers in comm_receive. */ if (flist == (connection *) NULL) { return (connection *) NULL; } if (protocol == PRC_UDP) { fd = socket(PF_INET, SOCK_DGRAM, 0); } else { fd = socket(PF_INET, SOCK_STREAM, 0); } if (fd < 0) { return (connection *)NULL; } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on)) < 0) { close(fd); return (connection *)NULL; } sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = INADDR_ANY; /* htonl? */ if (bind(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0) { close(fd); return (connection *)NULL; } size = sizeof(sin); if (getsockname(fd, (struct sockaddr *)&sin, &size) < 0) { close(fd); return (connection *)NULL; } if (protocol != PRC_UDP && listen(fd, 64) < 0) { close(fd); return (connection *)NULL; } if (fcntl(fd, F_SETFL, FNDELAY) < 0) { close(fd); return (connection *)NULL; } FD_SET(fd, &rfds); FD_SET(fd, &ufds); return conn_new(fd, &sin); } /* * NAME: conn->accept() * DESCRIPTION: accept a connection */ connection *conn_accept(conn) connection *conn; { int fd; struct sockaddr_in sin; int len, on; if (!FD_ISSET(conn->fd, &readfds)) { return (connection *) NULL; } len = sizeof(sin); fd = accept(conn->fd, (struct sockaddr *) &sin, &len); if (fd < 0) { return (connection *) NULL; } on = 1; setsockopt(conn->fd, SOL_SOCKET, SO_OOBINLINE, &on, sizeof(on)); FD_SET(fd, &rfds); FD_SET(fd, &ufds); return conn_new(fd, &sin); } /* * NAME: conn->connect() * DESCRIPTION: initiate a tcp connection */ connection *conn_connect(host, port, protocol) char *host; int port, protocol; { int fd; static struct sockaddr_in sin; if (protocol != PRC_TCP) { return (connection *) NULL; } /* No free connections: see the comment about nusers in comm_receive(). */ if (flist == (connection *) NULL) { return (connection *) NULL; } sin.sin_family = AF_INET; sin.sin_port = htons(port); sin.sin_addr.s_addr = inet_addr(host); fd = socket(PF_INET, SOCK_STREAM, 0); if (fd < 0) { return conn_new(-1, &sin); } if (fcntl(fd, F_SETFL, FNDELAY) < 0) { close(fd); return conn_new(-1, &sin); } if (connect(fd, (struct sockaddr *)&sin, sizeof(sin)) < 0 && !(errno == EINPROGRESS || errno == EAGAIN)) { close(fd); return conn_new(-1, &sin); } FD_SET(fd, &wfds); return conn_new(fd, &sin); } /* * NAME: conn->connected() * DESCRIPTION: mark a connecting socket as active */ int conn_connected(conn) register connection *conn; { struct sockaddr sa; int salen = sizeof(sa); if (conn->fd == -1) return -1; if (!FD_ISSET(conn->fd, &writefds)) return 0; /* Use getpeername() solely to check for errors. */ if (getpeername(conn->fd, &sa, &salen) < 0) { FD_CLR(conn->fd, &wfds); return -1; } FD_CLR(conn->fd, &wfds); FD_SET(conn->fd, &rfds); FD_SET(conn->fd, &ufds); return 1; } /* * NAME: conn->writable() * DESCRIPTION: check if a connection has been selected for writing */ int conn_writable(conn) register connection *conn; { return conn->fd >= 0 && FD_ISSET(conn->fd, &writefds); } /* * NAME: conn->del() * DESCRIPTION: delete a connection */ void conn_del(conn) register connection *conn; { if (conn->fd >= 0) { close(conn->fd); FD_CLR(conn->fd, &rfds); FD_CLR(conn->fd, &ufds); FD_CLR(conn->fd, &wfds); FD_CLR(conn->fd, &readfds); FD_CLR(conn->fd, &writefds); conn->fd = -1; } conn->next = flist; flist = conn; } /* * NAME: conn->wait() * DESCRIPTION: mark a connection as waiting for writability */ void conn_wait(conn, wait) register connection *conn; register int wait; { if (conn->fd < 0) { return; } if (wait) { FD_SET(conn->fd, &wfds); } else { FD_CLR(conn->fd, &wfds); } } /* * NAME: conn->block() * DESCRIPTION: temporarily unselect (block) a connection with respect to * incoming data */ void conn_block(conn, flag) register connection *conn; register int flag; { if (conn->fd < 0) { return; } if (flag) { FD_CLR(conn->fd, &ufds); FD_CLR(conn->fd, &readfds); } else { FD_SET(conn->fd, &ufds); } } /* * NAME: conn->select() * DESCRIPTION: wait for input from connections */ int conn_select(wait) int wait; { struct timeval timeout; int retval; /* * First, check readability and writability for binary sockets with pending * data only. */ memcpy(&readfds, &ufds, sizeof(fd_set)); memcpy(&writefds, &wfds, sizeof(fd_set)); timeout.tv_sec = (int) wait; timeout.tv_usec = 0; retval = select(maxfd + 1, &readfds, &writefds, (fd_set *) NULL, &timeout); if (retval < 0) { FD_ZERO(&readfds); } /* * Now check writability for all sockets in a polling call. */ memcpy(&xxxfds, &rfds, sizeof(fd_set)); timeout.tv_sec = 0; timeout.tv_usec = 0; select(maxfd + 1, (fd_set *) NULL, &xxxfds, (fd_set *) NULL, &timeout); return retval; } /* * NAME: conn->recvfrom() * DESCRIPTION: read a datagram */ int conn_recvfrom(conn, buf, size) connection *conn; char *buf; int size; { struct sockaddr_in sin; int len = sizeof(sin); if (conn->fd < 0) { return -1; } if (!FD_ISSET(conn->fd, &readfds)) { return 0; } size = recvfrom(conn->fd, buf, size, 0, (struct sockaddr *)&sin, &len); memcpy(&(conn->sin), &sin, len); strcpy(conn->host, inet_ntoa(sin.sin_addr)); conn->hostlen = strlen(conn->host); return size; /* Size may legitimately be 0 */ } /* * NAME: conn->read() * DESCRIPTION: read from a connection */ int conn_read(conn, buf, size) connection *conn; char *buf; int size; { if (conn->fd < 0) { return -1; } if (!FD_ISSET(conn->fd, &readfds) || size == 0) { return 0; } size = read(conn->fd, buf, size); return (size == 0) ? -1 : size; } /* * NAME: conn->sendto() * DESCRIPTION: send a datagram */ void conn_sendto(conn, data, size, host, port) connection *conn; char *data, *host; int size, port; { static struct sockaddr_in sin; if (conn->fd >= 0) { sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr(host); sin.sin_port = htons(port); if (sendto(conn->fd, data, size, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0 && errno == EMSGSIZE && !conn->extended) { conn_extend(conn); sendto(conn->fd, data, size, 0, (struct sockaddr *)&sin, sizeof(sin)); } } } /* * NAME: conn->write() * DESCRIPTION: write to a connection; return the number of bytes written */ int conn_write(conn, buf, size) connection *conn; char *buf; register int size; { int len; if (conn->fd < 0) { return -1; } else if (!FD_ISSET(conn->fd, &xxxfds)) { return 0; } else { len = write(conn->fd, buf, size); if (len < 0) { FD_CLR(conn->fd, &xxxfds); if (errno == EWOULDBLOCK) { return 0; } else { close(conn->fd); FD_CLR(conn->fd, &rfds); FD_CLR(conn->fd, &ufds); FD_CLR(conn->fd, &wfds); FD_CLR(conn->fd, &readfds); FD_CLR(conn->fd, &writefds); conn->fd = -1; return -1; } } if (len < size && !conn->extended) { conn_extend(conn); } return len; } } /* * NAME: conn->ipnum() * DESCRIPTION: return the ip number of a connection */ string *conn_ipnum(conn) connection *conn; { return str_new(conn->host, conn->hostlen); } /* * NAME: conn->port() * DESCRIPTION: return the port number of a connection */ Int conn_port(conn) connection *conn; { return ntohs(conn->sin.sin_port); }