Created
October 20, 2020 05:24
-
-
Save bsodhi/a24e06e6806b685fb4d633c40638e77b to your computer and use it in GitHub Desktop.
A socket based client-server in C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <string.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <arpa/inet.h> | |
#include <stdbool.h> | |
#include <pthread.h> | |
#include <sys/un.h> | |
#include <stddef.h> | |
bool create_worker_thread(int fd); | |
/** | |
* This code is adapted from the samples available at: | |
* https://opensource.com/article/19/4/interprocess-communication-linux-networking and | |
* https://www.gnu.org/software/libc/manual/html_node/Local-Socket-Example.html | |
* | |
* Compile it using: gcc local_socket_client_server.c -lpthread -o ipc_demo | |
* Needless to say, this code is not perfect and may have some subtle bugs. A purpose | |
* if this code is to show how to write a socket based client server program that | |
* off-loads the client connection to a new thread for processing. | |
*/ | |
void log_msg(const char *msg, bool terminate) { | |
printf("%s\n", msg); | |
if (terminate) exit(-1); /* failure */ | |
} | |
/** | |
* Create a named (AF_LOCAL) socket at a given file path. | |
* @param socket_file | |
* @param is_client whether to create a client socket or server socket | |
* @return Socket descriptor | |
*/ | |
int make_named_socket(const char *socket_file, bool is_client) { | |
printf("Creating AF_LOCAL socket at path %s\n", socket_file); | |
if (!is_client && access(socket_file, F_OK) != -1) { | |
log_msg("An old socket file exists, removing it.", false); | |
if (unlink(socket_file) != 0) { | |
log_msg("Failed to remove the existing socket file.", true); | |
} | |
} | |
struct sockaddr_un name; | |
/* Create the socket. */ | |
int sock_fd = socket(AF_LOCAL, SOCK_STREAM, 0); | |
if (sock_fd < 0) { | |
log_msg("Failed to create socket.", true); | |
} | |
/* Bind a name to the socket. */ | |
name.sun_family = AF_LOCAL; | |
strncpy (name.sun_path, socket_file, sizeof(name.sun_path)); | |
name.sun_path[sizeof(name.sun_path) - 1] = '\0'; | |
/* The size of the address is | |
the offset of the start of the socket_file, | |
plus its length (not including the terminating null byte). | |
Alternatively you can just do: | |
size = SUN_LEN (&name); | |
*/ | |
size_t size = (offsetof(struct sockaddr_un, sun_path) + | |
strlen(name.sun_path)); | |
if (is_client) { | |
if (connect(sock_fd, (struct sockaddr *) &name, size) < 0) { | |
log_msg("connect failed", 1); | |
} | |
} else { | |
if (bind(sock_fd, (struct sockaddr *) &name, size) < 0) { | |
log_msg("bind failed", 1); | |
} | |
} | |
return sock_fd; | |
} | |
/** | |
* Starts a server socket that waits for incoming client connections. | |
* @param socket_file | |
* @param max_connects | |
*/ | |
_Noreturn void start_server_socket(char *socket_file, int max_connects) { | |
int sock_fd = make_named_socket(socket_file, false); | |
/* listen for clients, up to MaxConnects */ | |
if (listen(sock_fd, max_connects) < 0) { | |
log_msg("Listen call on the socket failed. Terminating.", true); /* terminate */ | |
} | |
log_msg("Listening for client connections...\n", false); | |
/* Listens indefinitely */ | |
while (1) { | |
struct sockaddr_in caddr; /* client address */ | |
int len = sizeof(caddr); /* address length could change */ | |
printf("Waiting for incoming connections...\n"); | |
int client_fd = accept(sock_fd, (struct sockaddr *) &caddr, &len); /* accept blocks */ | |
if (client_fd < 0) { | |
log_msg("accept() failed. Continuing to next.", 0); /* don't terminate, though there's a problem */ | |
continue; | |
} | |
/* Start a worker thread to handle the received connection. */ | |
if (!create_worker_thread(client_fd)) { | |
log_msg("Failed to create worker thread. Continuing to next.", 0); | |
continue; | |
} | |
} /* while(1) */ | |
} | |
/** | |
* This functions is executed in a separate thread. | |
* @param sock_fd | |
*/ | |
void thread_function(int sock_fd) { | |
log_msg("SERVER: thread_function: starting", false); | |
char buffer[5000]; | |
memset(buffer, '\0', sizeof(buffer)); | |
int count = read(sock_fd, buffer, sizeof(buffer)); | |
if (count > 0) { | |
printf("SERVER: Received from client: %s\n", buffer); | |
write(sock_fd, buffer, sizeof(buffer)); /* echo as confirmation */ | |
} | |
close(sock_fd); /* break connection */ | |
log_msg("SERVER: thread_function: Done. Worker thread terminating.", false); | |
pthread_exit(NULL); // Must be the last statement | |
} | |
/** | |
* This function launches a new worker thread. | |
* @param sock_fd | |
* @return Return true if thread is successfully created, otherwise false. | |
*/ | |
bool create_worker_thread(int sock_fd) { | |
log_msg("SERVER: Creating a worker thread.", false); | |
pthread_t thr_id; | |
int rc = pthread_create(&thr_id, | |
/* Attributes of the new thread, if any. */ | |
NULL, | |
/* Pointer to the function which will be | |
* executed in new thread. */ | |
thread_function, | |
/* Argument to be passed to the above | |
* thread function. */ | |
(void *) sock_fd); | |
if (rc) { | |
log_msg("SERVER: Failed to create thread.", false); | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Sends a message to the server socket. | |
* @param msg Message to send | |
* @param socket_file Path of the server socket on localhost. | |
*/ | |
void send_message_to_socket(char *msg, char *socket_file) { | |
int sockfd = make_named_socket(socket_file, true); | |
/* Write some stuff and read the echoes. */ | |
log_msg("CLIENT: Connect to server, about to write some stuff...", false); | |
if (write(sockfd, msg, strlen(msg)) > 0) { | |
/* get confirmation echoed from server and print */ | |
char buffer[5000]; | |
memset(buffer, '\0', sizeof(buffer)); | |
if (read(sockfd, buffer, sizeof(buffer)) > 0) { | |
printf("CLIENT: Received from server:: %s\n", buffer); | |
} | |
} | |
log_msg("CLIENT: Processing done, about to exit...", false); | |
close(sockfd); /* close the connection */ | |
} | |
/** | |
* This is the driver function you can use to test client-server | |
* communication using sockets. | |
* @param argc | |
* @param argv | |
* @return | |
*/ | |
int main(int argc, char *argv[]) { | |
if (argc < 3) { | |
printf("Usage: %s [server|client] [Local socket file path] [Message to send (needed only in case of client)]\n", | |
argv[0]); | |
exit(-1); | |
} | |
if (0 == strcmp("server", argv[1])) { | |
start_server_socket(argv[2], 10); | |
} else { | |
send_message_to_socket(argv[3], argv[2]); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment