7.8.4. C IN THE UNIX ENVIRONMENT

7.8.4.1. FORK

fork() is a system call that can be invoked by a C program to duplicate a process. The original process is called the 'parent', the duplicated process is called the 'child'.

		PARENT		 	CHILD

main() main()

{ {

pid = fork(); pid = fork();

} } pid > 0 pid = 0

Both continue processing from the 'fork' call; the only difference is the value of 'pid':

pid = -1 fork failed - no child

pid > 0 parent process

pid = 0 child process

Example:

main()

{

switch(fork())

{

case -1:

printf("Fork error\n");

break;

case 0:

printf("Child process\n");

child();

break;

default:

printf("Parent process\n");

parent();

break;

}

}

Any variables declared before the fork command have the same value in both the parent and the child. Thus fork() can be used to "share" a pipeline. If one of the processes changes the value of one of these variables this change will not affect the value of the variables in the other process.

An ORPHAN is a child process that continues to execute after its parent has finished execution. To avoid this the parent should execute:

wait(0);

which causes the parent to sleep until the child is finished.

7.8.4.2. PIPES

pipe() is a system call that facilitates inter-process communication. It opens a "virtual file" that can be used by the process as well as all its child processes for reading as well as writing. So one process can write to this "virtual file" or pipe and the other process can read from it. If a process tries to read before something is written to the pipe, the process is suspended until something is written.

Pipe() is passed an array of two INTEGERS.

Example:

int pip[2] where:

pip[0] is a file descriptor used to read from the pipe

pip[1] is a file descriptor used to write to the pipe

The buffer size for the pipe is fixed. Once it is full all further writes are blocked. Pipes work on a first in first out basis.

Example:

main()

{

int pid, pip[2];

char buff[20];

pipe(pip); [Create the pipe and return file descriptors in pip]

pid = fork();[create child; both processes continue from here]

if (pid == 0) [Child]

write(pip[1], "Hi Mom!", 7); [7 chars in the string]

else [Parent]

read(pip[0], buff, 7);

}

7.8.4.3. NAMED PIPES (FIFO's)

NOTE: FIFO's are in System V but not 4.3 BSD Unix.

A named pipe creates a file which can be opened by any process for reading or writing. This file is special because its contents are removed as soon as any process reads them.

mknod name p [From the system prompt]

or [In a C program]

int mknod( char *pathname, int mode, int dev);

where:

pathname is a file name that identifies the named pipe.

mode specifies the access mode (read, write, execute permissions for owner,group, and world) logically or'ed with S_IFIO defined in <sys/stat.h>. The access privileges follow the same format an the chmod unix command.

dev When mknod is used to create a pipe the dev field is ignored.

The pipe can then be opened with the standard fopen() system call. Once a pipe is no longer needed it has to removed from the file system with either rm or ulink(). For a more complete explanation of named pipes see [STEVENS].

7.8.4.4. EXEC

If you want to execute another program from within a C program but do not want to return to the calling program you simply use execl().

execl("filename_of_command","arg 0","arg1","arg2",NULL);

Where:

filename_of_command must be the full path name of the command.

e.g. "/bin/fgrep"

arg 0 is the program name e.g. "fgrep" the program name may be NULL but the string must be included:

e.g. execl("/bin/date","",NULL);

arg1 ... argn Other arguments can be passed to the function by listing them (as strings) after the command. The argument list must be NULL terminated.

Example:

execl("/bin/fgrep","fgrep","-i","Subject:","mail",NULL);

The execl() call overlays (REPLACES) the existing process with the new one that executes and then exits. There is no return to the calling program. Any statements after the 'exec' call will not be executed except if the execl() call fails.

7.8.4.5. SYSTEM

If you want to execute a shell command, (a command that is available to you at the system prompt), you may do so without having to overwrite and stop your current program (as with the exec call). This is done by the use of the system command. It's syntax is:

system(path_name);

Where path_name is the complete path_name to the command you wish executed. Your program will cause the system to execute the command, and then return control to your program.

7.8.4.6. SIGNALS

Signals include events such as:

a) terminal 'break'

b) program error (divide by zero, memory violation, etc.)

c) An asynchronous "signal" from another process.

A receiving process can:

a) ignore the signal

b) allow the system default action

c) trap a signal and execute its own function for the action

d) reset a signal to the default action

The file <signal.h> contains definitions for the signal values as well as some other useful 'defines'.

To alter the action resulting from a signal enter the call:

signal(signal_number, value)

where:

* The value is:

a) the address of a function to be executed

b) a code for ignore (SIG_IGN) or default (SIG_DEFL)

*signal_number is defined in <signal.h>

Example:

signal(SIGINT, SIG_IGN); /* ignore an interrupt signal */

signal(SIGINT, SIG_DFL); /* revert to default action */

A call to 'signal' returns the previous action for the signal (pointer to the previous handler).

A function to handle the action when a signal is received should follow the following format, and must be defined as type integer, at the start of the routine which is doing the call to 'signal()'.

handler_name(int sig, int code, struct sigcontext *scp)

[sig signal caught]

[code reason for trap or fault]

[scp needed for restoration after action]

{

.

. code to be executed

.

sigreturn(scp);

}

Look in signal.h for codes associated with signals e.g. a floating point exception could be caused by zero divide, overflow, etc. Not all codes have signals associated with them.

A process can send a signal to another process. Signals SIGUSR1 and SIGUSR2 are reserved for user defined signals and are defined in the signal.h file. To send a signal use the "kill" command (really a misnomer in this case).

kill(int pid, int sig);

pid; [process id]

sig; [signal number]

NOTE: that kill(pid, SIGKILL); sends a 'sure kill' signal value (i.e. it cannot be caught or ignored). A parent must know its child's pid in order to send signals to it.

7.8.4.7. SOCKETS

A socket is similar to a pipe. The difference is that two processes can communicate via a socket without inherited file descriptors. In fact, BSD UNIX implements pipes as a pair of sockets. Sockets provide a generalization for the standard I/O functions (open(), read(), write(), and close()) that are capable of communicating between different machines over a network.

Sockets are characterized by:

* The domain in which they operate

* The name to which they are optionally bound

* The socket type

* The socket protocol

7.8.4.7.1. CREATING SOCKETS

To create a socket the socket() system call is used:

int socket(int domain, int type, int protocol)

domain

Sockets can exist in several different domains, or address families (AF). These are sometimes referred to as protocol families (PF) There are two common domains for sockets; those between processes on the same machine (AF_UNIX or PF_UNIX), and those between processes on two different machines on the internet (AF_INET or PF_INET). Their are several other domains available including: "arpanet imp addresses", "DECnet", "IBM SNA","Apple Talk".

There are several different family types of sockets (methods of transmitting data) that can be used for each type of socket. The two main ones that are supported on UNIX are TCP (transmission control protocol), and UDP (internet user datagram protocol). The different protocols are also grouped into families. Both TCP and UDP belong to the INET family.

type

Once you have chosen the family of socket you wish to use, you have to choose what type to use. There are three common types:

* SOCK_STREAM provides byte-by-byte stream communications in a fashion similar to pipes. A stream is a "reliable connection" transmission system. The internet is responsible for making sure that all information is delivered reliably.

* SOCK_DGRAM is used for datagram transmission. A datagram is basically a header followed by some data, they are a "unreliable connectionless" delivery system. This simply means that it is up to the program of a higher level protocol (such as TCP) to confirm the delivery of the datagrams.

* SOCK_RAW is used when you want a high degree of control over the transmission. Raw sockets are used by special privileged programs to access very low level protocols. The only kind discussed will be SOCK_STREAM.

Protocol

The choice of type usually determines the protocol that will by used. If 0 is used for the protocol parameter the default protocol is use. The following show the actual protocols used as defined in <netinet/in.h>

family              type                protocol            Actual protocol     
AF_INET             SOCK_DGRAM          IPPROT0_UDP         UDP                 
AF_INET             SOCK_STREAM         IPPROT0_TCP         TCP                 
AF_INET             SOCK_RAW            IPPROT0_ICMP        ICMP                
AF_INET             SOCK_RAW            IPPROT0_RAW         (raw)               

Example:

if((sfd = socket(AF_INET, SOCK_STREAM, 0))== -1)

{

perror("socket");

exit(-1);

}

7.8.4.7.2. NAMING THE SOCKET

For programs to use the socket the connect() system call has to be used. connect() requires a way to identify the socket. The bind() system call is used to associate the socket with either a file name in the file system or a PORT number. On the local computer it is sufficient to bind the socket to a filename, but for communication between machines the internet address along with a port number are required.

Example (binding to a file name):

Host:

/*

From: Berkeley UNIX: A simple and comprehensive Guide

James Wilson

*/

#include <sys/types.h>

#include <sys/socket.h>

#define SIZEOFSOCKNAME 10

int main()

{

int i,sd,ns;

char buff[256];

struct sockaddr sockname;

int fromlen;

sd = socket(AF_UNIX,SOCK_STREAM,0);

sockname.sa_family = AF_UNIX;

strcpy(sockname.sa_data,"my_socket");

if (bind(sd,&sockname,SIZEOFSOCKNAME) == -1)

{

perror("binding error");

exit();

}

listen (sd,1);

ns = accept(sd,&sockname, &fromlen);

i = read(ns,buff,sizeof(buff));

printf("Server received %s\n",buff);

printf("It was %d bytes long\n",i);

close(ns);

close(sd);

unlink(sockname.sa_data);

return 0;

}

client:

/*

From: Berkeley UNIX: A simple and comprehensive Guide

James Wilson

*/

#include <sys/types.h>

#include <sys/socket.h>

#define SIZEOFSOCKNAME 12

/* 2 bytes (family) + 10 bytes for name */

int main()

{

int sd;

struct sockaddr sockname;

sd = socket(AF_UNIX, SOCK_STREAM,0);

sockname.sa_family = AF_UNIX;

strcpy(sockname.sa_data,"my_socket");

if (connect(sd,&sockname,SIZEOFSOCKNAME) == -1)

{

perror("Bad connection");

exit();

}

write (sd,"Hello",5);

close(sd);

}

Run:

% host&

% client

Server received Hello

It was 5 bytes long

%

Example (binding to a port number):

struct sockaddr_in saddr;

bzero( &saddr, sizeof(saddr));

saddr.sin_family = AF_INET;

bcopy( hp -> h_addr, &saddr.sin_addr, hp -> h_length );

saddr.sin_port = htons(PORTNUM);

if (bind(sfd, &saddr, sizeof(saddr)) != 0)

{

perror("bind");

exit(-1);

}

7.8.4.7.3. CONNECTING A SOCKET TO A PORT

Port numbers define entry points for services provided by server applications. These services include: finger, whois and mail. A complete list of services can be found is /etc/services

gethostname(char hostname[256], sizeof(hostname))

Returns the name of the computer (host) that the program is running on.

The name is returned in hostname.

struct hostent *gethostbyname(hostname)

Returns a pointer to a structure hostent which contains some information about the host.

struct hostent

{

char *h_name;

char **h_aliases;

int h_addrtype;

int h_length;

char **h_addr_list;

};

7.8.4.7.4. LISTENING FOR CONNECTIONS
To accept connections a willingness to accept incoming connections, and a queue limit for incoming connections, are specified with listen. The listen call applies only to sockets of type SOCK_STREAM or SOCK_SEQPACKET.

The backlog parameter defines the maximum length the queue of pending connections may grow to. If a connection request arrives with the queue full the client may receive an error with an indication of ECONNREFUSED, or, if the underlying protocol supports retransmission, the request may be ignored so that retries may succeed.

Example:

if (listen(sfd,1) != 0)

{

perror("listen");

exit(-1);

}

7.8.4.7.5. ACCEPTING A CONNECTION

Once the listen() call is executed the program uses the accept() system call to signal that it is willing to accept connections to the socket. accept() takes the first connection request in the queue and creates another socket with the same properties as the original socket. accept() blocks until a request for connection is recieved.

#include <sys/types.h>

#include <sys/socket.h>

int accept (int sockfd, struct sockaddr *peer, int *addrlen);

where:

sockfd is the socket number of the original socket previously created with socket().

peer is a result parameter that is filled in with the address of the connecting entity, as known to the communications layer. The exact format of the peer parameter is determined by the domain in which the communication is occurring.

addrlen contains the amount of space pointed to by peer; on return it will contain the actual length (in bytes) of the address returned.

The new socket number is returned.

Example:

int sockffd,newsockfd;

if(( sockfd = socket( . . . )) < 0)

perror("socket error");

if(bind(sockfd, . . . ) <0)

perror("bind error");

if(listen(sockfd, 5) < 0)

perror("listen error");

for( ; ; ) [Accept connections forever]

{

newsockfd = accept(sockfd, . . . ); [Blocks]

perror("accept error");

if(fork() == 0)

{ [Child]

close(sockfd);

func(newsockfd); [Process Child]

exit(0);

}

close(newsockfd); [Parent]

[Look for another connection]

}

7.8.4.7.6. NETWORK BYTE ORDER

Unfortunately different computers use different byte orders to define words; ie: one computer might send the high order byte first while another computer might send the low order byte first. To accommodate this a "network" standard was defined. Every system provides function that converts the byte order from the local order to this standard and back. These functions are:

htons() host to networks short integer

ntohs() network to host short integer

htoni() host to network integer

ntohi() network to host integer

htonl() host to network long integer

ntohl() network to host long integer

Example:

To send data:

i = htoni(i);

write_data(s,&i,sizeof(i));

To receive data:

read_data(s,&i,sizeof(i));

i = ntohi(i);

Example:

void create_socket()

{

struct sockaddr_in saddr;

int slen;

int nb;

[Initialize the socket name and length ]

bzero( &saddr, sizeof(saddr));

saddr.sin_family = AF_INET;

bcopy( hp -> h_addr, &saddr.sin_addr, hp -> h_length );

saddr.sin_port = htons(PORTNUM);

Generate the file descriptor and Check for error in file descriptor

if((sfd = socket(AF_INET, SOCK_STREAM, 0))== -1)

{

perror("socket");

exit(-1);

}

[Bind the socket and check for error]

if (bind(sfd, &saddr, sizeof(saddr)) != 0)

{

perror("bind");

exit(-1);

}

slen = sizeof(saddr);

if (getsockname(sfd, &saddr, &slen) != 0)

{

perror("getsockname");

exit(-1);

}

/*

Listen and check for error

*/

if (listen(sfd,1) != 0)

{

perror("listen");

exit(-1);

}

prompt_term(argv,saddr.sin_port);

}

7.8.4.7.7. CONNECTING TO A SOCKET

For a process to connect to a socket it first has to create a socket with socket(), then connect with the connect() system call.

int connect(int s, struct sockaddr *name, int namelen)

where:

s is the file descriptor of the socket created by the client process.

name is a structure containing the address family and either the socket name or the machine address and port number.

namelen is the lenght of name.

void connect_socket()

{

struct sockaddr_in saddr;

char hostname[256];

struct hostent *hp;

int port,slen,i,j,nb;

[Get the name of the host machine]

gethostname(hostname, sizeof(hostname));

hp = gethostbyname(hostname);

[Assign various attributes]

bzero( &saddr, sizeof(saddr));

saddr.sin_family = AF_INET;

bcopy( hp -> h_addr, &saddr.sin_addr, hp -> h_length );

[Read the port number]

printf("Please input the port number\n");

scanf("%d", &port);

[Generate the socket]

saddr.sin_port = htons(port);

if((s = socket(AF_INET, SOCK_STREAM, 0))==-1)

{

perror("socket");

exit(-1);

}

[Connect the socket]

if (connect(s, &saddr, sizeof(saddr)) != 0)

{

perror("connect");

exit(-1);

}

}

7.8.4.7.8. SENDING AND RECEIVING DATA

To send and recieve data of a socket the select() system call is used to determine which file descriptors are ready for reading or writing.

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,

struct timeval *timeout)

where:

nfds determines the maximum number of file descriptors to check.

readfds are the file descriptors that could be waiting to be read.

writefds are the file descriptors that could be waiting to be writen to.

exceptfds are the file descriptors that could have exceptional conditions waiting.

timeout if not NULL indicates the maximum time to wait.

void select_I_O()

{

fd_set rmask;

int nf,nfld;

struct timeval *timeout;

timeout =(struct timeval *)NULL;

nfld =s+1; [Number of fields is socket number + 1]

for(;;)

{

FD_ZERO(&rmask);[Clear the mask]

FD_SET(0,&rmask);[set flag for stdin (0)]

FD_SET(s,&rmask);[set flag for socket]

nf=select(nfld,&rmask,(fd_set*)NULL,

(fd_set *)NULL, (struct timeval *)NULL);

if(nf==(-1))

{

perror(select);

exit(130);

}

if(FD_ISSET(0,&rmask)) socket_send();

if(FD_ISSET(s,&rmask)) receive();

}

}