/* * demonstration "chat server". * Alan J Rosenthal, May 2003 */ #include #include #include #include #include #include #include #include #include #include #include #include #include int port = 1234; static int listenfd; #define NAMESIZE 80 struct client { int fd; char name[NAMESIZE]; int namecomplete; struct client *next; } *top = NULL; static void addclient(int fd); static void removeclient(struct client *p); static void broadcast(char *s, int size); int main(int argc, char **argv) { int c; struct client *p; extern void setup(), newconnection(), whatsup(struct client *p); while ((c = getopt(argc, argv, "p:")) != EOF) { if (c == 'p') { if ((port = atoi(optarg)) == 0) { fprintf(stderr, "%s: port number must be a positive integer\n", argv[0]); return(1); } } else { fprintf(stderr, "usage: %s [-p port]\n", argv[0]); return(1); } } setup(); /* aborts on error */ /* the only way the server exits is by being killed */ for (;;) { fd_set fdlist; int maxfd = listenfd; FD_ZERO(&fdlist); FD_SET(listenfd, &fdlist); for (p = top; p; p = p->next) { FD_SET(p->fd, &fdlist); if (p->fd > maxfd) maxfd = p->fd; } if (select(maxfd + 1, &fdlist, NULL, NULL, NULL) < 0) { perror("select"); } else { for (p = top; p; p = p->next) if (FD_ISSET(p->fd, &fdlist)) break; /* * it's not very likely that more than one client will drop at * once, so it's not a big loss that we process only one each * select(); we'll get it later... */ if (p) whatsup(p); /* might remove p from list, so can't be in the loop */ if (FD_ISSET(listenfd, &fdlist)) newconnection(); } } return(0); } void setup() /* bind and listen, abort on error */ { struct sockaddr_in r; listenfd = socket(AF_INET, SOCK_STREAM, 0); memset(&r, '\0', sizeof r); r.sin_family = AF_INET; r.sin_addr.s_addr = INADDR_ANY; r.sin_port = htons(port); if (bind(listenfd, (struct sockaddr *)&r, sizeof r)) { perror("bind"); exit(1); } if (listen(listenfd, 5)) { perror("listen"); exit(1); } } void newconnection() /* accept connection, update linked list */ { int fd; struct sockaddr_in r; socklen_t len = sizeof r; if ((fd = accept(listenfd, (struct sockaddr *)&r, &len)) < 0) { perror("accept"); } else { static char greeting[] = "Welcome to the Stupid Chat Server.\r\n" "Please enter your name: "; printf("new connection from %s, fd %d\n", inet_ntoa(r.sin_addr), fd); addclient(fd); write(fd, greeting, sizeof greeting - 1); } } void whatsup(struct client *p) /* select() said activity; check it out */ { char buf[500], buf2[500 + NAMESIZE + 4], *bp, *next; int len; extern void cleanupstr(char *s); extern char *skipwhite(char *bp), *nextbreakpoint(char *bp, int width); len = read(p->fd, buf, sizeof buf - 1); if (len <= 0) { if (len < 0) perror("read()"); close(p->fd); if (p->namecomplete) { printf("Disconnecting fd %d, name %s\n", p->fd, p->name); sprintf(buf, "%s has disconnected.\r\n", p->name); removeclient(p); broadcast(buf, strlen(buf)); } else { printf("Disconnecting fd %d, no name\n", p->fd); removeclient(p); } } else if (p->namecomplete) { int width = 79 - (strlen(p->name) + 2 /* 2 for ": " */); buf[len] = '\0'; cleanupstr(buf); /* output with line breaks */ for (bp = buf; (bp = skipwhite(bp))[0]; bp = next) { next = nextbreakpoint(bp, width); sprintf(buf2, "%s: %.*s\r\n", p->name, next - bp, bp); broadcast(buf2, strlen(buf2)); } } else { buf[len] = '\0'; cleanupstr(buf); buf[NAMESIZE - 1] = '\0'; if (buf[0]) { strcpy(p->name, buf); p->namecomplete = 1; sprintf(buf2, "Welcome to our new participant, %s\r\n", p->name); broadcast(buf2, strlen(buf2)); } else { static char msg[] = "Please enter your name: "; write(p->fd, msg, sizeof msg - 1); } } } static void addclient(int fd) { struct client *p = malloc(sizeof(struct client)); if (!p) { fprintf(stderr, "out of memory!\n"); /* highly unlikely to happen */ exit(1); } p->fd = fd; p->name[0] = '\0'; p->namecomplete = 0; p->next = top; top = p; } static void removeclient(struct client *p) { struct client **pp; for (pp = ⊤ *pp && *pp != p; pp = &(*pp)->next) ; if (*pp) { struct client *t = (*pp)->next; free(*pp); *pp = t; } else { fprintf(stderr, "Trying to remove fd %d, but I don't know about it\n", p->fd); } } static void broadcast(char *s, int size) { struct client *p; for (p = top; p; p = p->next) if (p->namecomplete) write(p->fd, s, size); /* should probably check write() return value and perhaps remove client */ } void cleanupstr(char *s) { /* ajr, 16 May 1988. */ char *t = s; enum { IGNORESPACE, /* initial state - spaces should be ignored. */ COPYSPACE, /* just saw a letter - next space should be copied. */ COPIEDSPACE /* just copied a space - spaces should be ignored, * and if this is the last space it should be removed. */ } state = IGNORESPACE; while (*t) { if (isspace(*t)) { if (state == COPYSPACE) { /* It's a space, but we should copy it. */ *s++ = *t++; /* * However, we shouldn't copy another one, and if this space is * last it should be removed later. */ state = COPIEDSPACE; } else { /* An extra space; skip it. */ t++; } } else if (*t < ' ' || (*t > 127 && *t < ' ' + 128)) { /* control character or similar. Skip it. */ t++; } else { /* Not a space. So copy it. */ *s++ = *t++; /* If the next character is a space, it should be copied. */ state = COPYSPACE; } } if (state == COPIEDSPACE) { /* Go back one, as the last character copied was a space. */ s--; } /* Chop off the string at wherever we've copied to. */ *s = '\0'; } char *skipwhite(char *bp) { while (*bp && isspace(*bp)) bp++; return(bp); } char *nextbreakpoint(char *bp, int width) { char *lastspace = NULL; if (!*bp) return(bp); for (bp++; *bp && width > 0; bp++, width--) if (isspace(*bp)) lastspace = bp; if (width) lastspace = bp; else if (!lastspace) for (lastspace = bp; *lastspace && !isspace(*lastspace); lastspace++) ; return(lastspace); }