Initial commit

This commit is contained in:
seajee
2025-04-25 01:52:14 +02:00
commit 04c671220e
11 changed files with 690 additions and 0 deletions

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
hello_server
hello_client
chat_server
chat_client
udp_server
udp_client

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 seajee
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

31
Makefile Normal file
View File

@@ -0,0 +1,31 @@
CC=gcc
CFLAGS=-Wall -Wextra -ggdb -I.
LDFLAGS=-lpthread
all: hello_server hello_client chat_server chat_client udp_server udp_client
chat_server: examples/chat_server.c
$(CC) $(CFLAGS) -o chat_server examples/chat_server.c $(LDFLAGS)
chat_client: examples/chat_client.c
$(CC) $(CFLAGS) -o chat_client examples/chat_client.c $(LDFLAGS)
hello_server: examples/hello_server.c
$(CC) $(CFLAGS) -o hello_server examples/hello_server.c $(LDFLAGS)
hello_client: examples/hello_client.c
$(CC) $(CFLAGS) -o hello_client examples/hello_client.c $(LDFLAGS)
udp_server: examples/udp_server.c
$(CC) $(CFLAGS) -o udp_server examples/udp_server.c $(LDFLAGS)
udp_client: examples/udp_client.c
$(CC) $(CFLAGS) -o udp_client examples/udp_client.c $(LDFLAGS)
clean:
rm -rf chat_server
rm -rf chat_client
rm -rf hello_server
rm -rf hello_client
rm -rf udp_server
rm -rf udp_client

13
README.md Normal file
View File

@@ -0,0 +1,13 @@
# sock.h
A simple C socket library wrapper. Supporting TCP and UDP via IPv4 and IPv6.
## Quick start
There are many examples in the repository that show how this library
simplifies the usage of sockets in C. To build them you can use the Makefile
provided.
```console
make
```

74
examples/chat_client.c Normal file
View File

@@ -0,0 +1,74 @@
#include <stdio.h>
#include <stdbool.h>
#include <pthread.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
#define DEFAULT_ADDRESS "127.0.0.1"
#define PORT 6969
#define BUFFER_CAPACITY 4096
bool disconnected = false;
void *listen_thread(void *user_data)
{
Sock *server = (Sock*) user_data;
char buffer[BUFFER_CAPACITY];
ssize_t received = 0;
while ((received = sock_recv(server, buffer, sizeof(buffer))) > 0) {
printf("%.*s\n", (int)received, buffer);
}
disconnected = true;
return NULL;
}
int main(int argc, char **argv)
{
const char *address = DEFAULT_ADDRESS;
if (argc > 1) {
address = argv[1];
}
Sock *client = sock_create(SOCK_IPV4, SOCK_TCP);
if (client == NULL) {
fprintf(stderr, "ERROR: Could not create socket\n");
return EXIT_FAILURE;
}
if (!sock_connect(client, sock_addr(address, PORT))) {
fprintf(stderr, "ERROR: Could not connect to server\n");
sock_close(client);
return EXIT_FAILURE;
}
pthread_t ltid;
if (pthread_create(&ltid, NULL, &listen_thread, client) != 0) {
fprintf(stderr, "ERROR: Could not create listening thread\n");
sock_close(client);
return EXIT_FAILURE;
}
pthread_detach(ltid);
printf("INFO: Connected to the server\n");
printf("INFO: Type `disconnect` to disconnect from the server\n");
char buffer[BUFFER_CAPACITY];
while (!disconnected) {
memset(buffer, 0, sizeof(buffer));
fgets(buffer, sizeof(buffer), stdin);
if (strcmp(buffer, "disconnect\n") == 0) {
break;
}
size_t len = strlen(buffer)-1;
sock_send(client, buffer, len);
}
sock_close(client);
printf("INFO: Closed socket\n");
return EXIT_SUCCESS;
}

179
examples/chat_server.c Normal file
View File

@@ -0,0 +1,179 @@
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
#define PORT 6969
#define POOL_CAPACITY 16
#define BUFFER_CAPACITY 4096
#define USERNAME_CAPACITY 16
Sock *socket_pool[POOL_CAPACITY];
pthread_mutex_t pool_lock;
bool add_client(Sock *sock)
{
bool added = false;
pthread_mutex_lock(&pool_lock);
for (size_t i = 0; i < POOL_CAPACITY; ++i) {
if (socket_pool[i] == NULL) {
socket_pool[i] = sock;
added = true;
break;
}
}
pthread_mutex_unlock(&pool_lock);
return added;
}
bool remove_client(Sock *sock) {
bool removed = false;
pthread_mutex_lock(&pool_lock);
for (size_t i = 0; i < POOL_CAPACITY; ++i) {
if (socket_pool[i] == sock) {
socket_pool[i] = NULL;
removed = true;
break;
}
}
pthread_mutex_unlock(&pool_lock);
return removed;
}
void broadcast(const Sock *from, const char *msg, size_t msg_len)
{
pthread_mutex_lock(&pool_lock);
for (size_t i = 0; i < POOL_CAPACITY; ++i) {
Sock *r = socket_pool[i];
if (r != NULL && r != from) {
sock_send(r, msg, msg_len);
}
}
pthread_mutex_unlock(&pool_lock);
}
void *handle_client(void *user_data)
{
Sock *client = (Sock*) user_data;
ssize_t received = 0;
char buffer[BUFFER_CAPACITY];
memset(buffer, 0, sizeof(buffer));
char username[USERNAME_CAPACITY];
size_t username_length = 0;
memset(username, 0, sizeof(username));
const char *username_prompt = "username:";
size_t prompt_length = strlen(username_prompt);
memcpy(buffer, "username: ", prompt_length);
sock_send(client, buffer, prompt_length);
received = sock_recv(client, username, sizeof(username));
if (received < 0) {
goto disconnect;
}
username_length = received;
printf("INFO: Client login with username `%.*s`\n", (int)username_length,
username);
received = snprintf(buffer, sizeof(buffer), "[Server] `%.*s` joined the chat",
(int)username_length, username);
broadcast(client, buffer, received);
memcpy(buffer, username, username_length);
buffer[username_length] = ':';
buffer[username_length + 1] = ' ';
size_t prefix_len = username_length + 2;
size_t read_len = sizeof(buffer) - prefix_len;
char *read_buf = buffer + prefix_len;
while ((received = sock_recv(client, read_buf, read_len)) > 0) {
printf("INFO: %.*s: %.*s\n", (int)username_length, username,
(int)received, read_buf);
broadcast(client, buffer, received + prefix_len);
}
disconnect:
sock_close(client);
remove_client(client);
printf("INFO: Client `%.*s` disconnected\n", (int)username_length,
username);
received = snprintf(buffer, sizeof(buffer), "[Server] `%.*s` left the chat",
(int)username_length, username);
broadcast(client, buffer, received);
return NULL;
}
int main(void)
{
Sock *server = sock_create(SOCK_IPV4, SOCK_TCP);
if (server == NULL) {
fprintf(stderr, "ERROR: Could not create socket\n");
return EXIT_FAILURE;
}
printf("INFO: Created socket\n");
if (!sock_bind(server, sock_addr("0.0.0.0", PORT))) {
fprintf(stderr, "ERROR: Could not bind socket\n");
sock_close(server);
return EXIT_FAILURE;
}
printf("INFO: Bind socket\n");
if (!sock_listen(server, 10)) {
fprintf(stderr, "ERROR: Could not listen on socket\n");
sock_close(server);
return EXIT_FAILURE;
}
printf("INFO: Listen socket\n");
while (true) {
Sock *client = sock_accept(server);
if (client == NULL) {
fprintf(stderr, "ERROR: Could not accept client\n");
sock_close(client);
continue;
}
if (!add_client(client)) {
fprintf(stderr, "ERROR: TCP_Socket pool buffer is full (%d)\n",
POOL_CAPACITY);
sock_close(client);
continue;
}
// printf("INFO: Pool:\n");
// pthread_mutex_lock(&pool_lock);
// for (size_t i = 0; i < POOL_CAPACITY; ++i) {
// printf(" %ld: %p\n", i, socket_pool[i]);
// }
// pthread_mutex_unlock(&pool_lock);
// printf("INFO: End pool:\n");
pthread_t thread;
if (pthread_create(&thread, NULL, &handle_client, client) != 0) {
fprintf(stderr, "ERROR: Could not create thread\n");
sock_close(client);
continue;
}
pthread_detach(thread);
printf("INFO: New client connected\n");
}
sock_close(server);
printf("INFO: Closed socket\n");
return EXIT_SUCCESS;
}

36
examples/hello_client.c Normal file
View File

@@ -0,0 +1,36 @@
#include <stdio.h>
#include <errno.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
int main(void)
{
char *err = "None";
Sock *sock = sock_create(SOCK_IPV4, SOCK_TCP);
if (sock == NULL) { err = "create"; goto defer; }
printf("create\n");
SockAddr addr = sock_addr("127.0.0.1", 6969);
if (!sock_connect(sock, addr)) { err = "connect"; goto defer; }
printf("connect\n");
const char *msg = "Hello from client!";
char buf[128];
memset(buf, 0, sizeof(buf));
sock_send(sock, msg, strlen(msg));
printf("send\n");
sock_recv(sock, buf, sizeof(buf));
printf("recv\n");
printf("%.*s\n", (int)sizeof(buf), buf);
defer:
sock_close(sock);
printf("close\n");
fprintf(stderr, "ERROR: %s: %s\n", err, strerror(errno));
return 0;
}

43
examples/hello_server.c Normal file
View File

@@ -0,0 +1,43 @@
#include <stdio.h>
#include <errno.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
int main(void) {
char *err = "None";
Sock *sock = sock_create(SOCK_IPV4, SOCK_TCP);
if (sock == NULL) { err = "create"; goto defer; }
printf("create\n");
SockAddr addr = sock_addr("0.0.0.0", 6969);
if (!sock_bind(sock, addr)) { err = "bind"; goto defer; }
printf("bind\n");
if (!sock_listen(sock, 16)) { err = "listen"; goto defer; }
printf("listen\n");
Sock *client = sock_accept(sock);
if (client == NULL) { err = "accept"; goto defer; }
printf("accept\n");
const char *msg = "Hello from server!";
char buf[128];
memset(buf, 0, sizeof(buf));
sock_send(client, msg, strlen(msg));
printf("send\n");
sock_recv(client, buf, sizeof(buf));
printf("recv\n");
printf("%.*s\n", (int)sizeof(buf), buf);
sock_close(client);
printf("close\n");
defer:
sock_close(sock);
printf("close\n");
fprintf(stderr, "ERROR: %s: %s\n", err, strerror(errno));
return 0;
}

27
examples/udp_client.c Normal file
View File

@@ -0,0 +1,27 @@
#include <stdio.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
int main(void)
{
Sock *sock = sock_create(SOCK_IPV4, SOCK_UDP);
if (sock == NULL) {
perror("sock_create");
return 1;
}
const char *msg = "Hello from client!";
sock_sendto(sock, msg, strlen(msg), sock_addr("127.0.0.1", 6969));
SockAddr server_addr;
char buf[128];
memset(buf, 0, sizeof(buf));
sock_recvfrom(sock, buf, sizeof(buf), &server_addr);
printf("Received \"%.*s\" from %s:%d\n", (int)sizeof(buf), buf,
server_addr.str, server_addr.port);
return 0;
}

35
examples/udp_server.c Normal file
View File

@@ -0,0 +1,35 @@
#include <stdio.h>
#define SOCK_IMPLEMENTATION
#include "sock.h"
int main(void)
{
Sock *server = sock_create(SOCK_IPV4, SOCK_UDP);
if (server == NULL) {
perror("sock_create");
return 1;
}
if (!sock_bind(server, sock_addr("0.0.0.0", 6969))) {
perror("sock_bind");
sock_close(server);
return 1;
}
SockAddr client_addr;
char buf[128];
memset(buf, 0, sizeof(buf));
sock_recvfrom(server, buf, sizeof(buf), &client_addr);
printf("Received \"%.*s\" from %s:%d\n", (int)sizeof(buf), buf,
client_addr.str, client_addr.port);
const char *msg = "Hello from server!";
sock_sendto(server, msg, strlen(msg), client_addr);
sock_close(server);
return 0;
}

225
sock.h Normal file
View File

@@ -0,0 +1,225 @@
#ifndef SOCK_H_
#define SOCK_H_
#include <arpa/inet.h>
#include <assert.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define SOCK_ADDR_STR_CAPACITY 64
typedef enum {
SOCK_ADDR_INVALID,
SOCK_IPV4,
SOCK_IPV6
} SockAddrType;
typedef struct {
SockAddrType type;
int port;
struct sockaddr sockaddr;
socklen_t len;
char str[SOCK_ADDR_STR_CAPACITY];
} SockAddr;
typedef enum {
SOCK_TCP,
SOCK_UDP
} SockType;
typedef struct {
SockType type;
SockAddr addr;
int fd;
} Sock;
SockAddr sock_addr(const char *addr, int port);
Sock *sock_create(SockAddrType domain, SockType type);
bool sock_bind(Sock *sock, SockAddr addr);
bool sock_listen(Sock *sock, int backlog);
Sock *sock_accept(Sock *sock);
bool sock_connect(Sock *sock, SockAddr addr);
ssize_t sock_send(Sock *sock, const void *buf, size_t size);
ssize_t sock_recv(Sock *sock, void *buf, size_t size);
ssize_t sock_sendto(Sock *sock, const void *buf, size_t size, SockAddr addr);
ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr);
void sock_close(Sock *sock);
#endif // SOCK_H_
#ifdef SOCK_IMPLEMENTATION
SockAddr sock_addr(const char *addr, int port)
{
SockAddr sa = {0};
if (inet_pton(AF_INET, addr, &sa.sockaddr) == 1) {
sa.type = SOCK_IPV4;
sa.len = sizeof(struct sockaddr_in);
struct sockaddr_in *in = (struct sockaddr_in*)&sa.sockaddr;
in->sin_family = AF_INET;
in->sin_port = htons(port);
} else if (inet_pton(AF_INET6, addr, &sa.sockaddr) == 1) {
sa.type = SOCK_IPV6;
sa.len = sizeof(struct sockaddr_in6);
struct sockaddr_in6 *in6 = (struct sockaddr_in6*)&sa.sockaddr;
in6->sin6_family = AF_INET6;
in6->sin6_port = htons(port);
} else {
sa.type = SOCK_ADDR_INVALID;
return sa;
}
sa.port = port;
strncpy(sa.str, addr, sizeof(sa.str));
return sa;
}
Sock *sock_create(SockAddrType domain, SockType type)
{
Sock *sock = malloc(sizeof(*sock));
if (sock == NULL) {
return NULL;
}
memset(sock, 0, sizeof(*sock));
sock->type = type;
int sock_domain = 0;
int sock_type = 0;
switch (domain) {
case SOCK_IPV4: sock_domain = AF_INET; break;
case SOCK_IPV6: sock_domain = AF_INET6; break;
default: {
free(sock);
return NULL;
} break;
}
switch (type) {
case SOCK_TCP: sock_type = SOCK_STREAM; break;
case SOCK_UDP: sock_type = SOCK_DGRAM; break;
default: {
free(sock);
return NULL;
} break;
}
sock->fd = socket(sock_domain, sock_type, 0);
if (sock->fd < 0) {
free(sock);
return NULL;
}
return sock;
}
bool sock_bind(Sock *sock, SockAddr addr)
{
if (bind(sock->fd, &addr.sockaddr, addr.len) < 0) {
return false;
}
sock->addr = addr;
return true;
}
bool sock_listen(Sock *sock, int backlog)
{
if (listen(sock->fd, backlog) < 0) {
return false;
}
return true;
}
Sock *sock_accept(Sock *sock)
{
int fd = accept(sock->fd, &sock->addr.sockaddr, &sock->addr.len);
if (fd < 0) {
return NULL;
}
Sock *res = malloc(sizeof(*res));
if (res == NULL) {
return NULL;
}
memset(res, 0, sizeof(*res));
res->type = sock->type;
res->fd = fd;
return res;
}
bool sock_connect(Sock *sock, SockAddr addr)
{
if (connect(sock->fd, &addr.sockaddr, addr.len) < 0) {
return false;
}
return true;
}
ssize_t sock_send(Sock *sock, const void *buf, size_t size)
{
return send(sock->fd, buf, size, 0);
}
ssize_t sock_recv(Sock *sock, void *buf, size_t size)
{
return recv(sock->fd, buf, size, 0);
}
ssize_t sock_sendto(Sock *sock, const void *buf, size_t size, SockAddr addr)
{
return sendto(sock->fd, buf, size, 0, &addr.sockaddr, addr.len);
}
ssize_t sock_recvfrom(Sock *sock, void *buf, size_t size, SockAddr *addr)
{
ssize_t res = recvfrom(sock->fd, buf, size, 0, &addr->sockaddr,
&addr->len);
switch (addr->sockaddr.sa_family) {
case AF_INET: {
struct sockaddr_in *sa = (struct sockaddr_in*)&addr->sockaddr;
addr->type = SOCK_IPV4;
addr->port = ntohs(sa->sin_port);
addr->len = sizeof(*sa);
inet_ntop(AF_INET, &sa->sin_addr, addr->str, sizeof(addr->str));
} break;
case AF_INET6: {
struct sockaddr_in6 *sa = (struct sockaddr_in6*)&addr->sockaddr;
addr->type = SOCK_IPV6;
addr->port = ntohs(sa->sin6_port);
addr->len = sizeof(*sa);
inet_ntop(AF_INET6, &sa->sin6_addr, addr->str, sizeof(addr->str));
} break;
default: {
assert(false && "Unsupported address family");
} break;
}
return res;
}
void sock_close(Sock *sock)
{
close(sock->fd);
free(sock);
}
#endif // SOCK_IMPLEMENTATION