- Connecting the Client
- Establishing the Server
- Threading Your Server
- Bringing It Together
Establishing the Server
The client uses the server's protocol to request information. When the client's request for connection arrives, the server is ready to accept the connection and begin processing.
The server's algorithm looks a lot like the client's with a few modifications. When you write a server program, you don't need some of the client's library and system calls, such as gethostbyname() or connect(). At the same time, the server adds a few new calls that turn the socket into a listening server socket.
Assigning the Port
After creating the socket (identical to the client's process, described earlier), you need to tell it what port to listen on. The program does this with bind():
#include <sys/socket.h> #include <sys/types.h> #include <resolv.h> struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = srv->s_port; addr.sin_addr.s_addr = INADDR_ANY; /* any interface */ bind(sd, &addr, /* bind port/address to socket */ sizeof(addr));
Except for INADDR_ANY and bind(), this code fragment looks a lot like the connect() code fragment shown earlier. The bind() system call tells the operating system that you want a specific port number. Without this call, the operating system assigns any available port number from a pool of port numbers. By assigning a specific number, in effect you're publishing that port for use.
The bind() system call also allows you to isolate the service to one or all network interfaces. Each active network interface card (NIC) owns at least one IP address, and each host on the network has at least one NIC. Placing INADDR_ANY in addr.sin_addr tells the operating system that you want to accept connections from all possible IP addresses on the host. Otherwise, you can select just one IP address to offer a service. This is very effective if your host is acting as a firewall, where only one side of the firewall should have access to the service.
Forcing the Socket to Listen
After binding the address, the TCP connection expects some client to request a connection. In order to get the socket to accept that connection, you must convert the socket into a listening socket:
listen(sd, 10); /* make into listener with 10 slots */
The listen() system call changes attributes of the socket. First, it makes it into a listening socket; you can't use the socket for data transmission anymore. Then it creates a waiting queue with the depth you specify. In the example above, up to 10 pending connections can wait while the server serves the current active connection.
Accepting Connections
Now that the server has a listening socket, it can wait for a connection. When the client makes the request to establish a dialogue, the server catches that request in an accept call:
int client_sd; FILE *fp; char s[200]; while (1) /* process all incoming clients */ { client_sd = accept(sd, 0, 0); /* accept connection */ fp = fdopen(client_sd, "r+"); /* convert into FILE* */ /*--- Process client's requests ---*/ while (fgets(s, sizeof(s), fp) != 0 && strcmp(s, "bye\n") != 0) /* proc client's requests */ { printf("msg: %s", s); /* display message */ fputs(s, fp); /* echo it back */ } fclose(fp); /* close the client's channel */ }
This code fragment reads each line from the client, displays in on the console, and echoes it to the client. When the client connects, the listening socket creates a new data socket through the accept() system call. If you recall, the program can't use the listening socket for data transfer. The accept() system call provides the needed I/O channel. Of course, the new socket needs to be closed when you're done with it.
The code presented in this section is an "echo server," the best way to start programming sockets. It demonstrates how to connect and perform I/O. From this point, you simply add your own data.