Network Programming
Q1. What is the purpose of the shutdown() function in TCP
sockets, and how is it different from close()?
In TCP socket programming, proper connection termination ensures clean
communication and resource management. Two key system calls used are
shutdown() and close().
The shutdown() function allows selective disabling of communication over
a socket. It is used when one end wants to stop sending or receiving data
while keeping the socket open for the other direction. The syntax is:
int shutdown(int sockfd, int how);
how can be:
o SHUT_RD (0): Disables further reading.
o SHUT_WR (1): Disables further writing.
o SHUT_RDWR (2): Disables both.
The close() function, on the other hand, completely closes the socket
descriptor and releases all associated resources. Once closed, the socket
is no longer usable.
Differences:
Aspect shutdown() close()
Behavio
Partial or full shutdown Full socket closure
r
Resourc Does not free socket Frees file
e immediately descriptor
Graceful, controlled Complete
Usage
termination termination
Example: In a client-server model, the client may send data, then call
shutdown(sockfd, SHUT_WR) to indicate it has finished sending. The
server can continue responding. Later, close() can be used for full closure.
Q2. How do you write a basic TCP Echo Server using the socket
API, and what are its uses and limitations?
A TCP Echo Server is a simple server that sends back whatever data it
receives from the client. It is used to demonstrate TCP bidirectional
communication.
Steps to build a TCP Echo Server:
1. Create a socket using socket().
2. Bind to a port using bind().
3. Listen for connections using listen().
4. Accept incoming client using accept().
5. Read data from the client using recv().
6. Echo the same data back using send().
Sample Code:
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, ...);
listen(sockfd, 5);
int clientfd = accept(sockfd, ...);
recv(clientfd, buffer, ...);
send(clientfd, buffer, ...);
Applications:
Testing network communication
Educational tool for learning sockets
Base model for more complex servers
Limitations:
Handles only one client at a time (iterative)
Lacks concurrency and error handling
Not suitable for high-traffic systems
Q3. What is pselect() in I/O multiplexing, and how is it better than
select() for handling signals?
pselect() is a system call used in I/O multiplexing that monitors multiple
file descriptors and allows signal-safe operations, offering an improvement
over select().
Syntax:
int pselect(int nfds, fd_set *readfds, ..., const struct timespec *timeout,
const sigset_t *sigmask);
The sigmask parameter allows atomic signal masking during wait, making
pselect() more suitable for programs that also handle signals.
Comparison with select():
Feature select() pselect()
Signal-safe? No Yes
Timeout Microsecon
Nanoseconds
precision ds
Supported via
Signal masking Not possible
sigmask
Use-case Example: A server that needs to wait for both I/O and a signal
like SIGINT (Ctrl+C) can use pselect() to avoid race conditions.
Advantages:
Safer with signal handling
Ideal for real-time systems
More reliable for event-driven models
Conclusion: pselect() enhances select() by ensuring safe signal handling
during I/O multiplexing. It is preferred in applications that need to respond
to both socket events and asynchronous signals.
Q4. What are socket timeouts, and how can you set read/write
timeouts using setsockopt()?
Socket timeouts define how long a socket operation (like reading or
writing) can block before timing out. This is essential to prevent indefinite
waits during communication failures.
Setting timeouts: Use the setsockopt() function with SO_RCVTIMEO (for
read) and SO_SNDTIMEO (for write) options.
Syntax:
struct timeval tv;
tv.tv_sec = 5; // 5 seconds
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
Benefits:
Prevents socket hang due to network issues
Enhances user experience in real-time systems
Enables retry or fallback logic in applications
Effect: If the timeout expires, recv() or send() returns -1 and sets errno to
EAGAIN or EWOULDBLOCK.
Use-cases:
HTTP clients waiting for server responses
Network devices with unstable connections
Time-sensitive communication (e.g., games, VoIP)
Q5. How do read()/write() differ from recv()/send() in socket
programming, and when should you use each?
In socket programming, two sets of functions are used for data
transmission: read()/write() (generic file operations) and recv()/send()
(socket-specific).
Key Differences:
Aspec
read()/write() recv()/send()
t
Works on all file
Scope Works only on sockets
descriptors
Supports flags like MSG_PEEK,
Flags No extra control
MSG_DONTWAIT
Simpler, general-
Usage Advanced socket handling
purpose
Syntax:
ssize_t read(int fd, void *buf, size_t count);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
Use-case:
Use recv() and send() when advanced options (flags) are required.
Use read() and write() in simple TCP applications where flags are not
necessary.
Example:
char buffer[100];
recv(sockfd, buffer, sizeof(buffer), MSG_PEEK); // Reads without consuming
Q6. What are readv() and writev() system calls, and how do they
improve I/O efficiency?
readv() and writev() are system calls used for scatter/gather I/O,
allowing multiple buffers to be read from or written to a file/socket in a
single operation.
Syntax:
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
The iovec structure is defined as:
struct iovec {
void *iov_base; // Pointer to data
size_t iov_len; // Length of data
};
Advantages:
Minimizes system calls (no repeated read()/write()).
Increases performance by reducing user-kernel context switches.
Ideal for applications that separate headers and body (e.g., web
servers).
Example:
char header[] = "Header\n", body[] = "Content\n";
struct iovec iov[2] = {
{header, strlen(header)},
{body, strlen(body)}
};
writev(sockfd, iov, 2);
Use-cases:
Web servers sending multiple parts (headers + data)
Logging services
Protocols with metadata + payload separation
Q7. What is the socket address structure for Unix domain sockets,
and how is it different from INET sockets?
In Unix systems, sockets can communicate over two primary domains:
INET (TCP/IP) and Unix Domain (local IPC). Each uses a different address
structure.
Unix Domain Sockets: Used for inter-process communication on the
same host using file paths instead of IP addresses.
Structure:
struct sockaddr_un {
sa_family_t sun_family; // AF_UNIX
char sun_path[108]; // File path
};
INET Sockets: Used for network communication across machines via
TCP/IP.
Structure:
struct sockaddr_in {
sa_family_t sin_family; // AF_INET
in_port_t sin_port; // Port number
struct in_addr sin_addr;// IP address
};
Comparison:
Unix Domain
Feature INET Sockets
Sockets
Addressing File path IP + Port
Network
Use Local IPC
communication
Unix Domain
Feature INET Sockets
Sockets
Performanc Faster, no TCP/IP Slower, includes
e overhead routing
Example:
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/mysocket");
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
Q8. What is socketpair() and how can it be used for full-duplex
communication between two processes?
The socketpair() system call creates a pair of connected, unnamed sockets
for two-way communication between related processes (e.g., parent-
child).
Syntax:
int socketpair(int domain, int type, int protocol, int sv[2]);
Typically used with AF_UNIX, SOCK_STREAM.
sv[0] and sv[1] are two connected endpoints.
Use-case:
Inter-process communication (IPC)
Replacement for pipes with full-duplex capability
Example:
int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
if (fork() == 0) {
close(sv[0]);
write(sv[1], "Hello", 5);
} else {
close(sv[1]);
char buf[10];
read(sv[0], buf, 10);
Advantages:
Full-duplex (bi-directional)
Simpler than using pipe() for both directions
Local-only, faster than network sockets
Q9. How do you implement a Unix Domain Datagram client/server,
and how is it different from Stream sockets?
Unix Domain Datagram Sockets use the SOCK_DGRAM type for
communication between processes on the same system. Unlike
SOCK_STREAM, they are connectionless and send fixed-sized messages.
Differences:
Featur Datagram Stream
e (SOCK_DGRAM) (SOCK_STREAM)
Type Connectionless Connection-oriented
Delivery Message-based Byte-stream
Orderin
Not guaranteed Guaranteed
g
Use- Continuous data
Simple messaging
case transfer
Server Code (Receiver):
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/server.sock");
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));
recvfrom(sockfd, buffer, sizeof(buffer), 0, NULL, NULL);
Client Code (Sender):
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/tmp/server.sock");
sendto(sockfd, "Hello", 5, 0, (struct sockaddr*)&addr, sizeof(addr));
Use-cases:
Local inter-process notifications
Lightweight message exchanges
Simple logging agents
Q10. What is a TCP Iterative Server, and how does it compare to a
Concurrent Server?
A TCP Iterative Server handles one client at a time using a single
process. It accepts a connection, processes it fully, then closes and
accepts the next.
Server Flow:
1. Create socket
2. Bind and listen
3. Accept client
4. Process and respond
5. Close connection and repeat
Example:
while (1) {
clientfd = accept(sockfd, ...);
handle_client(clientfd);
close(clientfd);
Comparison with Concurrent Server:
Iterative
Feature Concurrent Server
Server
Multiple
Clients One at a time
simultaneously
Complexit
Simple Needs fork/threads
y
Scalabilit
Limited High
y
Applications:
Embedded systems
Lightweight devices
Simple services (e.g., Echo server)
Q11. What is a Preforked Server without locking around accept(),
and what issues can it cause?
In a preforked server, multiple child processes are created in advance to
handle incoming clients. Each child calls accept() on the same listening
socket.
Without Locking: When multiple processes call accept() simultaneously
without synchronization, a race condition occurs. Two or more processes
may attempt to accept the same connection.
Issues:
Lost or delayed connections
Unpredictable client handling
Difficult debugging and maintenance
Solutions:
File Locking: Use fcntl() or flock() around accept() section
Accept Mutex: Use shared memory or semaphores
Serialize with Master Process: Let the master accept, and pass
sockets to workers
Sample Lock Implementation:
flock(fd, LOCK_EX);
clientfd = accept(sockfd, ...);
flock(fd, LOCK_UN);
Q12. How can one process send file descriptors to another using
Unix domain sockets?
Descriptor passing allows sending open file descriptors between
processes using Unix Domain Sockets and the sendmsg() / recvmsg()
system calls.
Key Concepts:
Uses control messages (cmsghdr) and SCM_RIGHTS
Socket type: AF_UNIX, SOCK_STREAM or SOCK_DGRAM
Sender (Parent Process):
int fds[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, fds);
int fd_to_send = open("file.txt", O_RDONLY);
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof(int))];
struct iovec io = {.iov_base = "A", .iov_len = 1};
msg.msg_iov = &io; msg.msg_iovlen = 1;
msg.msg_control = buf;
msg.msg_controllen = CMSG_SPACE(sizeof(int));
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));
sendmsg(fds[0], &msg, 0);
Receiver (Child Process):
struct msghdr msg = {0};
char buf[CMSG_SPACE(sizeof(int))];
struct iovec io = {.iov_base = "B", .iov_len = 1};
msg.msg_control = buf;
msg.msg_controllen = sizeof(buf);
msg.msg_iov = &io; msg.msg_iovlen = 1;
recvmsg(fds[1], &msg, 0);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
int received_fd = *(int *)CMSG_DATA(cmsg);