The sample codes here demonstrate the following techniques:
Code for the Server:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>
/*----------------------------------------------------------------*/
/* Prevent Terminated Children from Becoming Zombies */
/*----------------------------------------------------------------*/
/* The method here is rather portable for systems that conform to POSIX.1-1990.
* In POSIX.1-2001, if
* - the disposition of SIGCHLD is set to SIG_IGN; or
* - the SA_NOCLDWAIT flag is set for SIGCHLD,
* then children that terminate do not become zombies. A call to wait()
* or waitpid() will block until all children have terminated, and then
* fail with errno set to ECHILD. Linux 2.6 conforms to this behavior, but
* Linux 2.4 does not.
*/
void handler_sigchld(int signum)
{
int status;
// Wait for all terminated children
// Loop is required as Unix signals are not queued by default.
while (waitpid(-1, &status, WNOHANG) > 0) {
}
}
int handle_terminated_children(void)
{
struct sigaction sigact;
sigact.sa_handler = handler_sigchld;
sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_RESTART; // Make certain system calls restartable across signals
if (sigaction(SIGCHLD, &sigact, NULL)) {
return -1;
}
return 0;
}
/*----------------------------------------------------------------*/
/* Server */
/*----------------------------------------------------------------*/
/* Port number of the server. Generally, avoid:
* - IANA well-known ports : 0 - 1023
* - BSD ephemeral ports : 1024 - 5000
* - IANA dynamic or private ports : 49152 - 65535
*
* To check the empherical port range of Linux, do
* \$ cat /proc/sys/net/ipv4/ip_local_port_range
* 32768 61000
* (The default range since Linux 2.4)
*
* Refer to
* http://www.iana.org/assignments/port-numbers
* for port number assignments from the IANA.
*/
#define SERVER_PORT 10060
/* The maximum length the queue of pending connections may grow to */
#define LISTEN_BACKLOG 16
int server(void)
{
/* Obtain a socket descriptor */
int listen_fd;
listen_fd = socket(AF_INET, SOCK_STREAM, 0); // TCP
if (listen_fd < 0) {
// errno is set appropriately
return -1;
}
/* Assign a local address to the socket */
struct sockaddr_in server_addr; // IPv4 socket address structure
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY); // Wildcard address; Network byte order
server_addr.sin_port = htons(SERVER_PORT);
if (bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
// errno is set appropriately
return -2;
}
/* Convert the socket into a passive listening socket */
if (listen(listen_fd, LISTEN_BACKLOG) < 0) {
// errno is set appropriately
return -3;
}
/* Arrange to handle terminated children */
if (handle_terminated_children() < 0) {
return -4;
}
for (;;) {
/* Get the descriptor of the next completed connection for the accepted socket */
int connection_fd;
struct sockaddr_in client_addr;
socklen_t client_addr_len;
client_addr_len = sizeof(client_addr);
connection_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_addr_len);
if (connection_fd < 0) {
// errno is set appropriately
switch (errno) {
case EINTR:
// Interrupted by a signal. Restart the connection.
continue;
case EAGAIN:
case ENETDOWN:
case EPROTO:
case ENOPROTOOPT:
case EHOSTDOWN:
case ENONET:
case EHOSTUNREACH:
case EOPNOTSUPP:
case ENETUNREACH:
// Linux accept() passes already-pending network errors on the new
// socket as an error code from accept(). The application should
// detect these errors in the case of TCP/IP and retry again for
// reliable operation.
continue;
default:
return -5;
}
}
pid_t child_pid;
child_pid = fork();
if (child_pid == 0) {
// Child process
close(listen_fd); // Close the listening socket
/* Interact with the client */
#define STR_MAX 100
FILE *fp;
char str[STR_MAX];
fp = fdopen(connection_fd, "r+");
if (!fp) {
// errno is set appropriately
return -6;
}
// Echo the client once
if (fgets(str, STR_MAX, fp)) {
fseek(fp, 0L, SEEK_CUR); // Synchronization; See man fdopen(3)
if (fputs(str, fp) == EOF) {
return -7;
}
}
if (fclose(fp) == EOF) {
// errno is set appropriately
return -8;
}
exit(0);
}
else if (child_pid > 0) {
// Parent process
close(connection_fd); // Close the connection socket
}
else { // child_pid < 0
// errno is set appropriately
return -9;
}
}
return 0;
}
int main(int argc, char *argv[])
{
int retval;
retval = server();
printf("retval => %d (%s)\n", retval, strerror(errno));
return 0;
}
Code for the Client:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
/*----------------------------------------------------------------*/
/* Useful Wrappers */
/*----------------------------------------------------------------*/
/* A wrapper for write() that
- tries again for if write() was interrupted by a caught signal
- invokes write() again for incomplete write() due to buffer limit
*/
ssize_t write_safe(int fd, const void *p, size_t n)
{
ssize_t num_written;
while (n > 0) {
num_written = write(fd, p, n);
if (num_written <= 0) {
if (errno == EINTR)
num_written = 0; // Try again
else
return -1;
}
n -= num_written;
p += num_written;
}
return num_written;
}
/* A wrapper for read() that
- tries again for if read() was interrupted by a caught singal
- invokes read() again for incomplete read() due to buffer limit
(NOTE: Sometimes you may not want this behavior as you will be
blocked here until the buffer is full or EOF is reached.)
*/
ssize_t read_safe(int fd, void *p, size_t n)
{
size_t num_left;
ssize_t num_read;
num_left = n;
while (num_left > 0) {
num_read = read(fd, p, num_left);
if (num_read < 0) {
if (errno == EINTR)
num_read = 0; // Try again
else
return -1;
}
else if (num_read == 0) { // EOF
break;
}
num_left -= num_read;
p += num_read;
}
return n - num_left;
}
/*----------------------------------------------------------------*/
/* Client */
/*----------------------------------------------------------------*/
int client(const char *server_ip, int server_port)
{
/* Obtain a socket descriptor */
int socket_fd;
socket_fd = socket(AF_INET, SOCK_STREAM, 0); // TCP
if (socket_fd < 0) {
// errno is set appropriately
return -1;
}
/* Specify the address of the server */
struct sockaddr_in server_addr; // IPv4 socket address structure
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
int status;
if ((status = inet_pton(AF_INET, server_ip, &server_addr.sin_addr)) < 0) {
// status < 0: Does not contain a valid address family
// status == 0: Source address is invalid in the specified address family
return -2;
}
server_addr.sin_port = htons(server_port);
/* Initiate a connection on the socket */
if (connect(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) {
// errno is set appropriately
return -3;
}
/* Interact with the server */
#define STR_MAX 100
// Get a string
char str[STR_MAX];
printf("Enter a few words: ");
if (fgets(str, STR_MAX, stdin)) {
// Send it to the server
if (write_safe(socket_fd, str, strlen(str)) < 0) {
// errno is set appropriately
return -4;
}
}
// Read from the server
ssize_t num_read;
if ((num_read = read_safe(socket_fd, str, STR_MAX)) < 0) {
// errno is set appropriately
return -5;
}
// Display the result returned
printf("Echo: [%*s]\n", num_read, str);
return 0;
}
#define SERVER_PORT 10060
int main(int argc, char *argv[])
{
int retval;
retval = client("127.0.0.1", SERVER_PORT);
printf("retval => %d (%s)\n", retval, strerror(errno));
return 0;
}
kill()