Explanation of Kragen's third C .signature puzzle

Last updated 1999-07-12.

Here's the program:

char a[99]="  KJ";main(int c,char**v){int s=socket(2,1,0);char*p,*t=strchr(*++v
,'@'),*o=a+4;*(short*)a=2;p=t;while(*p)(*p++&48)-48?*o++=atoi(p):0;connect(s,a,
16);strncpy(a,v[1],7);a[7]=':';a[8]=32;if(fork())while((c=read(0,a+9,99))>0)(
write(s,a,c+9)>0)||exit(0);else while((c=read(s,a,99))>0)write(1,a,c);}

This is a chat client; it works with the even smaller chat server. The function it performs is similar to IRC.

Here's a line-by-line (or expression-by-expression) dissection of the program, along with some comments on the whole chat system. (You should read how to run the program first.)

char a[99]=" KJ";

So we have a character array of which chars 2 and 3 are K and J. If this array is used as a struct sockaddr_in, that makes the port number, 18955.

main(int c,char**v) {

Unlike the server, it has arguments.

int s=socket(2,1,0);

PF_INET, SOCK_DGRAM. 1 needs to be changed to something else on Linux.

char *p, *t = strchr(*++v,'@'), *o=a+4;
*(short*)a=2;
p=t;
while(*p)(*p++&48)-48?*o++=atoi(p):0;

This bit of code is lifted from my first .signature puzzle program. See its explanation for details; basically this puts the IP address of the chat server in the struct sockaddr_in.

connect(s,a,16);

This sets the socket up to send packets to the server by default, as opposed to into a black hole or to the moon.

strncpy(a,v[1],7);

This copies up to seven characters from the second command-line argument (remember, we incremented v) into the beginning of 'a'. If there aren't 7 or more characters in the argument, the rest of the 7 characters is filled with NUL characters, which typically do not display on the screen if sent to it.

This is the name the person is using, remember.

a[7]=':';
a[8]=32;

So we put ": " after the seven characters of the person's name.

if(fork())
    while ( (c=read(0,a+9,90)) > 0) 
        (write(s,a,c+9)>0) || exit(0);
    else 
        while ( (c=read(s,a,99)) > 0)
            write(1,a,c);

fork() makes the process clone itself. In the parent process, the fork() call returns the child's process id, which is never 0. In the child process, it returns 0. On error, it returns -1, which would be confusing, because the parent process would run but the child wouldn't.

Each process has its own copy of all the data; both of them use 'a' as a buffer to read data into and out of.

The parent process reads from fd 0, which is stdin and usually somebody's keyboard, into a at offset 9, so as not to overwrite the nickname and ": " before offset 9. It reads up to 90 bytes. If the read call returns end-of-file (0) or error, it exits.

Then the write() call in the parent process writes out data from a through the socket, starting at the beginning of a, and running for 9 more bytes than were read.

Normally, when you're reading from somebody's keyboard, the last byte will be a newline, so normally, the last byte sent will be a newline.

The other half of the fork(), the child process, reads from the socket and writes to standard out, exiting if there's an error.

}

. . . the end of main().

Bugs

Some of these bugs would instantly disappear if I switched to TCP instead of UDP, but that would require a much more complex server.

Still, I think it's pretty awesome that it's possible to write a multi-user networked chat system at all in two .sig blocks. ;)

I note that, in some languages, packing meaning together horizontally can be much more readable than in C, e.g. Scheme, Lisp, and Forth. I suspect this may have something to do with why those languages are so visually compact.


Kragen's third C .signature puzzle | Kragen's home page