Inter Process Communication
Inter Process Communication
The POSIX thread libraries are a standards based thread API for C/C++. It allows
one to spawn a new concurrent process flow. It is most effective on multi-
processor or multi-core systems where the process flow can be scheduled to run
on another processor thus gaining speed through parallel or distributed
processing. Threads require less overhead than "forking" or spawning a new
process because the system does not initialize a new system virtual memory
space and environment for the process. While most effective on a multiprocessor
system, gains are also found on uniprocessor systems which exploit latency in
I/O and other system functions which may halt process execution. (One thread
may execute while another is waiting for I/O or some other system latency.)
Parallel programming technologies such as MPI and PVM are used in a
distributed computing environment while threads are limited to a single computer
system. All threads within a process share the same address space. A thread is
spawned by defining a function and its arguments which will be processed in the
thread. The purpose of using the POSIX thread library in your software is to
execute software faster.
Thread Basics:
Example: pthread1.c
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
main()
{
pthread_t thread1, thread2;
char *message1 = "Thread 1";
char *message2 = "Thread 2";
int iret1, iret2;
Compile:
Details:
• In this example the same function is used in each thread. The arguments
are different. The functions need not be the same.
• Threads terminate by explicitly calling pthread_exit, by letting the function
return, or by a call to the function exit which will terminate the process
including any threads.
• Function call: pthread_create - create a new thread
Arguments:
Arguments:
Arguments:
This routine kills the thread. The pthread_exit function never returns. If
the thread is not detached, the thread id and return value may be
examined from another thread by using pthread_join.
Note: the return pointer *retval, must not be of local scope otherwise it
would cease to exist once the thread terminates.
• [C++ pitfalls]: The above sample program will compile with the GNU C
and C++ compiler g++. The following function pointer representation below
will work for C but not C++. Note the subtle differences and avoid the
pitfall below:
Thread Synchronization:
Mutexes:
Mutexes are used to prevent data inconsistencies due to operations by multiple
threads upon the same memory area performed at the same time or to prevent
race conditions where an order of operation upon the memory is expected. A
contention or race condition often occurs when two or more threads need to
perform operations on the same memory area, but the results of computations
depends on the order in which these operations are performed. Mutexes are
used for serializing shared resources such as memory. Anytime a global
resource is accessed by more than one thread the resource should have a Mutex
associated with it. One can apply a mutex to protect a segment of memory
("critical region") from other threads. Mutexes can be applied only to threads in a
single process and do not work between processes as do semaphores.
print? print?
1 int counter=0; /* Note scope of variable and mutex are the
01
2 same */
pthread_mutex_t mutex1 =
3 /* Function C */ 02
PTHREAD_MUTEX_INITIALIZER;
4 void functionC()
03 int counter=0;
5 {
04
6
05 /* Function C */
7 counter++
06 void functionC()
8 07 {
9 } 08 pthread_mutex_lock( &mutex1 );
09 counter++
10 pthread_mutex_unlock( &mutex1 );
11 }
Possible execution sequence
Thread 1 Thread 2 Thread 1 Thread 2
counter = counter =
counter = 0 counter = 0
0 0
counter = counter = Thread 2 locked out.
counter = 1 Thread 1 has exclusive use of variable
1 1
counter
counter = 2
If register load and store operations for the incrementing of variable counter
occurs with unfortunate timing, it is theoretically possible to have each thread
increment and overwrite the same variable with the same value. Another
possibility is that thread two would first increment counter locking out thread one
until complete and then thread one would increment it to 2.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
void *functionC();
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
main()
{
int rc1, rc2;
pthread_t thread1, thread2;
exit(0);
}
void *functionC()
{
pthread_mutex_lock( &mutex1 );
counter++;
printf("Counter value: %d\n",counter);
pthread_mutex_unlock( &mutex1 );
}
Counter value: 1
Counter value: 2
When a mutex lock is attempted against a mutex which is held by another thread,
the thread is blocked until the mutex is unlocked. When a thread terminates, the
mutex does not unless explicitly unlocked. Nothing happens by default.
Man Pages:
Joins:
A join is performed when one wants to wait for a thread to finish. A thread calling
routine may launch multiple threads then wait for them to finish to get the results.
One waits for the completion of the threads with a join.
#define NTHREADS 10
void *thread_function(void *);
pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
int counter = 0;
main()
{
pthread_t thread_id[NTHREADS];
int i, j;
/* Now that all threads are complete I can print the final result.
*/
/* Without the join I could be printing a value before all the
threads */
/* have been completed.
*/
Condition Variables:
• Creating/Destroying:
o pthread_cond_init
o pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
o pthread_cond_destroy
• Waiting on condition:
o pthread_cond_wait - unlocks the mutex and waits for the condition
variable cond to be signaled.
o pthread_cond_timedwait - place limit on how long it will block.
• Waking thread based on condition:
o pthread_cond_signal - restarts one of the threads that are waiting
on the condition variable cond.
o pthread_cond_broadcast - wake up all threads blocked by the
specified condition variable.
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
main()
{
pthread_t thread1, thread2;
exit(0);
}
void *functionCount1()
{
for(;;)
{
// Lock mutex and then wait for signal to relase mutex
pthread_mutex_lock( &count_mutex );
pthread_mutex_unlock( &count_mutex );
void *functionCount2()
{
for(;;)
{
pthread_mutex_lock( &count_mutex );
pthread_mutex_unlock( &count_mutex );
Note that functionCount1() was halted while count was between the values
COUNT_HALT1 and COUNT_HALT2. The only thing that has been ensures is
that functionCount2 will increment the count between the values
COUNT_HALT1 and COUNT_HALT2. Everything else is random.
The logic conditions (the "if" and "while" statements) must be chosen to insure
that the "signal" is executed if the "wait" is ever processed. Poor software logic
can also lead to a deadlock condition.
Note: Race conditions abound with this example because count is used as the
condition and can't be locked in the while statement without causing deadlock.
Thread Scheduling:
When this option is enabled, each thread may have its own scheduling
properties. Scheduling attributes may be specified:
The threads library provides default values that are sufficient for most cases.
Thread Pitfalls:
• Race conditions: While the code may appear on the screen in the order
you wish the code to execute, threads are scheduled by the operating
system and are executed at random. It cannot be assumed that threads
are executed in the order they are created. They may also execute at
different speeds. When threads are executing (racing to complete) they
may give unexpected results (race condition). Mutexes and joins must be
utilized to achieve a predictable execution order and outcome.
• Thread safe code: The threaded routines must call functions which are
"thread safe". This means that there are no static or global variables which
other threads may clobber or read assuming single threaded operation. If
static or global variables are used then mutexes must be applied or the
functions must be re-written to avoid the use of these variables. In C, local
variables are dynamically allocated on the stack. Therefore, any function
that does not use static data or other shared resources is thread-safe.
Thread-unsafe functions may be used by only one thread at a time in a
program and the uniqueness of the thread must be ensured. Many non-
reentrant functions return a pointer to static data. This can be avoided by
returning dynamically allocated data or using caller-provided storage. An
example of a non-thread safe function is strtok which is also not re-
entrant. The "thread safe" version is the re-entrant version strtok_r.
• Mutex Deadlock: This condition occurs when a mutex is applied but then
not "unlocked". This causes program execution to halt indefinitely. It can
also be caused by poor application of mutexes or joins. Be careful when
applying two or more mutexes to a section of code. If the first
pthread_mutex_lock is applied and the second pthread_mutex_lock fails
due to another thread applying a mutex, the first mutex may eventually
lock all other threads from accessing data including the thread which holds
the second mutex. The threads may wait indefinitely for the resource to
become free causing a deadlock. It is best to test and if failure occurs, free
the resources and stall before retrying.
...
pthread_mutex_lock(&mutex_1);
while ( pthread_mutex_trylock(&mutex_2) ) /* Test if already locked
*/
{
pthread_mutex_unlock(&mutex_1); /* Free resource to avoid deadlock
*/
...
/* stall here */
...
pthread_mutex_lock(&mutex_1);
}
count++;
pthread_mutex_unlock(&mutex_1);
pthread_mutex_unlock(&mutex_2);
...
The order of applying the mutex is also important. The following code segment
illustrates a potential for deadlock:
void *function1()
{
...
pthread_mutex_lock(&lock1); // Execution step 1
pthread_mutex_lock(&lock2); // Execution step 3 DEADLOCK!!!
...
...
pthread_mutex_lock(&lock2);
pthread_mutex_lock(&lock1);
...
}
void *function2()
{
...
pthread_mutex_lock(&lock2); // Execution step 2
pthread_mutex_lock(&lock1);
...
...
pthread_mutex_lock(&lock1);
pthread_mutex_lock(&lock2);
...
}
main()
{
...
pthread_create(&thread1, NULL, function1, NULL);
pthread_create(&thread2, NULL, function2, NULL);
...
}
• If function1 acquires the first mutex and function2 acquires the second,
all resources are tied up and locked.
• Condition Variable Deadlock: The logic conditions (the "if" and "while"
statements) must be chosen to insure that the "signal" is executed if the
"wait" is ever processed.
Process:
fork():
The fork() system call will spawn a new child process which is an identical
process to the parent except that has a new system process ID. The process is
copied in memory from the parent and a new process structure is assigned by
the kernel. The return value of the function is which discriminates the two threads
of execution. A zero is returned by the fork function in the child's process.
Example:
#include <iostream>
#include <string>
int globalVariable = 2;
main()
{
string sIdentifier;
int iStackVariable = 20;
Man Pages:
vfork():
The vfork() function is the same as fork() except that it does not make a copy
of the address space. The memory is shared reducing the overhead of spawning
a new process with a unique copy of all the memory. This is typically used when
using fork() to exec() a process and terminate. The vfork() function also
executes the child process first and resumes the parent process when the child
terminates.
#include <iostream>
#include <string>
int globalVariable = 2;
main()
{
string sIdentifier;
int iStackVariable = 20;
Note: The child process executed first, updated the variables which are shared
between the processes and NOT unique, and then the parent process executes
using variables which the child has updated.
[Potential Pitfall]: A deadlock condition may occur if the child process does not
terminate, the parent process will not proceed.
Man Pages:
The function clone() creates a new child process which shares memory, file
descriptors and signal handlers with the parent. It implements threads and thus
launches a function as a child. The child terminates when the parent terminates.
See the YoLinux POSIX threads tutorial
Man Pages:
wait():
The parent process will often want to wait until all child processes have been
completed. this can be implemented with the wait() function call.
wait(): Blocks calling process until the child process terminates. If child process
has already teminated, the wait() call returns immediately. if the calling process
has multiple child processes, the function returns when one returns.
waitpid(): Options available to block calling process for a particular child process
not the first one.
Code snipet:
#include <sys/wait.h>
...
...
int childExitStatus;
if( WIFEXITED(childExitStatus) )
{
// Child process exited thus exec failed.
// LOG failure of exec in child process.
cout << "Result of waitpid: Child process exited thus exec
failed." << endl;
}
OR
...
int childExitStatus;
if( !WIFEXITED(childExitStatus) )
{
cerr << "waitpid() exited with an error: Status= "
<< WEXITSTATUS(childExitStatus)
<< endl;
}
else if( WIFSIGNALED(childExitStatus) )
{
cerr << "waitpid() exited due to a signal: "
<< WTERMSIG(childExitStatus)
<< endl;
}
Notes:
Man Pages:
Avoids orphaned process group when parent terminates. When parent dies, this
will be a zombie. (No parent process. Parent=1) Instead, create a new process
group for the child. Later process the group is terminated to stop all spawned
processes. Thus all subsequent processes should be of this group if they are to
be terminated by the process group id. Process group leader has the same
process id and group process id. If not changed then the process group is that of
the parent. Set the process group id to that of the child process.
#include <sys/types.h>
#include <unistd.h>
#ifdef __gnu_linux__
pid_t pgid = setpgid(child_pID, child_pID);
#endif
...
...
Use the setgid call to set the group id of the current process. Requires
root access.
The macro testing for __gnu_linux__ is for cross platform support as man
other OS's use a different system call.
Man Pages:
...
...
Man Pages:
#include <stdio.h>
#include <stdlib.h>
main()
{
system("ls -l");
printf("Command done!");
}
The statement "Command done!" will not print untill the "ls -l" command
has completed.
The popen() call opens a process by creating a pipe, forking, and invoking the
shell (bourne shell on Linux). The advantage to using popen() is that it will allow
one to interrogate the results of the command issued.
This example opens a pipe which executes the shell command "ls -l". The
results are read and printed out.
#include <stdio.h>
main()
{
FILE *fpipe;
char *command="ls -l";
char line[256];
if ( !(fpipe = (FILE*)popen(command,"r")) )
{ // If fpipe is NULL
perror("Problems with pipe");
exit(1);
}
#include <stdio.h>
main()
{
FILE *fpipe;
char *command="ls -l";
char line[256];
if ( !(fpipe = (FILE*)popen(command,"r")) )
{ // If fpipe is NULL
perror("Problems with pipe");
exit(1);
}
Man Pages:
The function call "execl()" initiates a new program in the same environment in
which it is operating. An executable (with fully qualified path. i.e. /bin/ls) and
arguments are passed to the function. Note that "arg0" is the command/file name
to execute.
int execl(const char *path, const char *arg0, const char *arg1, const
char *arg2, ... const char *argn, (char *) 0);
#include <unistd.h>
main()
{
execl("/bin/ls", "/bin/ls", "-r", "-t", "-l", (char *) 0);
}
Where all function arguments are null terminated strings. The list of
arguments is terminated by NULL.
The routine execlp() will perform the same purpose except that it will use
environment variable PATH to determine which executable to process. Thus a fully
qualified path name would not have to be used. The first argument to the function
could instead be "ls". The function execlp() can also take the fully qualified
name as it also resolves explicitly.
Man Pages:
This is the same as execl() except that the arguments are passed as null
terminated array of pointers to char. The first element "argv[0]" is the command
name.
#include <unistd.h>
main()
{
char *args[] = {"/bin/ls", "-r", "-t", "-l", (char *) 0 };
execv("/bin/ls", args);
}
The routine execvp() will perform the same purpose except that it will use
environment variable PATH to determine which executable to process. Thus a fully
qualified path name would not have to be used. The first argument to the function
could instead be "ls". The function execvp() can also take the fully qualified
name as it also resolves explicitly.
Man Pages:
execve():
Assignment:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
// Class definition:
class CReadEnvironmentVariablesFile
{
public:
CReadEnvironmentVariablesFile() { m_NumberOfEnvironmentVariables=0;
};
~CReadEnvironmentVariablesFile();
char **ReadFile(string& envFile);
private:
int m_NumberOfEnvironmentVariables;
char **m_envp;
};
Call execve:
main()
{
string envFile("environment_variables.conf");
CReadEnvironmentVariablesFile readEnvFile;
char **Env_envp = readEnvFile.ReadFile(envFile);
// Command to execute
char *Env_argv[] = { "/bin/ls", "-l", "-a", (char *) 0 };
cout << "Failure! execve error code=" << execReturn << endl;
Handle errors:
#ifdef EACCES
case EACCES :
{
return "EACCES Permission denied";
}
#endif
#ifdef EPERM
case EPERM :
{
return "EPERM Not super-user";
}
#endif
#ifdef E2BIG
case E2BIG :
{
return "E2BIG Arg list too long";
}
#endif
#ifdef ENOEXEC
case ENOEXEC :
{
return "ENOEXEC Exec format error";
}
#endif
#ifdef EFAULT
case EFAULT :
{
return "EFAULT Bad address";
}
#endif
#ifdef ENAMETOOLONG
case ENAMETOOLONG :
{
return "ENAMETOOLONG path name is too long ";
}
#endif
#ifdef ENOENT
case ENOENT :
{
return "ENOENT No such file or directory";
}
#endif
#ifdef ENOMEM
case ENOMEM :
{
return "ENOMEM Not enough core";
}
#endif
#ifdef ENOTDIR
case ENOTDIR :
{
return "ENOTDIR Not a directory";
}
#endif
#ifdef ELOOP
case ELOOP :
{
return "ELOOP Too many symbolic links";
}
#endif
#ifdef ETXTBSY
case ETXTBSY :
{
return "ETXTBSY Text file busy";
}
#endif
#ifdef EIO
case EIO :
{
return "EIO I/O error";
}
#endif
#ifdef ENFILE
case ENFILE :
{
return "ENFILE Too many open files in system";
}
#endif
#ifdef EINVAL
case EINVAL :
{
return "EINVAL Invalid argument";
}
#endif
#ifdef EISDIR
case EISDIR :
{
return "EISDIR Is a directory";
}
#endif
#ifdef ELIBBAD
case ELIBBAD :
{
return "ELIBBAD Accessing a corrupted shared lib";
}
#endif
default :
{
std::string errorMsg(strerror(errnum));
if ( errnum ) return errorMsg;
}
}
}
Man Pages:
PATH=/usr/bin:/bin
MANPATH=/opt/man
LANG=C
DISPLAY=:0.0
Man Pages:
Note: Don't mix malloc() and new. Choose one form of memory allocation and
stick with it.
Malloc:
..
...
int ii;
...
// Allocate arrays of character strings.
int ii;
for(ii=0; ii < NumberOfEnvironmentVariables; ii++)
{
// NULL terminated
m_envp[ii] = (char *) malloc(vEnvironmentVariables[ii].size()+1);
strcpy( m_envp[ii], vEnvironmentVariables[ii].c_str());
}
return m_envp;
}
...
Free:
...
free(m_envp);
...
Socket
Socket programming and the C BSD API
C and C++ sockets programming with examples of the BSD API on the Linux
platform
Sockets allow one process to communicate with another whether it is local on the
same computer system or remote over the network. Many other higher level
protocols are built upon sockets technology.
The sockets API provides many configuration options so we will try and cover the
socket API components and then give examples of a few implementations. It
would be very difficult to cover all variations of its use.
Sockets utilize the following standard protocols:
Protocol Description
IP Internet Protocol provides network routing using IP addressing eg
192.168.1.204
UDP User Datagram Protocol - IP with ports to distinguish among
processes running on same host. No data verification.
TCP Transmission Control Protocol - IP with ports to distinguish
among processes running on same host. Connection oriented,
stream transfer, full duplex, reliable with data verification.
Typically one configures a socket server to which a socket client may attach and
communicate. The IP protocol layer will also require that the domain name or IP
addresses of the communicating processes be made known as well. Within the
IP protocol it is also important to provide the mechanism used: TCP or UDP.
The BSD is a "C" programming API. Examples shown are compiled using the
GNU C++ compiler on Linux:
#include <iostream>
#include <sys/types.h> // Types used in sys/socket.h and netinet/in.h
#include <netinet/in.h> // Internet domain address structures and
functions
#include <sys/socket.h> // Structures and functions used for socket API
#include <netdb.h> // Used for domain/DNS hostname lookup
#include <unistd.h>
#include <errno.h>
main()
{
int socketHandle;
// create socket
...
...
}
o TCP: SOCK_STREAM
o UDP: SOCK_DGRAM
o Raw protocol at network layer: SOCK_RAW
Also see:
o Socket server:
bind(): bind the socket to a local socket address. This
assigns a name to the socket.
listen(): listen for connections on a socket created with
"socket()" and "bind()" and accept incoming connections.
This is used for TCP and not UDP. Zero is returned on
success.
accept(): accept a connection on a socket. Accept the first
connection request on the queue of pending connections,
create a new connected socket with mostly the same
properties as defined by the call to "socket()", and allocate a
new file descriptor for the socket, which is returned. The
newly created socket is no longer in the listening state. Note
this call blocks until a client connects.
...
...
#define MAXHOSTNAME 256
...
...
socketInfo.sin_family = AF_INET;
// Use any address available to the system. This is a typical
configuration for a server.
// Note that this is where the socket client and socket server
differ.
// A socket client will specify the server address to connect to.
socketInfo.sin_addr.s_addr = htonl(INADDR_ANY); // Translate long
integer to network byte order.
socketInfo.sin_port = htons(portNumber); // Set port number
listen(socketHandle, 1);
int socketConnection;
if( (socketConnection = accept(socketHandle, NULL, NULL)) < 0)
{
close(socketHandle);
exit(EXIT_FAILURE);
}
...
...
o Socket functions:
bind():
Function prototype:
Bind arguments:
listen():
Function prototype:
Listen arguments:
For very heavy server use, modify the system limit in the
proc file and set "backlog" to the same value (eg. 512).
Returns 0: Sucess, -1: Failure and errno may be set.
accept():
Function prototype:
Accept arguments:
...
...
// Load system information for remote socket server into socket data
structures
...
...
o Connect function prototype:
int connect(int sockfd, const struct sockaddr *serv_addr,
socklen_t addrlen);
Connect arguments: (Same as server's bind() arguments)
/* Internet address. */
struct in_addr {
__u32 s_addr; /* Defined as 32 or 64 bit address (system
dependent) */
};
•
o Note:
IP addresses: Note the specific IP address could be
specified:
#include <arpa/inet.h> // IP from string
conversion
socketInfo.sin_addr.s_addr =
inet_addr("127.0.0.1");
cout << inet_ntoa(socketInfo.sin_addr) << endl;
socketInfo.sin_addr.s_addr = htonl(INADDR_ANY);
socketInfo.sin_port = htons(INADDR_ANY);
Use send() and recv(), or write() and read(), or sendto() and recvfrom() to
read/write to/from a socket.
char *pcIpAddress;
unsigned short shPort;
...
...
if (iSocketType == SOCK_STREAM)
{
rc = recv(socketHandle, (char*) _pcMessage, (int) _iMessageLength,
0);
if ( rc == 0 )
{
cerr << "ERROR! Socket closed" << endl;
}
else if (rc == -1)
{
cerr << "ERROR! Socket error" << endl;
closeSocket();
}
}
else if (iSocketType == SOCK_DGRAM)
{
int iLength;
struct sockaddr_in stReceiveAddr;
iLength = (int) sizeof(struct sockaddr_in);
memset((void*) &stReceiveAddr, 0, iLength);
pcIpAddress = inet_ntoa(stReceiveAddr.sin_addr);
shPort = ntohs(stReceiveAddr.sin_port);
cout << "Socket Received: " << _iNumRead << " bytes from "
<< pcIpAddress << ":" << shPort << endl;
...
...
...
...
cout << "Number of bytes read: " << count << endl;
cout << "Received: " << buf << endl;
}
...
...
send(): send a message from a socket. Used only when in a connected state.
The only difference between send and write is the presence of flags. With zero
flags parameter, send is equivalent to write.
#include <string.h>
...
...
char buf[512];
strcpy(buf,"Message to send");
...
...
send(socketHandle, buf, strlen(buf)+1, 0);
...
...
TCP send() or UDP sendto():
int iSocketType;
int iBytesSent = 0;
char *pMessage = "message to send";
int iMessageLength = 16; // number of bytes (includes NULL
termination)
sockaddr pSendAddress;
...
...
if (iSocketType == SOCK_STREAM)
{
if ((iBytesSent = send(socketHandle, (char*) pMessage, (int)
iMessageLength, 0)) < 0 )
{
cerr << "Send failed with error " << errno << endl;
close(socketHandle);
}
}
else if (iSocketType == SOCK_DGRAM)
{
if ((iBytesSent = sendto(socketHandle, (char*) pMessage,
(int) iMessageLength,
0,
(struct sockaddr*) pSendAddress,
(int) sizeof(struct sockaddr_in))) < 0 )
{
cerr << "Sendto failed with error " << errno << endl;
close();
}
}
else
{
// Failed - Socket type not defined
}
...
...
Close the socket when done:
#include <unistd.h>
...
close(socketHandle);
...
Socket Server:
Simple Socket Server:
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#define MAXHOSTNAME 256
using namespace std;
main()
{
struct sockaddr_in socketInfo;
char sysHost[MAXHOSTNAME+1]; // Hostname of this computer we are
running on
struct hostent *hPtr;
int socketHandle;
int portNumber = 8080;
// create socket
socketInfo.sin_family = AF_INET;
socketInfo.sin_addr.s_addr = htonl(INADDR_ANY); // Use any address
available to the system
socketInfo.sin_port = htons(portNumber); // Set port number
listen(socketHandle, 1);
int socketConnection;
if( (socketConnection = accept(socketHandle, NULL, NULL)) < 0)
{
exit(EXIT_FAILURE);
}
close(socketHandle);
File: serverFork.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#define MAXHOSTNAME 256
using namespace std;
main()
{
struct sockaddr_in socketInfo;
char sysHost[MAXHOSTNAME+1]; // Hostname of this computer we are
running on
struct hostent *hPtr;
int socketHandle;
int portNumber = 8080;
signal(SIGCHLD, handleSig);
// create socket
socketInfo.sin_family = AF_INET;
socketInfo.sin_addr.s_addr = htonl(INADDR_ANY); // Use any address
available to the system
socketInfo.sin_port = htons(portNumber); // Set port number
listen(socketHandle, 1);
int socketConnection;
for(;;) // infinite loop to handle remote connections. This should
be limited.
{
if( (socketConnection = accept(socketHandle, NULL, NULL)) < 0)
{
close(socketHandle);
if(errno == EINTR) continue;
perror("accept");
exit(EXIT_FAILURE);
}
switch(fork())
{
case -1:
perror("fork");
close(socketHandle);
close(socketConnection);
exit(EXIT_FAILURE);
case 0: // Child process - do stuff
close(socketHandle);
// Do your server stuff like read/write messages to the
socket here!
exit(0);
default: // Parent process, look for another connection
close(socketConnection);
continue;
}
}
For more on the use of the fork() function see the YoLinux.com fork() tutorial.
Socket Client:
File: client.cpp
#include <iostream>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <errno.h>
#define MAXHOSTNAME 256
using namespace std;
main()
{
struct sockaddr_in remoteSocketInfo;
struct hostent *hPtr;
int socketHandle;
char *remoteHost="localhost";
int portNumber = 8080;
// create socket
strcpy(buf,"Message to send");
send(socketHandle, buf, strlen(buf)+1, 0);
}
Function Description
gethostname(char *name, size_t len) returns hostname of local host
getservbyname(const char returns a structure of type servent for the
*name,const char *proto) given host name and protocol
gethostbyname(const char *name) returns a structure of type hostent for the
given host name
getservbyport(int port, const char returns a servent structure for the line that
*proto) matches the port port given in network byte
order using protocol proto. If proto is NULL,
any protocol will be matched.
getservent(void) returns a structure servent containing the
broken out fields from the line in
/etc/services
Socket options:
One can "get" (read) the current socket options or "set" them to new values. The
default values are obtained from the OS:
Function Prototypes:
int getsockopt(int s, int level, int optname, void *optval,
socklen_t *optlen);
int setsockopt(int s, int level, int optname, const void *optval,
socklen_t optlen);
getsockopt/setsockopt arguments:
File: printSocketOptions.c
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <stdio.h>
int main()
{
int socketHandle;
// create socket
int iSocketOption = 0;
int iSocketOptionLen = sizeof(int);;
getsockopt man page: get a particular socket option for the specified
socket.
• Socket "keep-alive":
• Socket re-use:
linger Option;
Option.l_onoff = 1;
Option.l_linger = 0;
This allows the socket to die quickly and allow the address to be reused again.
Warning: This linger configuration specified may/will result in data loss upon
socket termination, thus it would not have the robustness required for a banking
transaction but would be ok for a recreational app.
• Broadcast:
setsockopt man page: set a particular socket option for the specified socket.
Function to test if a socket or set of sockets has data and can be read (Test so
you don't get blocked on a read) or written.
#include <sys/select.h>
#include <sys/time.h>
...
...
timeToWait.tv_sec = 0;
timeToWait.tv_usec = _lWaitTimeMicroseconds;
FD_ZERO(&readSetOfSockets);
FD_SET(_socketHandle, &readSetOfSockets);
close(_socketHandle);
return false;
}
return false;
}
Arguments to select():
int nBytes = 8;
setsockopt(socketHandle, SOL_SOCKET, SO_RCVLOWAT,(const char *)
&nBytes, sizeof(int))
Port Numbers:
The use of port numbers below 1024 are limited to the root process. For a list of
port numbers with established uses see the file /etc/services.
Posts are 16 bit identifiers. Many are reserved and managed by the Internet
Assigned Numbers Authority (IANA).
See RFC 1700.
Note that when transferring data between different platforms or with Java, the
byte order endianess will have to be considered. The network (the neutral
medium) byte order is Big Endian and the byte order to which data is usually
marshalled.
Also note: Java processes and stores data in big endian byte ordering on any
platform.
Character data is not subject to this problem as each character is one byte in
length but integer is.
Integer data can be converted from/to host or network byte order with the
following routines:
Function Description
Network to host byte order conversion for long integer data
ntohl()
(uint32_t)
Network to host byte order conversion for short integer data
ntohs()
(uint16_t)
Host to network byte order conversion for long integer data
htonl()
(uint32_t)
Host to network byte order conversion for short integer data
htons()
(uint16_t)
Requires #include <arpa/inet.h>
The routines are aware of the processor they are running on and thus
either perform a conversion or not, depending if it is required.
Man pages:
Note that one uses these calls for portable code on any platform. The port of the
library to that platform determines whether a byte swap will occur and not your
source code.
Include file/code snipit to determine the processor endianess:
File: is_bigendian.h
#ifndef __IS_BIGENDIAN__
#define __IS_BIGENDIAN__
#endif // __IS_BIGENDIAN__
Code snipit to swap bytes "in place" and convert endian:
#include <netdb.h>
#include "is_bigendian.h"
/**
In-place swapping of bytes to match endianness of hardware
if(!is_bigendian())
{
for ( start = (unsigned char *)_object, end = start + _size - 1;
start < end; ++start, --end )
{
unsigned char swap = *start;
*start = *end;
*end = swap;
}
}
}
#include <netdb.h>
#include "is_bigendian.h"
struct ExampleA
{
#ifdef BIGENDIAN
unsigned int a:1;
unsigned int b:1;
unsigned int c:1;
unsigned int d:1;
unsigned int e:4;
unsigned int f:8;
unsigned int g:8;
unsigned int h:8;
#else
unsigned int h:8;
unsigned int g:8;
unsigned int f:8;
unsigned int e:4;
unsigned int d:1;
unsigned int c:1;
unsigned int b:1;
unsigned int a:1;
#endif
};
...
...
// Bits:
// |B31....B25 |B24....B16 |B15....B8 |B7....B0 | Big endian
// |B7....B0 |B15....B8 |B24....B16 |B31....B25 | Little endian
// Code body
...
...
return tmplg;
}
...
...
int iTry = 0;
int mRetrymax = 10;
int mRetrywait = 2;
// If this client can not connect to the server, try again after period
"Retrywait".
...
• Check return codes: Just because the socket was opened doesn't mean
it will stay open. Dropped connections do happen. Check all read/write
socket API function return codes:
o return code > 0: Data received/sent
o return code == 0: Socket closed
o return code == -1: Check system errno and interpret (or call
"perror()")
For even more robust code for a socket client, close, then open a new
socket connection for return codes 0 or -1.
Note that with a UDP client/server communications, the socket read will
read the whole frame sent. This is not necessarily true for a TCP stream.
The function used to read the stream will return the number of bytes read.
If the required number of bytes have not been read, repeat the read for the
remaining bytes until the whole message has been read.
In this example, our message header is a single integer used to store the
message size in the first 32 bits of the message.
• Check return codes: Just because the socket was opened doesn't mean
it will stay open. Dropped connections do happen. Check all read/write
socket API function return codes:
o return code > 0: Data received/sent
o return code == 0: Socket closed
o return code == -1: Check system errno and interpret (or call
"perror()")
For even more robust code for a socket client, close, then open a new
socket connection for return codes 0 or -1.
Note that with a UDP client/server communications, the socket read will
read the whole frame sent. This is not necessarily true for a TCP stream.
The function used to read the stream will return the number of bytes read.
If the required number of bytes have not been read, repeat the read for the
remaining bytes until the whole message has been read.
In this example, our message header is a single integer used to store the
message size in the first 32 bits of the message.
• Check return codes: Just because the socket was opened doesn't mean
it will stay open. Dropped connections do happen. Check all read/write
socket API function return codes:
o return code > 0: Data received/sent
o return code == 0: Socket closed
o return code == -1: Check system errno and interpret (or call
"perror()")
For even more robust code for a socket client, close, then open a new
socket connection for return codes 0 or -1.
Note that with a UDP client/server communications, the socket read will
read the whole frame sent. This is not necessarily true for a TCP stream.
The function used to read the stream will return the number of bytes read.
If the required number of bytes have not been read, repeat the read for the
remaining bytes until the whole message has been read.
In this example, our message header is a single integer used to store the
message size in the first 32 bits of the message.
...
...
...
...
• On "SOCK_STREAM" (TCP) sockets, this flag requests that the recv()
function block until the full amount of data specified (length) can be
returned. The function may return the smaller amount of data if the socket
is a message-based socket, if a signal is caught or if the connection is
terminated.
• UDP message order: TCP will guarentee that the order of the message
delivery is maintained. UDP will not guarentee the message delivery order
and thus you will have to maintain a counter in the message if the order is
important.
• Signals: The socket layer can issue signals, which if not handled, can
terminate your application. When a socket is terminated at one end, the
other may receive a SIGPIPE signal.
•
• Note GDB will report a received signal even if it's being ignored by the
application.
In this example, we tried to send() over a broken socket link.
#include <stdlib.h>
#include <signal.h>
int main()
{
/// Ignore SIGPIPE "Broken pipe" signals when socket connections are
broken.
signal(SIGPIPE, handleSigpipe);
...
...
}
• Also see:
o C++ Signal class tutorial - YoLinux.com tutorial
o signal: Signal handling
o sigaction: examine and change a signal action
Interrogate a socket:
Read/Write:
Convert:
There are two Linux C/C++ library types which can be created:
1. Static libraries (.a): Library of object code which is linked with, and
becomes part of the application.
2. Dynamically linked shared object libraries (.so): There is only one form of
this library but it can be used in two ways.
1. Dynamically linked at run time but statically aware. The libraries
must be available during compile/link phase. The shared objects
are not included into the executable component but are tied to the
execution.
2. Dynamically loaded/unloaded and linked during execution (i.e.
browser plug-in) using the dynamic linking loader system functions.
•
o ctest1.c
void ctest1(int *i)
{
*i=5;
}
o ctest2.c
void ctest2(int *i)
{
*i=100;
}
o prog.c
#include <stdio.h>
void ctest1(int *);
void ctest2(int *);
int main()
{
int x;
ctest1(&x);
printf("Valx=%d\n",x);
return 0;
}
Historical note: After creating the library it was once necessary to run the
command: ranlib ctest.a. This created a symbol table within the archive.
Ranlib is now embedded into the "ar" command.
Note for MS/Windows developers: The Linux/Unix ".a" library is conceptually the
same as the Visual C++ static ".lib" libraries.
How to generate a shared object: (Dynamically linked object library file.) Note
that this is a two step process.
Compiler options:
Where the name of the library is libctest.so. (This is why you must
create the symbolic links or you will get the error "/usr/bin/ld: cannot find
-lctest".)
The libraries will NOT be included in the executable but will be
dynamically linked during runtime execution.
List Dependencies:
The shared library dependencies of the executable can be listed with the
command: ldd name-of-executable
Run Program:
Man Pages:
Links:
• LDP: Shared libraries
Library Path:
In order for an executable to find the required libraries to link with during run time,
one must configure the system so that the libraries can be found. Methods
available: (Do at least one of the following)
Sample: /etc/ld.so.conf
/usr/X11R6/lib
/usr/lib
...
..
/usr/lib/sane
/usr/lib/mysql
/opt/lib
Add the library path to this file and then execute the command (as root)
ldconfig to configure the linker run-time bindings.
You can use the "-f file-name" flag to reference another configuration file
if you are developing for different environments.
See man page for command ldconfig.
OR
This will NOT permanently configure the system to include this directory.
The information will be lost upon system reboot.
OR
...
if [ -d /opt/lib ];
then
LD_LIBRARY_PATH=/opt/lib:$LD_LIBRARY_PATH
fi
...
export LD_LIBRARY_PATH
This instructs the run time loader to look in the path described by the
environment variable LD_LIBRARY_PATH, to resolve shared libraries. This
will include the path /opt/lib.
Library paths used should conform to the "Linux Standard Base" directory
structure.
Library Info:
The command "nm" lists symbols contained in the object file or shared library.
0000000000100988 A __bss_start
000000000000068c T ctest1
00000000000006a0 T ctest2
w __cxa_finalize
00000000001007b0 A _DYNAMIC
0000000000100988 A _edata
0000000000100990 A _end
00000000000006f8 T _fini
0000000000100958 A _GLOBAL_OFFSET_TABLE_
w __gmon_start__
00000000000005b0 T _init
w _Jv_RegisterClasses
Library Versions:
Library versions should be specified for shared objects if the function interfaces
are expected to change (C++ public/protected class definitions), more or fewer
functions are included in the library, the function prototype changes (return data
type (int, const int, ...) or argument list changes) or data type changes (object
definitions: class data members, inheritance, virtual functions, ...).
The library version can be specified when the shared object library is created. If
the library is expected to be updated, then a library version should be specified.
This is especially important for shared object libraries which are dynamically
linked. This also avoids the Microsoft "DLL hell" problem of conflicting libraries
where a system upgrade which changes a standard library breaks an older
application expecting an older version of the the shared object function.
Versioning occurs with the GNU C/C++ libraries as well. This often make binaries
compiled with one version of the GNU tools incompatible with binaries compiled
with other versions unless those versions also reside on the system. Multiple
versions of the same library can reside on the same system due to versioning.
The version of the library is included in the symbol name so the linker knows
which version to link with.
00000000 T ctest1
Some symbols may also get version strings from assembler code which appears
in glibc headers files. Look at include/libc-symbols.h.
00000000 A GCC_3.0
00000000 A GLIBC_2.0
00000000 A GLIBC_2.1
00000000 A GLIBC_2.1.1
00000000 A GLIBC_2.1.2
00000000 A GLIBC_2.1.3
00000000 A GLIBC_2.2
00000000 A GLIBC_2.2.1
00000000 A GLIBC_2.2.2
00000000 A GLIBC_2.2.3
00000000 A GLIBC_2.2.4
...
..
..
...
U strcpy@@GLIBC_2.0
U strncmp@@GLIBC_2.0
U strncpy@@GLIBC_2.0
...
..
Links:
• Symbol versioning
• GNU.org: ld version scripts
#ifndef CTEST_H
#define CTEST_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif
Use the notation extern "C" so the libraries can be used with C and C++.
This statement prevents the C++ from name mangling and thus creating
"unresolved symbols" when linking.
#include <stdio.h>
#include <dlfcn.h>
#include "ctest.h"
fn = dlsym(lib_handle, "ctest1");
if ((error = dlerror()) != NULL)
{
fprintf(stderr, "%s\n", error);
exit(1);
}
(*fn)(&x);
printf("Valx=%d\n",x);
dlclose(lib_handle);
return 0;
}
Explanation:
• dlopen("/opt/lib/libctest.so", RTLD_LAZY);
Open shared library named "libctest.so".
The second argument indicates the binding. See include file dlfcn.h.
Returns NULL if it fails.
Options:
o RTLD_LAZY: If specified, Linux is not concerned about unresolved
symbols until they are referenced.
o RTLD_NOW: All unresolved symbols resolved when dlopen() is
called.
o RTLD_GLOBAL: Make symbol libraries visible.
• dlsym(lib_handle, "ctest1");
Returns address to the function which has been loaded with the shared
library..
Returns NULL if it fails.
Note: When using C++ functions, first use nm to find the "mangled" symbol
name or use the extern "C" construct to avoid name mangling.
i.e. extern "C" void function-name();
Object code location: Object code archive libraries can be located with either
the executable or the loadable library. Object code routines used by both should
not be duplicated in each. This is especially true for code which use static
variables such as singleton classes. A static variable is global and thus can only
be represented once. Including it twice will provide unexpected results. The
programmer can specify that specific object code be linked with the executable
by using linker commands which are passed on by the compiler.
Use the "-Wl" gcc/g++ compiler flag to pass command line arguments on to the
GNU "ld" linker.
Man pages:
Links:
When running the above "C" examples with the "C++" compiler one will quickly
find that "C++" function names get mangled and thus will not work unless the
function definitions are protected with extern "C"{}.
The dynamic library loading routines enable the programmer to load "C"
functions. In C++ we would like to load class member functions. In fact the entire
class may be in the library and we may want to load and have access to the
entire object and all of its member functions. Do this by passing a "C" class
factory function which instantiates the class.
...
...
};
extern "C"
{
// These two "C" functions manage the creation and destruction of the
class Abc
Abc* create()
{
return new Abc;
}
void destroy(Abc* p)
{
delete p; // Can use a base class or derived class pointer here
}
}
This file is the source to the library. The "C" functions to instantiate
(create) and destroy a class defined in the dynamically loaded library
where "Abc" is the C++ class.
Main executable which calls the loadable libraries:
// load the symbols
create_t* create_abc = (create_t*) dlsym(lib_handle, "create");
...
...
...
...
Pitfalls:
Links:
The Microsoft Windows equivalent to the Linux / Unix shared object (".so") is the
".dll". The Microsoft Windows DLL file usually has the extension ".dll", but may
also use the extension ".ocx". On the old 16 bit windows, the dynamically linked
libraries were also named with the ".exe" suffix. "Executing" the DLL will load it
into memory.
The Visual C++ .NET IDE wizard will create a DLL framework through the GUI,
and generates a ".def" file. This "module definition file" lists the functions to be
exported. When exporting C++ functions, the C++ mangled names are used.
Using the Visual C++ compiler to generate a ".map" file will allow you to discover
the C++ mangled name to use in the ".def" file. The "SECTIONS" label in the
".def" file will define the portions which are "shared". Unfortunately the generation
of DLLs are tightly coupled to the Microsoft IDE, so much so that I would not
recomend trying to create one without it.
The Microsoft Windows C++ equivalent functions to libdl are the following
functions:
• ::LoadLibrary() - dlopen()
• ::GetProcAddress() - dlsym()
• ::FreeLibrary() - dlclose()
[Potential Pitfall]: Microsoft Visual C++ .NET compilers do not allow the linking
controll that the GNU linker "ld" allows (i.e. --whole-archive, -no-whole-archive).
All symbols need to be resolved by the VC++ compiler for both the loadable
library and the application executable individually and thus it can cause
duplication of libraries when the library is loaded. This is especially bad when
using static variables (i.e. used in singleton patterns) as you will get two memory
locations for the static variable, one used by the loadable library and the other
used by the program executable. This breaks the whole static variable concept
and the singleton pattern. Thus you can not use a static variable which is
referenced by by both the loadable library and the application executable as they
will be unique and different. To use a unique static variable, you must pass a
pointer to that static variable to the other module so that each module (main
executable and DLL library) can use the same instatiation. On MS/Windows you
can use shared memory or a memory mapped file so that the main executable
and DLL library can share a pointer to an address they both will use.
private:
static Abc *mInstance; // Singleton. Use this declaration in C++
class member variable declaration.
...
}
return mInstance ?
mInstance : (mInstance = (Abc*)
MemoryMappedPointers::getPointer("Abc")) ?
mInstance : (mInstance = (Abc*)
MemoryMappedPointers::createEntry("Abc",(void*)new Abc));
#else
// If pointer to instance of Abc exists (true) then return instance
pointer
// else return a newly created pointer to an instance of Abc.
Windows linker will pull two instances of object, one in exe and one in loadable
module. Specify one for both to use by using memory mapped pointer so both
exe and loadable library point to same variable or object.
Note that the GNU linker does not have this problem.
For more on singletons see the YoLinux.com C++ singleton software design
pattern tutorial.
Cross platform programming of loadable libraries:
#ifndef USE_PRECOMPILED_HEADERS
#ifdef WIN32
#include <direct.h>
#include <windows.h>
#else
#include <sys/types.h>
#include <dlfcn.h>
#endif
#include <iostream>
#endif
#ifdef WIN32
HINSTANCE lib_handle;
#else
void *lib_handle;
#endif
...
...
...
...
...
...
#ifdef WIN32
FreeLibrary(lib_handle);
#else
dlclose(lib_handle);
#endif
Tools:
Man pages: