InNeedOfRefactor
This page is deprecated, see NetworkProgramming
This is to describe how to write networking programs in C using the BSD Socket Layer API. This mostly applies to almost all other languages with only minor modifications.
General structure for a server setup
- Create a socket of the protocol family (Inet, Inet6, Unix etc). This is usually done with a socket(2) call, but can also be created with socketpair(2). You really should read the socket(2) man page for the differnet protocol families.
- Bind it to an address of the correct address family (Inet, Inet6, Unix etc). This is done with a bind(2) call.
- Listen on that address. This is done with a listen(2) call.
- Accept a client connection. This is done with an accept(2) call.
General structure for a client setup
- Create a socket of the correct protocol family (inet, inet6, unix etc)
- Connect to an address of the correct address family (Inet, Inet6, Unix etc), this may involve gethostbyname(3) for internet domains.
How to create a TCP Server (tcp(7))
- 1
- TCP is a streaming socket from the internet family of protocols, so first we create a socket of the correct type:
struct protoent *protocol;
int serverfd;
protocol=getprotobyname("tcp");
if (!protocol) {
printf("Could not getprotobyname");
exit(1)?;
}
server=socket(PF_INET,SOCK_STREAM,protocol->p_proto);
if (server<0) {
perror("socket");
}
- 2
- Then we create an address to bind it to. We don't care which of our local IP's we bind it to so we choose "INADDR_ANY" (all interfaces)
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port goes here)
address.sin_addr.s_addr = INADDR_ANY;
- 3
- Then we bind the port to that socket.
if (bind(serverfd,&address,sizeof(address))<0) {
perror("bind");
}
- 4
- Now we start listening on that port. The 5 is the number of outstanding connections that will be queued by the kernel before you get a chance to accept(2) them.
if (listen(serverfd,5)<0) {
perror("listen");
exit(4)?;
}
- 5
- And now we wait for an incoming connection
int sockfd;
struct sockaddr_in clientaddress;
sockfd=accept(serverfd,&clientaddress,sizeof(clientaddress));
if (sockfd<0) {
perror("accept");
exit(5)?;
}
This creates a new socket (called "sockfd") that we can talk to the client that connected with. We can go back and do the accept(2) again to get another client socket etc.
How to create a TCP Client. (tcp(7))
- 1
- First you need to create a socket as in TCP Server step 1 above, note the rest of this example uses "sockfd" not "serverfd"
- 2
- Optionally you can bind the socket to a local address using step 2 and 3 above. Note, this isn't necessary, or usually even desired.
- 3
- Next you want to create an address to connect out to:
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(port goes here);
struct hostent *host;
int i=0;
int success=0;
host = gethostbyname("host.name.goes.here");
if (!host) {
fprintf(stderr,"gethostbyname: %s",hstrerror(herrno));
exit(5)?;
}
for (i=0;i<host->h_length;i++) {
if(connect(sockfd,&address,sizeof(address))==0) {
success=1;
break;
}
fprintf(stderr,"connect(%s):%s",inet_ntoa(address.sin_addr),strerror(errno));
}
if (!success) {
fprintf(stderr,"hostname is unreachable");
exit(6)?;
}
Done!
How to talk over a TCP socket. (tcp(7))
Now, after you have TCP connection, either by having accept(2) or connect(2) return, you want to send traffic over it.
- !!To Read Data
char buffer[65535?;
int bytes;
bytes=read(sockfd,buffer,sizeof(buffer));
if (bytes<0) {
perror("read");
}
else if (bytes==0) {
close(sockfd);
printf("Socket closed");
exit(0);
}
process data
The number of bytes read is in "bytes", if bytes is 0, then the other end closed the connection.
To Write Data
char buffer[65535?;
int bytes;
int remaining;
populate buffer with data, set bytes to being the number of bytes to write, eg:
strcpy(buffer,"Hello World");
bytes=strlen(buffer);
remaining=bytes;
while(remaining>0) {
ret=write(sockfd,buffer+(bytes-remaining),remaining);
if (ret<0) {
perror("write");
close(ret);
exit(8)?;
}
remaining=remaining-ret;
}
How to write a UDP Server/Client (udp(7))
- 1
- Creating UDP is similar to TCP above, but instead of using SOCK_STREAM you use SOCK_DGRAM, and instead of "tcp" you use "udp" (obviously).
- 2
- If you are writing a server style UDP socket, then you probably care which port you connect to, so you bind as you do in the TCP example. This step is optional.
- 3
- If you are only going to send to one host you can use connect(2) (as above) to bind the other end of the connection to a socket. This step is optional.
- 4
- to send:
- You can use write(2), this assumes that you have used connect(2) above as you can't specify the destination, and that you don't want any special processing.
- You can use send(2), this assumes that you have used connect(2) above, but you can specify special flags
- You can use sendto(2) to send to a specific address and with specific flags.
- Remember:
- You have to send your entire message in one operation, multiple send(2)'s/write(2)/sendto(2) will be sent in seperate packets
- UDP is unreliable, your packet may not arrive at the other end, and multiple packets may arrive out of order.
- UDP has no flow control, you could flood the other end of the link with data, or overload intermediate bandwidth (usually your own)
- 5
- to recieve:
- You can use read(2) when you really don't care about where it comes from or want to do any special processing.
- You can use recv(2) when you don't care where the data comes from but you can do special processing (PEEKing etc)
- You can use recvfrom(2) when you want to know where the packet came from.
Differences between TCP/UDP (ip(7)) and Unix Domain Sockets (unix(7))
Unix domain sockets can be stream orientated (like TCP) or datagram orientated (like UDP). The only differences are:
- You use PF_UNIX for the protocol family instead of PF_INET
- Instead of sockaddr_in, you use sockaddr_un.
- You use AF_UNIX for the address family instead of AF_INET.
- sockaddr_un doesn't have sin_port or sin_addr, but sun_path which is a string of where to put the socket, for instance:
struct sockaddr_un address;
address.sun_family=AF_UNIX;
strcpy(address.sun_path,"/tmp/.``mysock``);
- Remember to unlink(2) your sockets when your finished with them, bind(2) will fail if a socket already exists.
- Unix domain sockets are reliable (even the datagrams)
- Instead of creating a socket with socket(2)/bind(2)/connect(2) etc you can create one with socketpair(2)
- You can pass file descriptors over unix domain sockets.
Tricks 'n Traps
- Don't read(2) or write(2) on a server socket, it won't work, you have to use the client socket returned from accept(2) when writing servers.
- fdopen(3) gives you a FILE * from a fd, meaning you can use a socket like a normal file, with fread(3), fscanf(3) etc.
- ports and addresses are in NetworkByteOrder, so you should use htons(3) on ports, and htonl(3) on IPv4 addresses. This is very evident on IBM PC's since they are LittleEndian
- UDP and TCP ports below 1024 can only be bound by the superuser, and people with CAP_NET_BIND_SERVICE.
- Port 0 means "any available port" and will be assigned by the kernel.
- Port 1 to 511 are available for root only services. These services are considered "trusted" because only root can run them, so normal users can't "hijack" the port.
- Port 512 to 1023 are assigned by the kernel when a root owned program asks for "any available port"
- Port 1024 to 4999 are assigned by the kernel when a non-root owned program asks for "any available port"
- Port 5000+ are available for use by non-root owned programs to bind to.
Network server programming styles (and examples).
- The traditional Unix server programming style is the fork(2)'ing server. This means you have one server which accept(2)'s on a socket, and then when a client connects fork(2)'s a new process to deal with that connection, and then returns around the loop and accept(2)'s again. For example, a hello world server
- include <sys/types.h>
- include <sys/socket.h>
- include <netinet/in.h>
- include <netdb.h>
/* make_tcp_socket
- parameters:
- none
- returns:
- a tcp socket on success
- -1 on failure
- side effects:
- may output error messages to stderr on failure
- creates a tcp socket
- /
int make_tcp_socket(void) {
struct protoent *protocol;
int serverfd;
protocol=getprotobyname("tcp");
if (!protocol) {
fprintf(stderr,"Could not getprotobyname");
return -1;
}
server=socket(PF_INET,SOCK_STREAM,protocol.p_proto);
if (server<0) {
perror("socket");
return -1;
}
}
/* get_port
- parameters:
- servername, the name of a service to look up in /etc/services
- protocol, the name of a protocol from /etc/protocols (eg: "tcp", "udp")
- defaultport, optional default port to use if the service is not found in /etc/services
- returns:
- a port, in network byte order
- or
- -1 on failure
- side effects:
- may output a message on stderr for failures
- /
int get_port(char *servername,char *protocol,int defaultport=0)
{
struct servent *service;
service = getservbyname(servername,protocol);
/* we found the service! */
if (service)
return service->s_port;
/* We didn't find it, is the port valid? */
else if (port>0 && port<65535)
return htons(port);
/* Give up */
else {
fprintf(stderr,"Can't find port for service \"%s\"",servername);
return -1;
}
}
/* make_tcp_server
- parameters:
- servername, a service from /etc/services
- port, optional port to use if the service is not found in /etc/services
- returns:
- a tcp socket bound to the correct port on all interfaces
- side effects:
- may output messages to stderr on failures
- creates a tcp socket which the client will need to close when finished with
- /
int make_tcp_server(char *servername,int port=0)
{
int serverfd;
struct sockaddr_in address;
serverfd = make_tcp_socket();
if (serverfd<0)
return -1;
port = get_port(servername,"tcp",port);
if (port<0) {
fprintf(stderr,"Can not look up port for service \"%s\", try adding it to /etc/services\n",servername);
close(serverfd);
return -1;
}
/* Create an address structure to bind to */
address.sin_family = AF_INET;
address.sin_port
address.sin_addr.s_addr=INADDR_ANY;
/* Bind to the port */
if (bind(serverfd,&address,sizeof(address))<0) {
perror("bind");
close(serverfd);
return -1;
}
/* Listen on the socket */
if (listen(serverfd,5)<0) {
perror("listen");
close(serverfd);
return -1;
}
return serverfd;
}
/* Whatever you want to do for clients */
int handle_client(int clientfd)
{
char *message = "Hello World\n";
write(clientfd,message,strlen(message));
close(clientfd);
exit(0);
}
int main(int argc,char **argv)
{
int serverport;
/* Create the server socket */
serverfd = make_tcp_server("hello",5432);
if (serverfd<0)
return 1;
/* The main loop */
for(;;) {
int clientfd;
/* Accept a connection */
clientfd = accept(serverfd);
if (clientfd<0) {
perror("accept");
/* Don't just abort here, since it's fairly common to get an accept(2) failure, especially when people are portscanning */
}
else if (fork()==0) {
/* Client "Thread" /
/ The client doesn't need the server fd anymore, so close it */
close(serverfd);
handle_client(clientfd);
}
else {
/* Server "Thread" /
/ Important: Close the fd, otherwise we'll eventually run out of file descripters after we've processed enough clients */
close(clientfd);
}
}
return 0; /* NOT REACHED */
}