The concept is well defined within two separate files, one for server and other for client. I’ve merged entire documentation in the form of comments with the code section.
Copy and paste the code within two files server.c
and client.c
and compile them in following manner:
$ gcc -O -Wall -W -pedantic -ansi -std=c99 -o server server.c
$ gcc -O -Wall -W -pedantic -ansi -std=c99 -o client client.c
The above two commands will generate two output files: server
and client
respectively.
Open two terminals side by side and execute file server
followed by file client
(in that specific order) within separate terminals.
Server Side Code
/////////////////////////
// File name: server.c //
/////////////////////////
// Server Side: The purpose of this program is to prepare the server node to
// listen for connection requests from the client node and establish a connection
// with the client node. The connection between a server and client node is estab-
// lished using the socket over the transport layer of the internet.
// Following are the relevant steps/ functions required to achieve this.
// 1. A socket must be created using the socket() function
// 2. The options for the socket (for controlling the behavior of the socket) are set using the setsockopt() function
// 3. The socket is bound to an address and port using the bind() function
// 4. The socket is made to listen on the bound port and address using the listen() function
// 5. The connection request from the client is accepted using the accept() function
// 6. After establishing a connection, information is shared using read() and write() function
// 7. After sharing of information is done, connection is terminated using close() function
// provides required data types
#include <sys/types.h>
// has address family and functions to create a socket
#include <sys/socket.h>
// has the sockaddr_in structure, htons()
#include <netinet/in.h>
// has functions for read and write operations
#include <unistd.h>
// basic C header
#include <stdio.h>
// header to help with strings
#include <string.h>
// has macros such as EXIT_FAILURE and exit() function
#include <stdlib.h>
// port through which connection is to be made
#define CONNECTION_PORT 3500
int main(void) {
// server socket
int socket_descriptor;
// socket created by the bind function
int client_socket;
// buffer to store message
char storage_buffer[80];
unsigned int length_of_address;
// option value for respective option_name
int option_value = 1;
// server and client address structures
struct sockaddr_in server_address;
struct sockaddr_in connection_address;
char* message = "Oh!, Hi Client, How may I serve you?";
// holds return value of read(), i.e. the number of bytes of data sent by client and read by server
ssize_t bytes_read;
// 1. socket() ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used for creating socket, which is a basic component for sending and receiving signals between nodes.
// - creates a socket and returns a file descriptor which represents an open file that will be utilized by the socket
// in reading and writing operations and the file descriptor is used to represent the socket in later stages.
//
// PROTOTYPE: int socket(int domain, int type, int protocol);
// RETURNS: a non-negative integer (file descriptor) if socket is created successfully, else -1
//
// domain - represents the address family over which the communication will be performed. It has pre-fixed values defined
// within sys/socket.h header file.
// - Some domain values are:
// 1. AF_LOCAL or AF_UNIX for local communication (when client and server are on the same node).
// Sockets with AF_LOCAL or AF_UNIX as domain value are called Local/UNIX Domain Sockets.
// 2. AF_INET represents IPv4 address of the client to which a connection should be made.
// AF_INET6 represents IPv6 address of the client to which a connection should be made.
// Sockets with AF_INET and AF_INET6 as domain value are called Internet Domain Sockets.
// 3. AF_BLUETOOTH is used for low-level bluetooth connection.
// type - represents the type of communication used in the socket.
// - Some mostly used types of communication are:
// 1. SOCK_STREAM uses TCP (transmission control protocol) to establish a connection.
// It provides a reliable byte stream of data flow and is a connection-based protocol.
// Sockets with SOCK_STREAM as type value are called Stream Sockets
// 2. SOCK_DGRAM uses UDP (user datagram protocol) which is unreliable and connectionless protocol.
// Sockets with SOCK_DGRAM as type value are called Datagram Sockets
// protocol - represents the protocol used in the socket. It is always represented by a number.
// - For one protocol in the protocol family, the protocol number will always be 0.
// - For more than one protocol in the protocol family, the specific number for the protocol will be specified.
// - This is the same number which appears on protocol field in the IP header of a packet.(man protocols for more details)
//
// creating socket with IPv4 domain and TCP protocol
socket_descriptor = socket(AF_INET, SOCK_STREAM, 0);
//
// check if the socket is created successfully
if (socket_descriptor < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 2. setsockopt() ///////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used to specify or set options for the socket, to control the behavior of the socket. This is completely optional,
// but it helps in reuse of address and port. Prevents error such as: “address already in use”.
//
// PROTOTYPE: int setsockopt(int socket_descriptor, int level, int option_name, const void *value_of_option, socklen_t option_length);
// RETURNS: 0 on the successful application of the option, else -1
//
// socket_descriptor - is the file descriptor returned by the socket() function.
// level - represents the level at which the option for the socket must be applied.
// - Some levels are:
// 1. SOL_SOCKET represents the socket level.
// 2. IPPROTO_TCP represents the TCP level.
// option_name - specifies the rules/ options, that should be modified for the socket.
// - Some useful options are:
// 1. SO_DEBUG is used to enable the recording of debugging information.
// 2. SO_REUSEADDR is used to enable the reusing of local address in the bind() function.
// 3. SO_SNDBUF is used to set the maximum buffer size that can be sent using the socket connction.
// value_of_option - is used to specify the value for the options set in the option_name parameter.
// option_length - is the length of the variable used to set the option value i.e. sizeof(value_of_option)
//
int status = setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, &option_value, sizeof(option_value));
// SO_REUSEADDR is a boolean option, and the option_value of 1 depicts that this option is turned on.
//
// check if the options are set successfully
if (status < 0) {
perror("Couldn't set options");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 3. sockaddr_in (or sockaddr) structure ////////////////////////////////////////////////////////////////////////////////////
// The sockaddr_in structure has four data elements to represent the:
// 1. address family
// 1. port
// 2. internet address and
// 3. additional space (used to match the size of the sockaddr structure when performing casting operation)
//
// The structure of sockaddr_in is given below:
// struct sockaddr_in {
// short sin_family; // address family or domain
// unsigned short sin_port; // port
// struct in_addr sin_addr; // structure for address
// char sin_zero[8]; // padding for casting operations
// };
//
// The structure of in_addr present inside the sockaddr_in structure is given below:
// struct in_addr {
// unsigned long s_addr; // internet address value
// };
//
// A data element inside the structure can be accessed by the . operator and values are assigned to the elements.
//
// intializing structure elements for address
server_address.sin_family = AF_INET;
//
// port number is passed to htons, which converts port number stored in memory to network byte order
// In network byte order, the most significant bits are stored first
server_address.sin_port = htons(CONNECTION_PORT);
//
// INADDR_ANY represents any address can be used for binding i.e. 0.0.0.0
// INADDR_LOOPBACK refers to the localhost of the node i.e. 127.0.0.1
// INADDR_BROADCAST has the address 255.255.255.255 and is used for broadcast communication.
server_address.sin_addr.s_addr = INADDR_ANY;
//
// assigning null character to the last index of the character array
server_address.sin_zero[8] = '\0';
//
// The struct sockaddr_in structure is cast to type struct sockaddr to match the syntax of the function.
// The sizeof() function is used to find the size of struct sockaddr.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 4. bind() ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used to assign an address (ip addr and port) to a socket created using socket() function or we may say,
// it binds the socket with the values address and port from the sockaddr_in structure
//
// PROTOTYPE: int bind(int socket_descriptor, const struct sockaddr *address, socklen_t length_of_address);
// RETURNS: 0 on binding the address and port successfully, else -1
//
// socket_descriptor - is the file descriptor returned by the socket() function.
// address - is a structure of type sockaddr.
// - We usually use a structure of type sockaddr_in to represent this information,
// because information such as port and address can only be stored in this structure.
// - The sockaddr_in is cast to the sockaddr data type when calling the bind() function.
// length_of_address - represents the size of the address passed as the second parameter
//
status = bind(socket_descriptor, (struct sockaddr*)&server_address, sizeof(struct sockaddr));
//
// check if the binding was successful
if (status < 0) {
perror("Couldn't bind socket");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 5. listen() ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used to make the server node wait and listen for the connections from the client node on the port and address specified
// by the bind() function.
//
// PROTOTYPE: int listen(int socket_descriptor, int back_log);
// RETURNS: 0 on listening on the address and port specified, else -1
//
// socket_descriptor - is the file descriptor returned by the socket() function.
// back_log - is the maximum no. of connection requests that can be made to the server by client node at a time.
// The number of requests made after the number specified by back_log may cause an error or will be
// ignored by the server if the options for retransmission are set.
// (OR)
// - defines the maximum length to which the queue of pending connections for socket_descriptor may
// grow. If a connection request arrives when the queue is full, the client may receive an error with
// an indication of ECONNREFUSED.
//
// listen on specified port with a maximum of 4 requests
status = listen(socket_descriptor, 4);
//
// check if the socket is listening successfully
if (status < 0) {
perror("Couldn't listen for connections");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 6. accept() ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used to establish a connection between the server and the client nodes for the transfer of data.
// - It creates a new socket from the first connection request for the specified socket_descriptor and returns the
// file descriptor of the new socket. The file descriptor of this new socket is used in the read() function to receive
// data from client node and write() function to send data to client node.
// (OR)
// - It extracts the first connection request on the queue of pending connections for the listening socket,
// socket_descriptor, creates a new connected socket, and returns a new file descriptor referring to that socket.
// At this point, the connection is established between client and server, and they are ready to transfer data.
//
// PROTOTYPE: int accept(int socket_descriptor, struct sockaddr *restrict address, socklen_t *restrict length_of_address);
// RETURNS: a non-negative integer (file descriptor of the accepted socket) on successful completion, else -1
//
// socket_descriptor is the file descriptor returned by the socket() function.
// address is the variable of the sockaddr_in structure in which the address of the socket returned from the
// function will be stored.
// length_of_address represents the size of the address passed as the second parameter
//
// accept connection signals from the client
length_of_address = sizeof(connection_address);
client_socket = accept(socket_descriptor, (struct sockaddr*)&connection_address, &length_of_address);
//
// check if the server is accepting the signals from the client
if (client_socket < 0) {
perror("Couldn't establish connection with client");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// read()
// - used to read/recive sent data. The data of specified size (80 here) is read and stored in storage_buffer
//
// PROTOTYPE: ssize_t read(int file_descriptor, void *buffer, size_t size);
// RETURNS: the number of bytes of data that has been read successfully, else -1
//
// file_descriptor client_socket
// buffer storage_buffer
// size 80
//
// receive a message from the client
bytes_read = read(client_socket, storage_buffer, 80);
// set the last index of the character array as a null character
storage_buffer[bytes_read] = '\0';
// print message from the client
printf("Message from the client: %s \n", storage_buffer);
// send()
// - used to send data to the client
//
// PROTOTYPE: ssize_t send(int socket_descriptor, const void *buf, size_t len, int flags);
//
// client_socket socket_descriptor
// buf message
// len strlen(message)
// flags 0
//
// send a message to the client
send(client_socket, message, strlen(message), 0);
// close all the sockets created
close(socket_descriptor);
close(client_socket);
return 0;
}
Client Side Code
/////////////////////////
// File name: client.c //
/////////////////////////
// Client Side: The purpose of this program is to initiate a connection to the server node for communication.
// Following are the three relavant steps/ functions required to achieve this.
// 1. A socket must be created using the socket() function
// 2. The socket is bound to an address and port using the bind() function
// 3. A connection is initiated by sending a connection request using connect() function
// provides required data types
#include <sys/types.h>
// has address family and sockets functions
#include <sys/socket.h>
// has the sockaddr_in structure
#include <netinet/in.h>
// has functions for read and write operations
#include <unistd.h>
// basic C header
#include <stdio.h>
// header to help with strings
#include <string.h>
// has macros such as EXIT_FAILURE and exit() function
#include <stdlib.h>
// port through which connection is to be made
#define CONNECTION_PORT 3500
int main(void) {
// client socket
int socket_descriptor;
// structure to represent the address
struct sockaddr_in server_address;
// message to be sent to the server
char* message = "Hello Server!, this is Client here.";
// storage buffer to receive message from server
char receive_buffer[100];
// for storing connection status
int status = 0;
// holds return value of read(), i.e. the number of bytes of data sent by client and read by server
ssize_t bytes_read;
// holds return value of write(), i.e. the number of bytes of data sent to server
// declaration & usage, just for the sake of ignoring warning from compiler
ssize_t bytes_written;
// 1. socket() ///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used for creating socket, which is a basic component for sending and receiving signals between nodes.
// - creates a socket and returns a file descriptor which represents an open file that will be utilized by the socket
// in reading and writing operations and the file descriptor is used to represent the socket in later stages.
//
// PROTOTYPE: int socket(int domain, int type, int protocol);
// RETURNS: a non-negative integer (file descriptor) if socket is created successfully, else -1
//
// domain - represents the address family over which the communication will be performed. It has pre-fixed values defined
// within sys/socket.h header file.
// - Some domain values are:
// 1. AF_LOCAL or AF_UNIX for local communication (when client and server are on the same node).
// Sockets with AF_LOCAL or AF_UNIX as domain value are called Local/UNIX Domain Sockets.
// 2. AF_INET represents IPv4 address of the client to which a connection should be made.
// AF_INET6 represents IPv6 address of the client to which a connection should be made.
// Sockets with AF_INET and AF_INET6 as domain value are called Internet Domain Sockets.
// 3. AF_BLUETOOTH is used for low-level bluetooth connection.
// type - represents the type of communication used in the socket.
// - Some mostly used types of communication are:
// 1. SOCK_STREAM uses TCP (transmission control protocol) to establish a connection.
// It provides a reliable byte stream of data flow and is a connection-based protocol.
// Sockets with SOCK_STREAM as type value are called Stream Sockets
// 2. SOCK_DGRAM uses UDP (user datagram protocol) which is unreliable and connectionless protocol.
// Sockets with SOCK_DGRAM as type value are called Datagram Sockets
// protocol - represents the protocol used in the socket. It is always represented by a number.
// - For one protocol in the protocol family, the protocol number will always be 0.
// - For more than one protocol in the protocol family, the specific number for the protocol will be specified.
// - This is the same number which appears on protocol field in the IP header of a packet.(man protocols for more details)
//
// creating the socket with IPv4 domain and TCP protocol
socket_descriptor = socket(AF_INET, SOCK_STREAM, 0);
//
// check if the socket is created successfully
if (socket_descriptor < 0) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 2. sockaddr_in (or sockaddr) structure ////////////////////////////////////////////////////////////////////////////////////
// The sockaddr_in structure has four data elements to represent the:
// 1. address family
// 1. port
// 2. internet address and
// 3. additional space (used to match the size of the sockaddr structure when performing casting operation)
//
// The structure of sockaddr_in is given below:
// struct sockaddr_in {
// short sin_family; // address family or domain
// unsigned short sin_port; // port
// struct in_addr sin_addr; // structure for address
// char sin_zero[8]; // padding for casting operations
// };
//
// The structure of in_addr present inside the sockaddr_in structure is given below:
// struct in_addr {
// unsigned long s_addr; // internet address value
// };
//
// A data element inside the structure can be accessed by the . operator and values are assigned to the elements.
//
// intializing structure elements for address
server_address.sin_family = AF_INET;
//
// port number is passed to htons, which converts port number stored in memory to network byte order
// In network byte order, the most significant bits are stored first
server_address.sin_port = htons(CONNECTION_PORT);
//
// INADDR_ANY represents any address can be used for binding i.e. 0.0.0.0
// INADDR_LOOPBACK refers to the localhost of the node i.e. 127.0.0.1
// INADDR_BROADCAST has the address 255.255.255.255 and is used for broadcast communication.
server_address.sin_addr.s_addr = INADDR_ANY;
//
// assigning null character to the last index of the character array
server_address.sin_zero[8] = '\0';
//
// The struct sockaddr_in structure is cast to type struct sockaddr to match the syntax of the function.
// The sizeof() function is used to find the size of struct sockaddr.
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 3. connect() //////////////////////////////////////////////////////////////////////////////////////////////////////////////
// - used for sending connection request and establising connetion with the server node.
//
// PROTOTYPE: int connect(int socket_descriptor, const struct sockaddr *address, socklen_t length_of_address);
// RETURNS: 0 on successfully connecting with the server, else -1
//
// socket_descriptor - is the file descriptor returned by the socket() function.
// address - represents the structure containing the information of the address and port number
// of the server node to which the connection is to be made.
// length_of_address - size of the address structure used in the second parameter.
//
status = connect(socket_descriptor, (struct sockaddr*)&server_address, sizeof(server_address));
//
// check if the connection is established successfully
if (status < 0) {
perror("Couldn't connect with the server");
exit(EXIT_FAILURE);
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// write()
// - used to write/send data.
//
// PORTOTYPE: ssize_t write(int file_descriptor, const void *buffer, size_t size);
// RETURNS: number of bytes successfully written into the file, which may at times be less than the specified nbytes, else -1
//
// file_descriptor socket_descriptor
// buffer message
// size strlen(message)
//
// send message to the server
// Note: Here variable bytes_written is used to store return value of write() function, just for the sake of supressing
// a trivial warning from compiler. It has no other usage, therefore discarding its value by casting to void below.
// If a warning doesn't bother you, you may choose not to declare varaible bytes_written.
bytes_written = write(socket_descriptor, message, strlen(message));
(void) bytes_written;
// read()
// - used to read/receive sent data. The data of specified size (100 here) is read and stored in storage_buffer
//
// PROTOTYPE: ssize_t read(int file_descriptor, void *buffer, size_t size);
// RETURNS: the number of bytes of data that has been read successfully, else -1
//
// file_descriptor socket_descriptor
// buffer receive_buffer
// size 100
//
// receive a message from the server
bytes_read = read(socket_descriptor, receive_buffer, 100);
// set the last index of the character array as a null character
receive_buffer[bytes_read] = '\0';
// print message from the server
printf("Message from the server: %s\n", receive_buffer);
// terminate the socket connection
close(socket_descriptor);
return 0;
}