Networking and the BSD Sockets API
Pages: 1, 2, 3
Servers
Now we discuss the startup procedure for a server, which is slightly more complicated than what a client must do. The first step is the same -- create a socket:
struct sockaddr_in serverAddress;
int listenfd, connectfd;
if ( (listenfd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
perror( "socket" );
exit(1);
}
The only thing that changed here is that we declared a second socket file descriptor variable to hold the return value of accept(), and we changed the name of the original socket file descriptor variable from sockfd to listenfd, to reflect the changed nature of the socket in a server application.
Next, we have to bind the socket to a port and address using the bind() function. This function takes a sockaddr_in struct, so we have to prepare one as we did for the client:
bzero( &serverAddress, sizeof(serverAddress) );
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons( 12345 );
serverAddress.sin_addr.s_addr = htonl( INADDR_ANY );
Initializing the server's socket address structure is done in the same way as the client's, with a few small changes. We first zero the memory space occupied by serverAddress, and then set the family, port number, and address. Notice, however, that we set the address differently than before. We could have used inet_pton() with the same localhost IP address that we used for the client, but to do so would restrict the server to accepting connections only on the localhost interface. In other words, our server would not be able to accept connections from the network, since it would only be listening for connections on the IP address 127.0.0.1.
There are functions that let us obtain the IP address of the Ethernet interface, but there is a better solution that will allow the socket to accept connections on any of the available interfaces (Ethernet, localhost, Airport, Firewire, etc.). By setting the address to the constant INADDR_ANY, the kernel will bind the socket to ress.sin_addr.s_addr to the network representation (obtained using htonl()) of the constant INADDR_ANY all available network interfaces. Thus, if we simultaneously have an active Ethernet connection, an active Airport connection, and the loopback interface (127.0.0.1), we will be able to connect to the socket over any of these interfaces' respective IP addresses.
Next we have to bind the socket to the address specified in the struct serverAddress. This is done using the bind() function:
if ( bind( listenfd, (struct sockaddr *)&serverAddress,
sizeof(serverAddress)) < 0 ) {
perror( "bind" );
exit(1);
}
Like the connect() function, we pass the socket file descriptor, the socket address structure that specifies the address and port to bind to, and finally, the length of the address structure. As always, we check to see if the function executed successfully by comparing the return value to zero.
Next we call the listen() function to tell the socket to listen for incoming connections. By calling listen(), we are converting our socket into a passive socket that can accept connections. Calling listen() is pretty straightforward:
if ( listen( listenfd, 5 ) < 0 ) {
perror( "listen" );
exit(1);
}
listen() takes two arguments: the socket file descriptor and the backlog. The backlog argument is used to limit the size of the queue for incoming connections. Thus, by passing 5 we tell the kernel to queue up to 5 pending connections. If a client attempts to connect when the queue is full, the kernel will refuse the connection, and the client's call to connect() will return with a connection-refused error. Note that the backlog does not specify the total number of connects the server can handle, because once a connection has been accepted by the server, the request is removed from the queue, thus making room for additional connection requests.
The next thing we do is call the accept() function within a rudimentary run loop. What accept() does is connect to the host whose connection request is at the front of the queue, and return a socket file descriptor for this connection. We can then read and write data to the client using this new socket. Let's take a look at how our simple server will send a message to clients:
for (;;) {
char *buffer = "Howdy!\n";
if ( (connectfd = accept( listenfd,
(struct sockaddr *)NULL, NULL )) < 0 ) {
perror( "accept" );
exit(1);
}
if ( write( connectfd, buffer, strlen(buffer)) < 0 ) {
perror( "write" );
exit(1);
}
close( connectfd );
}
The rudimentary run loop I mentioned above is done with the infinite for loop; the server will continue waiting for connections until the user kills the process (using Ctrl-C, for example). Our server is pretty inflexible, since the message it sends to clients that connect is hard-coded, and short: "Howdy!" The message is coded as a null-terminated string. In the call to accept(), we pass the file descriptor for the listening socket (and nothing for the address structure, since we don't need any of that information that accept() returns in this structure), and in return, accept() gives us the file descriptor for the connected socket. This socket, connectfd, is our end of the connection between the server host and the client host. This is the socket that we read data from and write data to when we communicate with the client.
Finally, after we send the message, we close the connected socket using the close() function and return to the top of the loop, ready to accept a new connection.
Now, putting all of these pieces together (with error checking in place), we have the following code for a small server application:
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main( int argc, char **argv )
{
struct sockaddr_in serverAddress;
int listenfd, connectfd;
if ( (listenfd = socket( AF_INET, SOCK_STREAM, 0 )) < 0 ) {
perror( "socket" );
exit(1);
}
bzero( &serverAddress, sizeof(serverAddress) );
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(12345);
serverAddress.sin_addr.s_addr = htonl( INADDR_ANY );
if ( bind( listenfd, (struct sockaddr *)&serverAddress,
sizeof(serverAddress) ) < 0 ) {
perror( "bind" );
exit(1);
}
if ( listen( listenfd, 5 ) < 0 ) {
perror( "listen" );
exit(1);
}
for (;;) {
char *buffer = "Howdy\n";
if ( (connectfd = accept( listenfd,
(struct sockaddr *)NULL, NULL )) < 0 ) {
perror( "accept" );
exit(1);
}
if ( write( connectfd, buffer, strlen(buffer) ) < 0 ) {
perror( "write" );
exit(1);
}
close( connectfd );
}
}
And that, my friends, is our server. If you created a standard tool project for both the client and the server, you can compile and run each. Before you run the client, however, make sure you have the server running. If you want to compile and run these in a shell, type the following commands to invoke the compiler:
% cc -o server server.c
% cc -o client client.c
When running, you might want to open a second shell so you have one for the client and one for the server. Again, make sure you have the server running (by typing ./server from the directory where you compiled the code), and then run the client (./client from the same directory). If you have trouble, here are the source files I worked with for you to play around with. If you want to see something kind of cool, try typing the following in the shell while the server is running (this, incidentally, is a good way of testing server applications):
% telnet localhost 12345
So there you have it -- a very simple example that shows how Unix does networking. If you're interested at all in networking, I can't recommend strongly enough that you pick up the book Unix Network Programming, Volume 1 by W. Richard Stevens. With the prominence of networking capabilities in most applications today, every programmer should have this book. There is a lot more to network programming than what we saw here. There are many considerations to be made for scalability, protocol independence, security, and more. This book covers it all.
In the next column, we'll take what we learned here and see how we use some of the Foundation classes to make RCE an application that can actually communicate over a network.
Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.
Read more Programming With Cocoa columns.
Return to the MacDevCenter.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 16 of 16.
-
how to get output after execution of system() call in server back to client in socket programming
2007-02-16 14:09:44 r16 [Reply | View]
-
UDP
2005-05-21 20:17:32 vatin [Reply | View]
Hi, I change the code to use UDP instead of TCP, but I get an error "operation not supported". How an I have an udp SErver working?
Bye
-
bzero
2003-06-22 08:40:39 anonymous2 [Reply | View]
I think so. I haven't seen bzero being used a lot, memset is used more.
-
bzero
2003-04-01 07:58:27 anonymous2 [Reply | View]
Isn't bzero an obsolete function with mset being the preffered choice?
-
Part 3
2003-02-06 18:11:10 anonymous2 [Reply | View]
I sure can't wait for Rendezvous part 3! When will it come out?
-
Part 3
2003-02-06 18:11:04 anonymous2 [Reply | View]
I sure can't wait for Rendezvous part 3! When will it come out?
-
bug in client.c
2003-02-02 22:11:50 anonymous2 [Reply | View]
Very nice article. One bug you may want to rectify in your example:
The test for n < 0 will never been seen, since the test in the while statement:
while ( n = read( sockfd, buffer, BufferSize-1) ) {
buffer[n] = 0;
printf("%s", buffer );
}
if ( n < 0 ) {
perror( "read" );
exit(1);
}
is true whether n > 0 or n < 0. Further, if n < 0, the "buffer[n] = 0" statement could in principle lead to a crash of the client program.
You should probably have for the while comparison:
while ( (n = read( sockfd, buffer, BufferSize-1)) > 0 ) {
buffer[n] = 0;
printf("%s", buffer );
}
I prefer explicit logic tests as this tends to help prompt one's thinking to avoid this type of errors.
Carrick Talmadge
clt@olemiss.edu
-
security error in client code
2003-01-13 11:42:51 anonymous2 [Reply | View]
In the client code, you wrote:
printf( buffer );
this is vulnerable to the common "format-string security error" which is the second most common security error in C network software. The corrected line should read:
printf( "%s", buffer );
In addition, you invite a future security error by using hardcoded constants here:
char buffer[201];
...
while (n = read(sockfd, buffer, 200)) {
instead of:
#define BUFSIZE 200
...
char buffer[BUFSIZE + 1];
...
while (n = read(sockfd, buffer, BUFSIZE)) {
Please take care not to promulgate insecure C programming practices in the future. -
Give the author a break...
2003-01-20 11:28:41 stevesheets [Reply | View]
>>you invite a future security error by using hardcoded constants here
>>Please take care not to promulgate insecure C programming practices in the future.
Give the author a break...
When someone is writing a programming example/snippet, not released product, you use an entirely different method/standard to judge it. The purpose is to show the theory behind the code. You must do this, usually with the editor yelling to keep the article short and to the point. And most users skip over long source code examples, so you must keep the lines of code you create small and exact.
Yes, we all know it is best to not hardcode sizes and constants. But it also perfectly correct when writing a coding article or code snippet to keep the code simple, without over use of macros and such.
Thanks for the article!
Steve Sheets
-
other socket APIs
2002-12-29 05:48:37 anonymous2 [Reply | View]
CFSocket
http://developer.apple.com/macosx/win32porting/networking.html
NSFileHandle
http://developer.apple.com/techpubs/macosx/Cocoa/Reference/Foundation/ObjC_classic/Classes/NSFileHandle.html
SmallSockets
http://smallsockets.sourceforge.net/
OmniNetworking
http://www.omnigroup.com/developer/sourcecode/
-
Beej's Guide To Network Programming
2002-12-28 22:16:35 jasontm [Reply | View]
if you want to learn how to use sockets, read this:
http://www.ecst.csuchico.edu/~beej/guide/net/html/
best tutorial out there. :)
-
Address in use
2002-12-28 20:29:13 johnts [Reply | View]
Great article, as usual. One thing I noticed, not really having to do with the code. When I run the server (and testing the client), I use Ctrl-C to quit the server. If I try to run the server again I get an error: "bind: Address already in use". Looks like the socket isn't being released right away. If I wait a bit, it will work again. I'm sure it's some TCP protocol issue, but thought I'd mention it. -
Address in use
2002-12-28 22:24:50 jasontm [Reply | View]
you need to use setsockopt() to set the SO_REUSEADDR option. that way the socket won't stay bound to a port after your program has terminated. Unix holds onto them by default just in case your program launches in the near future. there's a delay that you remove by setting SO_REUSEADDR. :)
use the mighty Google to find out more.. i'm too tired to launch into a tutorial. :P
good luck.. -
Address in use
2002-12-29 17:44:43 johnts [Reply | View]
Got it to work, using code from Beej's Guide page mentioned in another post - there was the exact description of the problem too. I added:
int yes=1;
if (setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) < 0) {
perror( "setsockopt" );
exit(1);
}
and I can start the server application right after stopping it without an error.
-
CFSocket
2002-12-28 09:47:43 anonymous2 [Reply | View]
It sure would be handy to have a tutorial on using CFSocket. It's a nice API and provides pretty straightforward async sockets for any Cocoa or Carbon application. I've done my own wrapper for CFSocket for a Jabber client I'm working on, if you'd like some example code..
http://www.jabberstudio.org/cgi-bin/viewcvs.cgi/acid/utility/DizSocket.m?rev=1.4&content-type=text/vnd.viewcvs-markup






i am doin a project in client server communication using socket programming in C in Linux
my objective is to implement remote shell
handling multiple clients simultaneously with a single daemon server running in background.
after execution of system("shell command") in server i am not able to pass back this output to client.can anybody help me on this...