Last active
April 3, 2017 20:03
-
-
Save Gelio/ea237651786e4bc609fde7e94a69b058 to your computer and use it in GitHub Desktop.
Socker communicator with server-client architecture created 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
#define _GNU_SOURCE | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <errno.h> | |
#include <sys/un.h> | |
#include <pthread.h> | |
#include <sys/time.h> | |
#include <stdint.h> | |
#include <netinet/in.h> | |
#define SOCKET_UNIX_PATH "/tmp/grzen-comm" | |
#define SOCKET_UNIX_PATH_SIZE 108 | |
#define USERNAME_SIZE 30 | |
#define BUFFER_SIZE 100 | |
#define ERR(source) (fprintf(stderr, "%s:%d\n", __FILE__, __LINE__), \ | |
perror(source), exit(EXIT_FAILURE) ) | |
void usage(char *filename) | |
{ | |
fprintf(stderr, "Usage: %s username\n", filename); | |
fprintf(stderr, "Username must be between 1 and %d characters\n", USERNAME_SIZE - 1); | |
exit(EXIT_FAILURE); | |
} | |
void *printMessages(void *socketDesPtr) | |
{ | |
int socketDes = *(int*)socketDesPtr; | |
fd_set baseFds; | |
FD_ZERO(&baseFds); | |
FD_SET(socketDes, &baseFds); | |
fd_set readFds; | |
while (1) | |
{ | |
readFds = baseFds; | |
if (select(socketDes + 1, &readFds, NULL, NULL, NULL) < 0) | |
ERR("select"); | |
if (!FD_ISSET(socketDes, &readFds)) | |
continue; | |
uint32_t messageLength; | |
ssize_t bytesRead = read(socketDes, (char*)&messageLength, sizeof(uint32_t)); | |
if (bytesRead < 0) | |
ERR("read message length"); | |
else if (bytesRead == 0) | |
{ | |
printf("Disconnected from the server\n"); | |
break; | |
} | |
messageLength = ntohl(messageLength); | |
char sender[USERNAME_SIZE]; | |
if (read(socketDes, sender, USERNAME_SIZE) < 0) | |
ERR("read sender name"); | |
char *message = malloc(messageLength * sizeof(char)); | |
if (message == NULL) | |
ERR("malloc"); | |
if (read(socketDes, message, messageLength) < 0) | |
ERR("read mesage"); | |
printf("Got a message from %s: %s\n", sender, message); | |
free(message); | |
} | |
return NULL; | |
} | |
void login(int socketDes, char *userName) | |
{ | |
if (write(socketDes, userName, USERNAME_SIZE) < 0) | |
ERR("write"); | |
printf("Logged in as %s\n", userName); | |
} | |
void readConsoleAndSendToServer(int socketDes) | |
{ | |
while (1) | |
{ | |
printf("Recipient: "); | |
char recipient[USERNAME_SIZE]; | |
if (fgets(recipient, USERNAME_SIZE, stdin) <= 0) | |
ERR("scanf"); | |
recipient[strlen(recipient) - 1] = '\0'; // remove newline | |
printf("Message: "); | |
char buffer[BUFFER_SIZE]; | |
if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) | |
ERR("fgets"); | |
buffer[strlen(buffer) - 1] = '\0'; // remove newline | |
if (strlen(buffer) == 0) | |
{ | |
printf("Empty message, discarding\n"); | |
continue; | |
} | |
printf("Sending message...\n"); | |
uint32_t messageLength = strlen(buffer); | |
messageLength = htonl(messageLength); | |
if (write(socketDes, (char*)&messageLength, sizeof(uint32_t)) < 0) | |
ERR("write message length"); | |
if (write(socketDes, recipient, USERNAME_SIZE) < 0) | |
ERR("write username"); | |
if (write(socketDes, buffer, strlen(buffer)) < 0) | |
ERR("write message"); | |
printf("Message sent\n"); | |
} | |
} | |
int createUnixSocket() | |
{ | |
int socketDes = socket(AF_LOCAL, SOCK_STREAM, 0); | |
if (socketDes < 0) | |
ERR("socket"); | |
struct sockaddr_un unixAddress; | |
memset(&unixAddress, 0, sizeof(struct sockaddr_un)); | |
unixAddress.sun_family = AF_LOCAL; | |
strncpy(unixAddress.sun_path, SOCKET_UNIX_PATH, SOCKET_UNIX_PATH_SIZE); | |
if (connect(socketDes, (struct sockaddr*)&unixAddress, sizeof(struct sockaddr_un)) < 0) | |
ERR("connect"); | |
return socketDes; | |
} | |
pthread_t createListeningThread(int *socketDes) | |
{ | |
pthread_t tid; | |
if (pthread_create(&tid, NULL, printMessages, socketDes) < 0) | |
ERR("pthread_create"); | |
return tid; | |
} | |
void cleanup(pthread_t tid, int socketDes) | |
{ | |
printf("Joining thread\n"); | |
if (pthread_join(tid, NULL) < 0) | |
ERR("pthread_join"); | |
printf("Closing socket\n"); | |
if (close(socketDes) < 0) | |
ERR("socketDes"); | |
printf("Exiting\n"); | |
} | |
void parseArguments(int argc, char **argv, char *userName) | |
{ | |
if (argc != 2) | |
usage(argv[0]); | |
strncpy(userName, argv[1], USERNAME_SIZE); | |
int userNameLength = strlen(userName); | |
if (userNameLength == 0) | |
usage(argv[0]); | |
} | |
int main(int argc, char **argv) | |
{ | |
char userName[USERNAME_SIZE]; | |
parseArguments(argc, argv, userName); | |
int socketDes = createUnixSocket(); | |
pthread_t tid = createListeningThread(&socketDes); | |
login(socketDes, userName); | |
readConsoleAndSendToServer(socketDes); | |
cleanup(tid, socketDes); | |
return EXIT_SUCCESS; | |
} |
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
#define _GNU_SOURCE | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <errno.h> | |
#include <sys/un.h> | |
#include <pthread.h> | |
#include <sys/time.h> | |
#include <stdint.h> | |
#include <netinet/in.h> | |
#include <netdb.h> | |
#define SOCKET_UNIX_PATH "/tmp/grzen-comm" | |
#define SOCKET_UNIX_PATH_SIZE 108 | |
#define USERNAME_SIZE 30 | |
#define BUFFER_SIZE 100 | |
#define TCP_PORT 7070 | |
#define ERR(source) (fprintf(stderr, "%s:%d\n", __FILE__, __LINE__), \ | |
perror(source), exit(EXIT_FAILURE) ) | |
void usage(char *filename) | |
{ | |
fprintf(stderr, "Usage: %s ip username\n", filename); | |
fprintf(stderr, "Username must be between 1 and %d characters\n", USERNAME_SIZE - 1); | |
exit(EXIT_FAILURE); | |
} | |
void *printMessages(void *socketDesPtr) | |
{ | |
int socketDes = *(int*)socketDesPtr; | |
fd_set baseFds; | |
FD_ZERO(&baseFds); | |
FD_SET(socketDes, &baseFds); | |
fd_set readFds; | |
while (1) | |
{ | |
readFds = baseFds; | |
if (select(socketDes + 1, &readFds, NULL, NULL, NULL) < 0) | |
ERR("select"); | |
if (!FD_ISSET(socketDes, &readFds)) | |
continue; | |
uint32_t messageLength; | |
ssize_t bytesRead = read(socketDes, (char*)&messageLength, sizeof(uint32_t)); | |
if (bytesRead < 0) | |
ERR("read message length"); | |
else if (bytesRead == 0) | |
{ | |
printf("Disconnected from the server\n"); | |
break; | |
} | |
messageLength = ntohl(messageLength); | |
char sender[USERNAME_SIZE]; | |
if (read(socketDes, sender, USERNAME_SIZE) < 0) | |
ERR("read sender name"); | |
char *message = malloc(messageLength * sizeof(char)); | |
if (message == NULL) | |
ERR("malloc"); | |
if (read(socketDes, message, messageLength) < 0) | |
ERR("read mesage"); | |
printf("Got a message from %s: %s\n", sender, message); | |
free(message); | |
} | |
return NULL; | |
} | |
void login(int socketDes, char *userName) | |
{ | |
if (write(socketDes, userName, USERNAME_SIZE) < 0) | |
ERR("write"); | |
printf("Logged in as %s\n", userName); | |
} | |
void readConsoleAndSendToServer(int socketDes) | |
{ | |
while (1) | |
{ | |
printf("Recipient: "); | |
char recipient[USERNAME_SIZE]; | |
if (fgets(recipient, USERNAME_SIZE, stdin) <= 0) | |
ERR("scanf"); | |
recipient[strlen(recipient) - 1] = '\0'; // remove newline | |
printf("Message: "); | |
char buffer[BUFFER_SIZE]; | |
if (fgets(buffer, BUFFER_SIZE, stdin) == NULL) | |
ERR("fgets"); | |
buffer[strlen(buffer) - 1] = '\0'; // remove newline | |
if (strlen(buffer) == 0) | |
{ | |
printf("Empty message, discarding\n"); | |
continue; | |
} | |
printf("Sending message...\n"); | |
uint32_t messageLength = strlen(buffer); | |
messageLength = htonl(messageLength); | |
if (write(socketDes, (char*)&messageLength, sizeof(uint32_t)) < 0) | |
ERR("write message length"); | |
if (write(socketDes, recipient, USERNAME_SIZE) < 0) | |
ERR("write username"); | |
if (write(socketDes, buffer, strlen(buffer)) < 0) | |
ERR("write message"); | |
printf("Message sent\n"); | |
} | |
} | |
int createTCPSocket(char *address) | |
{ | |
int socketDes = socket(AF_INET, SOCK_STREAM, 0); | |
if (socketDes < 0) | |
ERR("socket"); | |
struct sockaddr_in tcpAddress; | |
struct addrinfo *result; | |
struct addrinfo hints = {}; | |
hints.ai_family = AF_INET; | |
int ret; | |
char port[10]; | |
snprintf(port, 10, "%d", TCP_PORT); | |
if ((ret = getaddrinfo(address, port, &hints, &result))) | |
{ | |
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(ret)); | |
exit(EXIT_FAILURE); | |
} | |
tcpAddress = *(struct sockaddr_in*)(result->ai_addr); | |
freeaddrinfo(result); | |
if (connect(socketDes, (struct sockaddr*)&tcpAddress, sizeof(struct sockaddr_in)) < 0) | |
ERR("connect"); | |
return socketDes; | |
} | |
pthread_t createListeningThread(int *socketDes) | |
{ | |
pthread_t tid; | |
if (pthread_create(&tid, NULL, printMessages, socketDes) < 0) | |
ERR("pthread_create"); | |
return tid; | |
} | |
void cleanup(pthread_t tid, int socketDes) | |
{ | |
printf("Joining thread\n"); | |
if (pthread_join(tid, NULL) < 0) | |
ERR("pthread_join"); | |
printf("Closing socket\n"); | |
if (close(socketDes) < 0) | |
ERR("socketDes"); | |
printf("Exiting\n"); | |
} | |
void parseArguments(int argc, char **argv, char *userName, char **address) | |
{ | |
if (argc != 3) | |
usage(argv[0]); | |
strncpy(userName, argv[2], USERNAME_SIZE); | |
int userNameLength = strlen(userName); | |
if (userNameLength == 0) | |
usage(argv[0]); | |
*address = argv[1]; | |
} | |
int main(int argc, char **argv) | |
{ | |
char userName[USERNAME_SIZE]; | |
char *address; | |
parseArguments(argc, argv, userName, &address); | |
int socketDes = createTCPSocket(address); | |
pthread_t tid = createListeningThread(&socketDes); | |
login(socketDes, userName); | |
readConsoleAndSendToServer(socketDes); | |
cleanup(tid, socketDes); | |
return EXIT_SUCCESS; | |
} |
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
#define _GNU_SOURCE | |
#include <stdlib.h> | |
#include <stdio.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <errno.h> | |
#include <sys/un.h> | |
#include <pthread.h> | |
#include <sys/time.h> | |
#include <stdint.h> | |
#include <netinet/in.h> | |
#include <signal.h> | |
#define SOCKET_UNIX_PATH "/tmp/grzen-comm" | |
#define SOCKET_UNIX_PATH_SIZE 108 | |
#define SOCKET_BACKLOG 10 | |
#define MAX_USERS 30 | |
#define SOCKET_POLL_TIMEOUT 2 | |
#define USERNAME_SIZE 30 | |
#define TCP_PORT 7070 | |
typedef struct user { | |
int active; | |
int fd; | |
char name[USERNAME_SIZE]; | |
} user_t; | |
typedef struct serverData { | |
user_t *users; | |
int socketDes; | |
} serverData_t; | |
#define ERR(source) (fprintf(stderr, "%s:%d\n", __FILE__, __LINE__), \ | |
perror(source), exit(EXIT_FAILURE) ) | |
void removeLocalSocket(char *path) | |
{ | |
if (unlink(path) < 0 && errno != ENOENT) | |
ERR("unlink"); | |
} | |
int prepareUsersFds(int socketDes, user_t *users, fd_set *fdSet) | |
{ | |
int maxFd = socketDes; | |
FD_ZERO(fdSet); | |
for (int i=0; i < MAX_USERS; i++) | |
{ | |
if (users[i].active == 0) | |
continue; | |
FD_SET(users[i].fd, fdSet); | |
if (users[i].fd > maxFd) | |
maxFd = users[i].fd; | |
} | |
return maxFd; | |
} | |
int getFreeUserId(user_t *users) | |
{ | |
for (int i=0; i < MAX_USERS; i++) | |
{ | |
if (users[i].active == 0) | |
return i; | |
} | |
return -1; | |
} | |
void sendMessageToUser(user_t *users, char *recipientUserName, char *senderUserName, uint32_t messageSize, char *message) | |
{ | |
int recipientId = -1; | |
for (int j=0; j < MAX_USERS; j++) | |
{ | |
if (!users[j].active) | |
continue; | |
if (strncmp(recipientUserName, users[j].name, USERNAME_SIZE) == 0) | |
{ | |
recipientId = j; | |
break; | |
} | |
} | |
if (recipientId == -1) | |
{ | |
printf("Recipient not found\n"); | |
return; | |
} | |
uint32_t messageSizeNet = htonl(messageSize); | |
if (write(users[recipientId].fd, (char*)&messageSizeNet, sizeof(uint32_t)) < 0) | |
ERR("write message size"); | |
if (write(users[recipientId].fd, senderUserName, USERNAME_SIZE) < 0) | |
ERR("write username"); | |
if (write(users[recipientId].fd, message, messageSize) < 0) | |
ERR("write message"); | |
printf("Message transferred\n"); | |
} | |
void *transferMessages(void *serverDataPtr) | |
{ | |
serverData_t *srvData = (serverData_t*)serverDataPtr; | |
int socketDes = srvData->socketDes; | |
user_t *users = srvData->users; | |
printf("Thread started\n"); | |
fd_set baseFds; | |
int maxFd; | |
struct timeval baseTimeout; | |
baseTimeout.tv_sec = SOCKET_POLL_TIMEOUT; | |
baseTimeout.tv_usec = 0; | |
while (1) | |
{ | |
struct timeval timeout = baseTimeout; | |
maxFd = prepareUsersFds(socketDes, users, &baseFds); | |
if (select(maxFd + 1, &baseFds, NULL, NULL, &timeout) < 0) | |
ERR("select"); | |
if (timeout.tv_sec == 0 && timeout.tv_usec == 0) | |
continue; | |
for (int i=0; i < MAX_USERS; i++) | |
{ | |
if (!users[i].active) | |
continue; | |
int fd = users[i].fd; | |
if (!FD_ISSET(fd, &baseFds)) | |
continue; | |
uint32_t messageSize; | |
ssize_t bytesRead = read(fd, (char*)&messageSize, sizeof(uint32_t)); | |
if (bytesRead < 0) | |
ERR("read"); | |
else if (bytesRead == 0) | |
{ | |
printf("Client %s disconnected\n", users[i].name); | |
if (close(users[i].fd) < 0) | |
ERR("close"); | |
users[i].active = 0; | |
continue; | |
} | |
messageSize = ntohl(messageSize); | |
char recipient[USERNAME_SIZE]; | |
if (read(fd, recipient, USERNAME_SIZE) < 0) | |
ERR("read"); | |
printf("Message of length %d from %s to %s\n", messageSize, users[i].name, recipient); | |
char *message = malloc(messageSize * sizeof(char)); | |
if (message == NULL) | |
ERR("malloc"); | |
if (read(fd, message, messageSize) < 0) | |
ERR("read"); | |
printf("Message: %s\n", message); | |
sendMessageToUser(users, recipient, users[i].name, messageSize, message); | |
free(message); | |
} | |
} | |
printf("Thread exits\n"); | |
return NULL; | |
} | |
int createUnixSocket() | |
{ | |
int socketDes = socket(AF_LOCAL, SOCK_STREAM, 0); | |
if (socketDes < 0) | |
ERR("socket"); | |
printf("Socket created\n"); | |
return socketDes; | |
} | |
void bindUnixSocket(int socketDes) | |
{ | |
struct sockaddr_un unixAddress; | |
memset(&unixAddress, 0, sizeof(struct sockaddr_un)); | |
unixAddress.sun_family = AF_LOCAL; | |
strncpy(unixAddress.sun_path, SOCKET_UNIX_PATH, SOCKET_UNIX_PATH_SIZE); | |
if (bind(socketDes, (struct sockaddr*)&unixAddress, sizeof(struct sockaddr_un)) < 0) | |
ERR("bind"); | |
if (listen(socketDes, SOCKET_BACKLOG) < 0) | |
ERR("listen"); | |
printf("Socket bound\n"); | |
} | |
pthread_t startMessageRelayThread(serverData_t *srvData) | |
{ | |
pthread_t tid; | |
if (pthread_create(&tid, NULL, transferMessages, (void*)srvData) < 0) | |
ERR("pthread_create"); | |
return tid; | |
} | |
void cleanup(user_t *users, int socketDes) | |
{ | |
for (int i=0; i < MAX_USERS; i++) | |
{ | |
if (users[i].active == 0) | |
continue; | |
if (close(users[i].fd) < 0) | |
ERR("close"); | |
} | |
if (close(socketDes) < 0) | |
ERR("close"); | |
removeLocalSocket(SOCKET_UNIX_PATH); | |
} | |
void acceptNewUser(int socketDes, user_t *users) | |
{ | |
int fd = accept(socketDes, NULL, NULL); | |
if (fd < 0) | |
ERR("accept"); | |
int userId = getFreeUserId(users); | |
if (userId == -1) | |
{ | |
printf("Server full, rejecting new user\n"); | |
if (close(userId) < 0) | |
ERR("close"); | |
return; | |
} | |
printf("User connected, waiting for username\n"); | |
users[userId].fd = fd; | |
char username[USERNAME_SIZE]; | |
if (read(fd, username, USERNAME_SIZE) < 0) | |
ERR("read"); | |
printf("User %s connected and added to the list\n", username); | |
strncpy(users[userId].name, username, USERNAME_SIZE); | |
users[userId].active = 1; | |
} | |
void *acceptNewUsersTCP(void *srvDataPtr) | |
{ | |
serverData_t *srvData = (serverData_t*)srvDataPtr; | |
int socketDes = srvData->socketDes; | |
user_t *users = srvData->users; | |
while (1) | |
acceptNewUser(socketDes, users); | |
return NULL; | |
} | |
int createTCPSocket() | |
{ | |
int socketDes = socket(AF_INET, SOCK_STREAM, 0); | |
if (socket < 0) | |
ERR("socket"); | |
return socketDes; | |
} | |
void bindTCPSocket(int socketDes) | |
{ | |
struct sockaddr_in tcpAddr; | |
tcpAddr.sin_family = AF_INET; | |
tcpAddr.sin_port = htons(TCP_PORT); | |
tcpAddr.sin_addr.s_addr = htonl(INADDR_ANY); | |
int t = 1; | |
if (setsockopt(socketDes, SOL_SOCKET, SO_REUSEADDR, &t, sizeof(t)) < 0) | |
ERR("setsockopt"); | |
if (bind(socketDes, (struct sockaddr*)&tcpAddr, sizeof(struct sockaddr_in)) < 0) | |
ERR("bind"); | |
if (listen(socketDes, SOCKET_BACKLOG) < 0) | |
ERR("listen"); | |
} | |
int main(int argc, char **argv) | |
{ | |
removeLocalSocket(SOCKET_UNIX_PATH); | |
int socketDes = createUnixSocket(); | |
bindUnixSocket(socketDes); | |
int socketDesTCP = createTCPSocket(); | |
bindTCPSocket(socketDesTCP); | |
user_t users[MAX_USERS]; | |
for (int i=0; i < MAX_USERS; i++) | |
users[i].active = 0; | |
serverData_t srvDataUnix; | |
srvDataUnix.users = users; | |
srvDataUnix.socketDes = socketDes; | |
serverData_t srvDataTCP; | |
srvDataTCP.users = users; | |
srvDataTCP.socketDes = socketDesTCP; | |
printf("Starting message relay thread\n"); | |
pthread_t tid = startMessageRelayThread(&srvDataUnix); | |
pthread_t tcpThread; | |
if (pthread_create(&tcpThread, NULL, acceptNewUsersTCP, &srvDataTCP) < 0) | |
ERR("pthread_create"); | |
printf("Accepting new users\n"); | |
while(1) | |
acceptNewUser(socketDes, users); | |
printf("Killing the transferring thread\n"); | |
if (pthread_kill(tid, SIGINT) != 0) | |
ERR("pthread_kill"); | |
printf("Joining the thread\n"); | |
if (pthread_join(tid, NULL) < 0) | |
ERR("pthread_join"); | |
printf("Cleanup\n"); | |
cleanup(users, socketDes); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment