171 lines
5.3 KiB
C
171 lines
5.3 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <pthread.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
|
|
static void sockaddr2str(const struct sockaddr *sa, char *s, const size_t maxlen) {
|
|
if (sa->sa_family == AF_INET) {
|
|
const struct sockaddr_in *sin = (struct sockaddr_in *)sa;
|
|
inet_ntop(AF_INET, &sin->sin_addr, s, maxlen);
|
|
const size_t len = strnlen(s, maxlen);
|
|
snprintf(s + len, maxlen - len, ":%u", ntohs(sin->sin_port));
|
|
return;
|
|
}
|
|
|
|
if (sa->sa_family == AF_INET6) {
|
|
const struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
|
|
s[0] = '[';
|
|
inet_ntop(AF_INET6, &sin6->sin6_addr, s + 1, maxlen - 1);
|
|
const size_t len = strnlen(s, maxlen);
|
|
snprintf(s + len, maxlen - len, "]:%u", ntohs(sin6->sin6_port));
|
|
return;
|
|
}
|
|
|
|
strncpy(s, "unknown", maxlen);
|
|
}
|
|
|
|
#define MAX_CLIENTS 100
|
|
#define BUFFER_SIZE 1024
|
|
|
|
struct {
|
|
int sockets[MAX_CLIENTS];
|
|
int count;
|
|
pthread_mutex_t lock;
|
|
} clients = {0};
|
|
|
|
typedef struct {
|
|
int sock;
|
|
struct sockaddr_in6 addr;
|
|
} pthread_input_t;
|
|
|
|
#define tell_everyone(...) do { \
|
|
pthread_mutex_lock(&clients.lock); \
|
|
printf(__VA_ARGS__); \
|
|
for (int i = 0; i < clients.count; i++) { \
|
|
if (dprintf(clients.sockets[i], __VA_ARGS__) < 0) { \
|
|
close(clients.sockets[i]); \
|
|
clients.sockets[i] = clients.sockets[--clients.count]; \
|
|
i--; \
|
|
} \
|
|
} \
|
|
pthread_mutex_unlock(&clients.lock); \
|
|
} while (0)
|
|
|
|
void* handle_client(void* _arg) {
|
|
const pthread_input_t* arg = _arg;
|
|
const int client_fd = arg->sock;
|
|
const struct sockaddr_in6 addr = arg->addr;
|
|
free(_arg);
|
|
|
|
char message[BUFFER_SIZE];
|
|
|
|
FILE* reader = fdopen(client_fd, "r");
|
|
|
|
if (reader == NULL) {
|
|
perror("fdopen");
|
|
close(client_fd);
|
|
return NULL;
|
|
}
|
|
|
|
while (fgets(message, BUFFER_SIZE, reader) != NULL) {
|
|
message[strcspn(message, "\n")] = 0;
|
|
if (message[0] == '\0') continue;
|
|
|
|
char client_ip[INET6_ADDRSTRLEN + 8];
|
|
sockaddr2str((struct sockaddr*)&addr, client_ip, sizeof(client_ip));
|
|
tell_everyone("%s: %s\n", client_ip, message);
|
|
}
|
|
pthread_mutex_lock(&clients.lock);
|
|
for (int i = 0; i < clients.count; i++) {
|
|
if (clients.sockets[i] != client_fd) continue;
|
|
|
|
close(clients.sockets[i]);
|
|
clients.sockets[i] = clients.sockets[--clients.count];
|
|
break;
|
|
}
|
|
pthread_mutex_unlock(&clients.lock);
|
|
char client_ip[INET6_ADDRSTRLEN + 8];
|
|
sockaddr2str((struct sockaddr*)&addr, client_ip, sizeof(client_ip));
|
|
tell_everyone("- %s. total clients: %d\n", client_ip, clients.count);
|
|
return NULL;
|
|
}
|
|
|
|
#define streq(a, b) (strcmp((a), (b)) == 0)
|
|
#define exit_error(...) do { \
|
|
fprintf(stderr, __VA_ARGS__); \
|
|
exit(EXIT_FAILURE); \
|
|
} while (0)
|
|
|
|
#define exit_perror(s) do { \
|
|
perror(s); \
|
|
exit(EXIT_FAILURE); \
|
|
} while (0)
|
|
|
|
int main(int argc, char* argv[]) {
|
|
struct in6_addr bind_addr = in6addr_any;
|
|
int port = 5000;
|
|
for (int i = 1; i < argc; i++) {
|
|
if (streq(argv[i], "--addr") && i + 1 < argc) {
|
|
if (inet_pton(AF_INET6, argv[++i], &bind_addr) != 1) {
|
|
struct in_addr v4;
|
|
if (inet_pton(AF_INET, argv[i], &v4) != 1) exit_error("invalid address: %s\n", argv[i]);
|
|
memset(&bind_addr, 0, sizeof(bind_addr));
|
|
bind_addr.s6_addr[10] = 0xff;
|
|
bind_addr.s6_addr[11] = 0xff;
|
|
memcpy(&bind_addr.s6_addr[12], &v4, 4);
|
|
}
|
|
}
|
|
else {
|
|
port = atoi(argv[i]);
|
|
if (port <= 0) exit_error("unknown argument: %s\n", argv[i]);
|
|
}
|
|
}
|
|
|
|
const int server_fd = socket(AF_INET6, SOCK_STREAM, 0);
|
|
if (server_fd < 0) exit_error("socket");
|
|
|
|
int opt = 0;
|
|
setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt));
|
|
opt = 1;
|
|
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
|
|
|
struct sockaddr_in6 server_addr = {
|
|
.sin6_family = AF_INET6,
|
|
.sin6_addr = bind_addr,
|
|
.sin6_port = htons(port)
|
|
};
|
|
|
|
if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) exit_perror("bind");
|
|
if (listen(server_fd, 128) < 0) exit_perror("listen");
|
|
|
|
tell_everyone("chatserver successfully started at port %d\n", port);
|
|
|
|
pthread_mutex_init(&clients.lock, NULL);
|
|
for (;;) {
|
|
struct sockaddr_in6 client_addr;
|
|
socklen_t client_len = sizeof(client_addr);
|
|
int client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &client_len);
|
|
if (client_fd < 0) {
|
|
close(client_fd);
|
|
continue;
|
|
}
|
|
|
|
pthread_mutex_lock(&clients.lock);
|
|
if (clients.count < MAX_CLIENTS) clients.sockets[clients.count++] = client_fd;
|
|
|
|
pthread_input_t* pi = malloc(sizeof(pthread_input_t));
|
|
pi->sock = client_fd;
|
|
pi->addr = client_addr;
|
|
pthread_t thread;
|
|
pthread_create(&thread, NULL, handle_client, pi);
|
|
pthread_detach(thread);
|
|
pthread_mutex_unlock(&clients.lock);
|
|
char ip_buf[INET6_ADDRSTRLEN + 8];
|
|
sockaddr2str((struct sockaddr*)&client_addr, ip_buf, sizeof(ip_buf));
|
|
tell_everyone("+ %s. total clients: %d\n", ip_buf, clients.count);
|
|
}
|
|
} |