Let’s say Alice and Bob are computer science students at a university. They are working on a project together, but they are in different parts of the campus and need to communicate with each other over the network. They decide to create a simple chat program using socket programming.
Alice writes a program that creates a socket and waits for incoming connections. Bob writes a program that connects to Alice’s socket. Once they are connected, they can send messages back and forth using the socket.
Alice’s program displays a prompt on her screen, asking her to enter a message. When she types a message and hits “enter,” the message is sent through her socket to Bob’s program. Bob’s program receives the message and displays it on his screen. Bob can then type a message and send it back to Alice.
In this way, Alice and Bob can communicate with each other in real time, even though they are in different locations on the network. They could use this chat program to collaborate on their project, share code snippets, or just chat about their day.
This is a simple example of how socket programming can be used to enable communication between programs over a network.
Socket programming is a method of enabling communication between computer programs over a network. It involves using a set of programming functions and structures to create and manage “sockets”, which are software objects that represent the endpoints of a communication channel.
WhatsApp uses socket programming to enable real-time messaging and communication between its users. When a user sends a message in WhatsApp, the message is sent from their device to WhatsApp’s servers using sockets. The servers then forward the message to the recipient’s device using sockets as well.
Now, Let’s get technical
A socket is identified by a 5-tuple, which includes the source IP address, source port number, destination IP address, destination port number, and the transport protocol (e.g. TCP or UDP) being used. This 5-tuple provides a unique identification for the socket, allowing data to be sent and received between the two endpoints.
In socket programming, there are two types of sockets:
- Stream Sockets (TCP Sockets): These sockets provide reliable, connection-oriented data transfer between two endpoints. They guarantee that data is delivered in the order it was sent and without errors or duplicates. Stream sockets are often used for applications that require high levels of reliability, such as web browsers, email clients, and file transfer protocols.
- Datagram Sockets (UDP Sockets): These sockets provide a connectionless, unreliable way of transmitting data between two endpoints. They do not guarantee that data will be delivered, nor do they ensure that it will be delivered in the order it was sent. Datagram sockets are often used for applications that require fast, lightweight communication, such as online gaming, audio and video streaming, and real-time chat applications.

How Sockets work
Create a socket
To create a socket, the socket() system call is used, which returns a socket descriptor (an integer value) that is used to refer to the socket in subsequent socket operations.
int sockid = int socket(int family, int type, int protocol);
- sockid: socket descriptor, an integer (like a file-handle)
- Family: integer, communication domain, e.g.,
- PF_INET for IPV4
- PF_UNIX, Local communication, File addresses
- type: communication type
- SOCK_STREAM – reliable, 2-way, connection-based service
- SOCK_DGRAM – unreliable, connectionless, messages of maximum length
- protocol: specifies protocol – usually set to 0 (i.e., use default protocol)
- IPPROTO_TCP
- IPPROTO_UDP
Bind Connection
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockid: integer, socket descriptor
- *addr: struct sockid, the (IP) address and port of the machine.
- For TCP/IP server, the internet address is usually set to INADDR_ANY, i.e., chooses any incoming interface
- size: the size (in bytes) of the *addr structure
Specifying Addresses
Socket API defines a generic data type for addresses
struct sockaddr
{
unsigned short sa_family; /* Address family (e.g. AF_INET) */
char sa_data[14]; /* Family-specific address information */
}
Particular form of the sockaddr used for TCP/IP addresses:
struct sockaddr_in
{
unsigned short sin_family; /* Internet protocol (AF_INET) */
unsigned short sin_port; /* Address port (16 bits) */
struct in_addr sin_addr; /* Internet address (32 bits) */
char sin_zero[8]; /* Not used */
}
bind()- Example with TCP
int sockid;
struct sockaddr_in addrport;
sockid = socket(PF_INET, SOCK_STREAM, 0);
addrport.sin_family = AF_INET;
addrport.sin_port = htons(5100);
addrport.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockid, (struct sockaddr *) &addrport, sizeof(addrport))!= -1) {
…}
Establish Connection: connect()
int status = connect(sockid, &foreignAddr, addrlen);
- sockid: integer, socket to be used in connection
- foreignAddr: struct sockaddr: address of the passive participant
- addrlen: integer, sizeof(name)
- status: 0 if successful connect, -1 otherwise
Incoming Connection: accept()
int s = accept(sockid, &clientAddr, &addrLen);
- s: integer, the new socket (used for data-transfer)
- sockid: integer, the orig. socket (being listened on)
- clientAddr: struct sockaddr, address of the active participant
- filled in upon return
- addrLen: sizeof(clientAddr): value/result parameter
- adjusted upon return
- must be set appropriately before call
Exchanging data with a stream socket
int count = send(sockid, msg, msgLen, flags);
- msg: const void[], message to be transmitted
- msgLen: integer, length of message (in bytes) to transmit
- flags: integer, special options, usually just 0
- count: # bytes transmitted (-1 if error)
int count = recv(sockid, recvBuf, bufLen, flags);
- recvBuf: void[], stores received bytes
- bufLen: # bytes received
- flags: integer, special options, usually just 0
- count: # bytes received (-1 if error)
Putting everything together
/* server side: create a file called server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 15000
int main() {
int create_socket, new_socket, addrlen, bufsize = 1024;
char buffer[bufsize];
struct sockaddr_in address;
// Create socket
if ((create_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}
printf("Socket created\n");
// Bind socket to port
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(create_socket, (struct sockaddr *)&address, sizeof(address)) == -1) {
perror("Bind failed");
exit(EXIT_FAILURE);
}
printf("Binding socket to port %d\n", PORT);
// Listen for incoming connections
if (listen(create_socket, 3) == -1) {
perror("Listen failed");
exit(EXIT_FAILURE);
}
printf("Listening for connections...\n");
// Accept incoming connections
addrlen = sizeof(address);
if ((new_socket = accept(create_socket, (struct sockaddr *)&address, (socklen_t*)&addrlen)) == -1) {
perror("Accept failed");
exit(EXIT_FAILURE);
}
printf("Client %s:%d connected\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
// Receive and send messages
do {
memset(buffer, 0, bufsize);
if (recv(new_socket, buffer, bufsize, 0) < 0) {
perror("Receive failed");
exit(EXIT_FAILURE);
}
printf("Message received: %s", buffer);
if (strcmp(buffer, "/bye\n") == 0) {
printf("Quitting...\n");
break;
}
printf("Message to send: ");
fgets(buffer, bufsize, stdin);
send(new_socket, buffer, bufsize, 0);
} while (1);
// Close sockets
close(new_socket);
close(create_socket);
return 0;
}
/*
to compile server side : gcc -g server.c -o server
to run server side: ./server
*/
/* client side: create a file called client.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#define PORT 15000
int main(int argc, char const *argv[]) {
int sock = 0, valread;
struct sockaddr_in serv_addr;
char buffer[1024] = {0};
char message[1024];
// Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
// Set server address and port
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// Convert IPv4 and IPv6 addresses from text to binary form
if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
// Connect to server
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
// Communicate with server
do {
printf("Enter a message to send (type '/bye' to quit): ");
fgets(message, sizeof(message), stdin);
if (strcmp(message, "/bye\n") == 0) {
send(sock, "Client has left, GoodBye!!!", strlen("Client has left, GoodBye!!!"), 0);
printf("Quitting...\n");
break;
} else {
send(sock, message, strlen(message), 0);
valread = recv(sock, buffer, sizeof(buffer), 0);
printf("Server response: %s", buffer);
memset(buffer, 0, sizeof(buffer));
}
} while (1);
close(sock);
return 0;
}
/*
to compile: gcc -g client.c -o client
to run client side: ./client
*/
Conclusion
Thank you for using our step-by-step guide for socket programming. We hope it has been helpful in understanding the basics of socket programming and how to implement a simple client-server application using sockets in C. Socket programming is an important aspect of network programming and can be used to create a wide range of network applications. Keep practicing and exploring new ideas in socket programming to become proficient in this field.
0 Comments