0% found this document useful (0 votes)
23 views11 pages

Lab 5 CN

Uploaded by

Gau
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views11 pages

Lab 5 CN

Uploaded by

Gau
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 11

Lab 5: Week-5: Socket Programming with Multiple clients

1. What is I/O multiplexing and why it is required?


2. Discuss different types of I/O multiplexing
3. Discuss how to design a concurrent chat server using fork () and select() API.

fork(): Each time a client connects, the server creates a new process using fork() to handle that
client.

select():A single process monitors all sockets (server + clients) using select().

Multi-client Chat Server using fork()

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <signal.h>

#define PORT 8080


#define BUFFER_SIZE 1024

void handle_client(int client_socket) {


char buffer[BUFFER_SIZE];
int n;

while ((n = recv(client_socket, buffer, BUFFER_SIZE - 1, 0)) > 0) {


buffer[n] = '\0';
printf("Client says: %s\n", buffer);

if (strcmp(buffer, "exit") == 0) {
printf("Client requested to close the connection.\n");
break;
}

send(client_socket, buffer, strlen(buffer), 0);


}

close(client_socket);
printf("Client disconnected.\n");
exit(0);
}

int main() {
int server_fd, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);

// Ignore SIGCHLD to prevent zombie processes


signal(SIGCHLD, SIG_IGN);

// Create TCP socket


if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("Socket creation failed");
exit(EXIT_FAILURE);
}

// Set up server address


server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);

// Bind socket
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
perror("Bind failed");
close(server_fd);
exit(EXIT_FAILURE);
}

// Listen for connections


if (listen(server_fd, 5) == -1) {
perror("Listen failed");
close(server_fd);
exit(EXIT_FAILURE);
}

printf("Server listening on port %d...\n", PORT);

while (1) {
// Accept a new client connection
if ((client_socket = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len)) == -1) {
perror("Accept failed");
continue;
}

printf("New client connected from %s:%d\n",


inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));

// Create a child process to handle the client


pid_t pid = fork();

if (pid == 0) {
// Child process
close(server_fd); // Child doesn't need the server socket
handle_client(client_socket);
} else if (pid > 0) {
// Parent process
close(client_socket); // Parent doesn't need the client socket
} else {
perror("Fork failed");
close(client_socket);
}
}

close(server_fd);
return 0;
}

Flow:

1. Server socket setup


a. Creates a TCP socket.
b. Binds it to PORT 8080.
c. Listens for incoming connections.
2. Accept new clients
a. When a client connects, fork() creates a child process to handle that
client.
b. The parent process goes back to listening for new connections.
3. Child process handles client
a. Reads messages from the client.
b. Echoes messages back.
c. If the client sends "exit", the child closes the socket and exits.
4. Signal handling
a. signal(SIGCHLD, SIG_IGN) prevents zombie processes when clients
disconnect.

Multiple Client Chat Server (select)

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>

#define PORT 8080


#define MAX_CLIENTS 10
int main() {
int server_fd, client_fd, max_sd, sd, activity, new_socket;
int client_socket[MAX_CLIENTS] = {0};
struct sockaddr_in address;
fd_set readfds;
char buffer[1024];
socklen_t addrlen = sizeof(address);

server_fd = socket(AF_INET, SOCK_STREAM, 0);

address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

bind(server_fd, (struct sockaddr *)&address, sizeof(address));


listen(server_fd, 3);

while (1) {
FD_ZERO(&readfds);
FD_SET(server_fd, &readfds);
max_sd = server_fd;

for (int i = 0; i < MAX_CLIENTS; i++) {


sd = client_socket[i];
if (sd > 0) FD_SET(sd, &readfds);
if (sd > max_sd) max_sd = sd;
}

activity = select(max_sd + 1, &readfds, NULL, NULL, NULL);

if (FD_ISSET(server_fd, &readfds)) {
new_socket = accept(server_fd, (struct sockaddr *)&address, &addrlen);
for (int i = 0; i < MAX_CLIENTS; i++) {
if (client_socket[i] == 0) {
client_socket[i] = new_socket;
break;
}
}
}

for (int i = 0; i < MAX_CLIENTS; i++) {


sd = client_socket[i];
if (FD_ISSET(sd, &readfds)) {
int valread = read(sd, buffer, 1024);
if (valread == 0) {
close(sd);
client_socket[i] = 0;
} else {
buffer[valread] = '\0';
for (int j = 0; j < MAX_CLIENTS; j++) {
if (client_socket[j] != 0 && client_socket[j] != sd) {
send(client_socket[j], buffer, strlen(buffer), 0);
}
}
}
}
}
}
return 0;
}

Flow

1. Create server socket.


2. Bind to an IP/port.
3. Listen for connections.
4. Use select() to:
o Accept new connections.
o Read from existing clients.
5. Broadcast messages from one client to all others.
6. Remove disconnected clients.

Assignments

1. Design a connection oriented concurrent chat server using fork () in C where the server
will serve multiple chart clients simultaneously. When the chat server receives a
“logout” message from a particular client then it terminates the respective connection
with that chat client.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <signal.h>
#include <sys/select.h>
#include <errno.h>
#define SERVER_PORT 8080
#define MAX_CLIENTS 64
#define MAX_TEXT 512
#define BACKLOG 16

// ----- Message frame child -> parent (kept <= PIPE_BUF for atomic writes) -----
enum { MSG_NORMAL = 1, MSG_LOGOUT = 2 };

typedef struct {
int sender_fd; // client socket fd (as seen by child/parent)
uint16_t type; // MSG_NORMAL or MSG_LOGOUT
uint16_t len; // payload length (0 for LOGOUT)
char text[MAX_TEXT]; // payload (not NUL-terminated necessarily)
} MsgFrame;

// ----- Client registry (kept by parent) -----


typedef struct {
int fd;
char tag[64]; // "ip:port" label to show who sent the message
int in_use;
} ClientEntry;

static ClientEntry clients[MAX_CLIENTS];

// Add a client to registry (parent)


static int registry_add(int fd, const char *tag) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (!clients[i].in_use) {
clients[i].fd = fd;
clients[i].in_use = 1;
snprintf(clients[i].tag, sizeof(clients[i].tag), "%s", tag);
return 0;
}
}
return -1; // full
}

static void registry_remove_fd(int fd) {


for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].in_use && clients[i].fd == fd) {
clients[i].in_use = 0;
clients[i].fd = -1;
clients[i].tag[0] = '\0';
break;
}
}
}
static void broadcast_except(int except_fd, const char *msg, size_t len) {
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].in_use && clients[i].fd != except_fd) {
ssize_t s = send(clients[i].fd, msg, len, 0);
if (s < 0) {
// If a send fails, close and drop that client.
close(clients[i].fd);
clients[i].in_use = 0;
}
}
}
}

// Child process: read from client, forward to parent via pipe.


// On "logout", send MSG_LOGOUT and exit.
static void child_loop(int client_fd, int pipe_write_fd, const char *tag) {
(void)tag; // Tag is not needed in child; parent keeps labels.
char buf[MAX_TEXT + 2];

for (;;) {
ssize_t n = recv(client_fd, buf, MAX_TEXT, 0);
if (n <= 0) {
// Treat as logout/disconnect
MsgFrame f = { .sender_fd = client_fd, .type = MSG_LOGOUT, .len = 0 };
(void)write(pipe_write_fd, &f, sizeof(f));
break;
}

// Trim CR/LF for command comparison


size_t len = (size_t)n;
while (len > 0 && (buf[len-1] == '\n' || buf[len-1] == '\r')) len--;
buf[len] = '\0';

if (strcasecmp(buf, "logout") == 0) {
MsgFrame f = { .sender_fd = client_fd, .type = MSG_LOGOUT, .len = 0 };
(void)write(pipe_write_fd, &f, sizeof(f));
break;
} else {
// Normal message: forward to parent
MsgFrame f;
memset(&f, 0, sizeof(f));
f.sender_fd = client_fd;
f.type = MSG_NORMAL;
f.len = (uint16_t)(len > MAX_TEXT ? MAX_TEXT : len);
memcpy(f.text, buf, f.len);
// Atomic write (sizeof(MsgFrame) fits well under typical PIPE_BUF 4096)
(void)write(pipe_write_fd, &f, sizeof(f));
}
}

close(client_fd);
close(pipe_write_fd);
_exit(0);
}

int main(void) {
// Avoid zombie processes (children are reaped automatically)
signal(SIGCHLD, SIG_IGN);

// Create a single unnamed pipe (many writers, one reader).


// Parent will read from pipe_r; each child inherits pipe_w and writes to it.
int pipe_fds[2];
if (pipe(pipe_fds) < 0) {
perror("pipe");
return 1;
}
int pipe_r = pipe_fds[0];
int pipe_w = pipe_fds[1];

// Create listening TCP socket


int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) { perror("socket"); return 1; }

// Allow quick restart


int yes = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));

struct sockaddr_in srv;


memset(&srv, 0, sizeof(srv));
srv.sin_family = AF_INET;
srv.sin_port = htons(SERVER_PORT);
srv.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(server_fd, (struct sockaddr*)&srv, sizeof(srv)) < 0) {


perror("bind");
close(server_fd);
return 1;
}
if (listen(server_fd, BACKLOG) < 0) {
perror("listen");
close(server_fd);
return 1;
}

// Init registry
for (int i = 0; i < MAX_CLIENTS; i++) clients[i].in_use = 0;
printf("Chat server (fork-based) listening on port %d ...\n", SERVER_PORT);

// Parent main loop: wait for new connections and for messages from children.
for (;;) {
fd_set rfds;
FD_ZERO(&rfds);
FD_SET(server_fd, &rfds);
FD_SET(pipe_r, &rfds);

int maxfd = (server_fd > pipe_r ? server_fd : pipe_r);

int rv = select(maxfd + 1, &rfds, NULL, NULL, NULL);


if (rv < 0) {
if (errno == EINTR) continue;
perror("select");
break;
}

// New client?
if (FD_ISSET(server_fd, &rfds)) {
struct sockaddr_in cli;
socklen_t clen = sizeof(cli);
int cfd = accept(server_fd, (struct sockaddr*)&cli, &clen);
if (cfd < 0) {
perror("accept");
} else {
char tag[64];
snprintf(tag, sizeof(tag), "%s:%d",
inet_ntoa(cli.sin_addr), ntohs(cli.sin_port));

if (registry_add(cfd, tag) != 0) {
const char *full = "Server full. Try later.\n";
send(cfd, full, strlen(full), 0);
close(cfd);
} else {
printf("[+] Client connected: %s (fd=%d)\n", tag, cfd);

// Fork child to read from this client and forward to parent via pipe
pid_t pid = fork();
if (pid == 0) {
// Child: close unused ends
close(server_fd);
close(pipe_r); // child only writes to pipe
child_loop(cfd, pipe_w, tag);
// never returns
} else if (pid > 0) {
// Parent: keep cfd open to broadcast; child handles reading
// Parent continues loop
} else {
perror("fork");
// Clean up this client if fork failed
registry_remove_fd(cfd);
close(cfd);
}
}
}
}

// Message(s) from any child?


if (FD_ISSET(pipe_r, &rfds)) {
// Read as many frames as available (pipe is stream; read one frame at a time)
MsgFrame f;
ssize_t n = read(pipe_r, &f, sizeof(f));
if (n == (ssize_t)sizeof(f)) {
// Find sender tag
const char *sender_tag = NULL;
for (int i = 0; i < MAX_CLIENTS; i++) {
if (clients[i].in_use && clients[i].fd == f.sender_fd) {
sender_tag = clients[i].tag;
break;
}
}

if (f.type == MSG_NORMAL) {
// Build "<tag>: message\n"
char line[MAX_TEXT + 96];
int m = snprintf(line, sizeof(line), "%s: %.*s\n",
sender_tag ? sender_tag : "unknown",
(int)f.len, f.text);
if (m < 0) m = 0;
broadcast_except(f.sender_fd, line, (size_t)m);
// (Optionally) echo back to sender too:
// send(f.sender_fd, line, (size_t)m, 0);
fprintf(stdout, "[MSG] %s", line);
fflush(stdout);
} else if (f.type == MSG_LOGOUT) {
// Inform others, then close the sender
char note[128];
int m = snprintf(note, sizeof(note), "*** %s left the chat ***\n",
sender_tag ? sender_tag : "a client");
broadcast_except(f.sender_fd, note, (size_t)m);

// Close and remove from registry


close(f.sender_fd);
registry_remove_fd(f.sender_fd);
fprintf(stdout, "[-] Disconnected: %s (fd=%d)\n",
sender_tag ? sender_tag : "unknown", f.sender_fd);
fflush(stdout);
}
} else if (n == 0) {
// Pipe closed unexpectedly — terminate gracefully
fprintf(stderr, "Parent: pipe closed; shutting down.\n");
break;
} else {
// Partial read (shouldn't happen with fixed-size writes <= PIPE_BUF).
// You could add a small buffer & accumulate; we keep it simple here.
}
}
}

// Cleanup (on error/shutdown)


close(pipe_r);
close(pipe_w);
close(server_fd);
for (int i = 0; i < MAX_CLIENTS; i++) if (clients[i].in_use) close(clients[i].fd);
return 0;
}

Build & Run

gcc chat_server_fork.c -o chat_server_fork


./chat_server_fork

Connect clients (use netcat for quick testing)


Open multiple terminals:
nc 127.0.0.1 8080
 Type messages in any client → all other clients receive:
 127.0.0.1:54036: hello everyone
 To disconnect just one client: type
Logout

*** 127.0.0.1:54036 left the chat ***

You might also like