initial commit
This commit is contained in:
8
LICENSE
Normal file
8
LICENSE
Normal file
@@ -0,0 +1,8 @@
|
||||
ISC License:
|
||||
|
||||
Copyright (c) 2004-2010 by Internet Systems Consortium, Inc. ("ISC")
|
||||
Copyright (c) 1995-2003 by Internet Software Consortium
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
10
Makefile
Normal file
10
Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
chatserver: chatserver.c
|
||||
cc chatserver.c -o chatserver -O3
|
||||
|
||||
#chatclient: chatclient.c
|
||||
# gcc -o chatclient chatclient.c `pkg-config --cflags --libs gtk+-3.0` -lpthread
|
||||
|
||||
chatclient-cli: chatclient-cli.noc
|
||||
cc chatclient-cli.c -o chatclient-cli -lpthread -lncurses
|
||||
|
||||
all: chatserver chatclient-cli
|
||||
460
chatclient-cli.c
Executable file
460
chatclient-cli.c
Executable file
@@ -0,0 +1,460 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
#include <time.h>
|
||||
#include <locale.h>
|
||||
|
||||
#include <ncurses.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <netinet/in.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
#include <netdb.h>
|
||||
#include <pthread.h>
|
||||
|
||||
struct {
|
||||
bool title_header;
|
||||
bool message_field;
|
||||
bool input_field;
|
||||
struct { int x, y; } cursor;
|
||||
int scroll_offset;
|
||||
int unread;
|
||||
} render_state = {0};
|
||||
|
||||
struct input_state_t {
|
||||
char buffer[1024];
|
||||
int buffer_size;
|
||||
int content_length;
|
||||
int cursor_pos;
|
||||
int view_start;
|
||||
};
|
||||
|
||||
int server_fd;
|
||||
FILE *reader;
|
||||
FILE *writer;
|
||||
|
||||
struct input_state_t input_state = (struct input_state_t){{0}, sizeof(input_state.buffer) - 1, 0, 0, 0};
|
||||
|
||||
static int input_state_insert_char(const char ch) {
|
||||
if (input_state.content_length >= input_state.buffer_size - 1) return 1;
|
||||
for (int i = input_state.content_length; i > input_state.cursor_pos; i--)
|
||||
input_state.buffer[i] = input_state.buffer[i - 1];
|
||||
|
||||
input_state.buffer[input_state.cursor_pos] = ch;
|
||||
input_state.content_length++;
|
||||
input_state.cursor_pos++;
|
||||
input_state.buffer[input_state.content_length] = '\0';
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void input_state_delete_char() {
|
||||
if (input_state.cursor_pos < input_state.content_length) {
|
||||
for (int i = input_state.cursor_pos; i < input_state.content_length - 1; i++) {
|
||||
input_state.buffer[i] = input_state.buffer[i + 1];
|
||||
}
|
||||
input_state.content_length--;
|
||||
input_state.buffer[input_state.content_length] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
static void input_state_backspace_char() {
|
||||
if (input_state.cursor_pos > 0) {
|
||||
input_state.cursor_pos--;
|
||||
input_state_delete_char();
|
||||
}
|
||||
}
|
||||
|
||||
static int input_state_handle_input_line(const int ch) {
|
||||
render_state.input_field = 0;
|
||||
if (ch == '\n' || ch == '\r' || ch == KEY_ENTER) return 1;
|
||||
if (ch == KEY_BACKSPACE || ch == 127) { input_state_backspace_char(); return 0; }
|
||||
if (ch == KEY_DC) { input_state_delete_char(); return 0; }
|
||||
if (ch == KEY_LEFT) { if (input_state.cursor_pos > 0) input_state.cursor_pos--; return 0; }
|
||||
if (ch == KEY_RIGHT) { if (input_state.cursor_pos < input_state.content_length) input_state.cursor_pos++; return 0; }
|
||||
if (ch == KEY_HOME) { input_state.cursor_pos = 0; return 0; }
|
||||
if (ch == KEY_END) { input_state.cursor_pos = input_state.content_length; return 0; }
|
||||
if (ch == KEY_PPAGE) {
|
||||
render_state.scroll_offset += LINES - 3 - 1 -2;
|
||||
render_state.message_field = false;
|
||||
render_state.title_header = false;
|
||||
return 0;
|
||||
}
|
||||
if (ch == KEY_NPAGE) {
|
||||
render_state.scroll_offset -= LINES - 3 - 1 -2;
|
||||
if (render_state.scroll_offset < 0) render_state.scroll_offset = 0;
|
||||
render_state.message_field = false;
|
||||
render_state.title_header = false;
|
||||
return 0;
|
||||
}
|
||||
if (ch == KEY_UP) {
|
||||
render_state.scroll_offset++;
|
||||
render_state.message_field = false;
|
||||
render_state.title_header = false;
|
||||
return 0;
|
||||
}
|
||||
if (ch == KEY_DOWN) {
|
||||
if (render_state.scroll_offset > 0) {
|
||||
render_state.scroll_offset--;
|
||||
render_state.message_field = false;
|
||||
render_state.title_header = false;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ch >= 32 && ch <= 126) input_state_insert_char((char)ch);
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct {
|
||||
char** data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
pthread_mutex_t lock;
|
||||
} messages;
|
||||
|
||||
static void init_messages() {
|
||||
messages.size = 0;
|
||||
messages.capacity = 16;
|
||||
messages.data = malloc(sizeof(*messages.data) * messages.capacity);
|
||||
pthread_mutex_init(&messages.lock, NULL);
|
||||
}
|
||||
|
||||
static void add_new_message(const char* str) {
|
||||
pthread_mutex_lock(&messages.lock);
|
||||
if (messages.size == messages.capacity) {
|
||||
messages.capacity *= 2;
|
||||
messages.data = realloc(messages.data, sizeof(*messages.data) * messages.capacity);
|
||||
}
|
||||
messages.data[messages.size++] = strdup(str);
|
||||
pthread_mutex_unlock(&messages.lock);
|
||||
if (render_state.scroll_offset > 0) {
|
||||
render_state.scroll_offset++;
|
||||
render_state.unread++;
|
||||
render_state.title_header = 0;
|
||||
}
|
||||
render_state.message_field = 0;
|
||||
}
|
||||
|
||||
#define PRINT_SPACES(n) printw("%*s", (n), "")
|
||||
#define PROGRAM_NAME "chatclient-cli"
|
||||
|
||||
#define CC_COLOR_PAIR_TITLE_HEADER 1
|
||||
#define CC_COLOR_PAIR_INPUT 2
|
||||
|
||||
static void cc_render_title_header(
|
||||
const int x,
|
||||
const int y,
|
||||
const int w
|
||||
) {
|
||||
char system_time[20];
|
||||
const int time_length = strftime(system_time, 20, "%Y-%m-%d %H:%M:%S", localtime(&(time_t){time(NULL)}));
|
||||
|
||||
char indicator[32] = "";
|
||||
int indicator_length = 0;
|
||||
if (render_state.unread > 0) {
|
||||
indicator_length = snprintf(indicator, sizeof(indicator), " [%d unread]", render_state.unread);
|
||||
}
|
||||
|
||||
move(y, x);
|
||||
attron(COLOR_PAIR(CC_COLOR_PAIR_TITLE_HEADER));
|
||||
|
||||
const int name_length = sizeof(PROGRAM_NAME) - 1;
|
||||
const int total_length = name_length + time_length + indicator_length;
|
||||
|
||||
if (total_length + 2 > w) {
|
||||
const int padding = (w - name_length - indicator_length) / 2;
|
||||
PRINT_SPACES(padding);
|
||||
printw("%-*s", w - padding, PROGRAM_NAME);
|
||||
printw("%s", indicator);
|
||||
}
|
||||
else {
|
||||
const int left_right_padding = 2;
|
||||
const int padding = w - left_right_padding * 2 - total_length;
|
||||
|
||||
PRINT_SPACES(left_right_padding);
|
||||
printw("%s", PROGRAM_NAME);
|
||||
printw("%s", indicator);
|
||||
PRINT_SPACES(padding);
|
||||
printw("%s", system_time);
|
||||
PRINT_SPACES(left_right_padding);
|
||||
}
|
||||
|
||||
attroff(COLOR_PAIR(CC_COLOR_PAIR_TITLE_HEADER));
|
||||
}
|
||||
|
||||
static void cc_render_message_field(
|
||||
const int x,
|
||||
const int y,
|
||||
const int w,
|
||||
const int h
|
||||
) {
|
||||
for (int j = y; j < h; j++) {
|
||||
move(j, x);
|
||||
for (int i = x; i < w; i++) {
|
||||
if (j == y || j == h - 1) {
|
||||
addch(i == x ? '+' : i == w-1 ? '+' : '-');
|
||||
} else if (i == x || i == w - 1) {
|
||||
addch('|');
|
||||
} else {
|
||||
addch(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
const unsigned int inner_w = w - x - 2;
|
||||
const unsigned int inner_h = h - y - 2;
|
||||
|
||||
pthread_mutex_lock(&messages.lock);
|
||||
const int max_offset = messages.size > inner_h ? messages.size - inner_h : 0;
|
||||
if (render_state.scroll_offset > max_offset) render_state.scroll_offset = max_offset;
|
||||
|
||||
const int last = messages.size - render_state.scroll_offset;
|
||||
const int first = last - inner_h > 0 ? last - inner_h : 0;
|
||||
|
||||
for (int i = first; i < last; i++) {
|
||||
mvprintw(y + 1 + (i - first), x + 1, "%-*.*s", inner_w, inner_w, messages.data[i]);
|
||||
}
|
||||
pthread_mutex_unlock(&messages.lock);
|
||||
}
|
||||
|
||||
static void cc_render_input_field(
|
||||
const int x,
|
||||
const int y,
|
||||
const int w
|
||||
) {
|
||||
if (input_state.cursor_pos < input_state.view_start)
|
||||
input_state.view_start = input_state.cursor_pos;
|
||||
else if (input_state.cursor_pos >= input_state.view_start + w)
|
||||
input_state.view_start = input_state.cursor_pos - w + 1;
|
||||
|
||||
move(y, x);
|
||||
addch('+');
|
||||
for (int i = 0; i < w - 2; i++) addch('-');
|
||||
addch('+');
|
||||
|
||||
move(y + 1, x);
|
||||
addch('|');
|
||||
for (int i = 0; i < w - 2; i++) addch(' ');
|
||||
addch('|');
|
||||
|
||||
move(y + 1, x + 1);
|
||||
int visible_length = input_state.content_length - input_state.view_start;
|
||||
if (visible_length > w - 2) visible_length = w - 2;
|
||||
attron(COLOR_PAIR(CC_COLOR_PAIR_INPUT) | A_BOLD);
|
||||
for (int i = 0; i < visible_length; i++)
|
||||
addch(input_state.buffer[input_state.view_start + i]);
|
||||
attroff(COLOR_PAIR(CC_COLOR_PAIR_INPUT) | A_BOLD);
|
||||
|
||||
move(y + 2, x);
|
||||
addch('+');
|
||||
for (int i = 0; i < w - 2; i++) addch('-');
|
||||
addch('+');
|
||||
|
||||
const int cursor_x = input_state.cursor_pos - input_state.view_start;
|
||||
render_state.cursor.y = y + 1;
|
||||
if (cursor_x > w - 3) render_state.cursor.x = x + w - 2;
|
||||
render_state.cursor.x = x + 1 + cursor_x;
|
||||
}
|
||||
|
||||
static void cc_render_ui(
|
||||
const int x,
|
||||
const int y,
|
||||
const int w,
|
||||
const int h
|
||||
) {
|
||||
bool do_refresh = false;
|
||||
|
||||
if (!render_state.title_header) {
|
||||
render_state.title_header = true;
|
||||
cc_render_title_header(x, y, w);
|
||||
do_refresh = true;
|
||||
}
|
||||
|
||||
if (!render_state.message_field) {
|
||||
render_state.message_field = true;
|
||||
cc_render_message_field(x, y + 1, w, h - 3);
|
||||
do_refresh = true;
|
||||
}
|
||||
|
||||
if (!render_state.input_field) {
|
||||
render_state.input_field = true;
|
||||
cc_render_input_field(x, y + h - 3, w);
|
||||
do_refresh = true;
|
||||
}
|
||||
|
||||
if (do_refresh) {
|
||||
move(render_state.cursor.y, render_state.cursor.x);
|
||||
refresh();
|
||||
}
|
||||
}
|
||||
|
||||
static void update_terminal_size() {
|
||||
endwin();
|
||||
refresh();
|
||||
clear();
|
||||
initscr();
|
||||
render_state.title_header = false;
|
||||
render_state.message_field = false;
|
||||
render_state.input_field = false;
|
||||
}
|
||||
|
||||
static int handle_input() {
|
||||
timeout(500);
|
||||
|
||||
const int ch = getch();
|
||||
|
||||
if (ch == ERR) {
|
||||
return 0;
|
||||
}
|
||||
if (ch == KEY_RESIZE) {
|
||||
update_terminal_size();
|
||||
return 0;
|
||||
}
|
||||
return ch;
|
||||
}
|
||||
|
||||
#define exit_error(...) do { \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
endwin(); exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
#define exit_perror(s) do { \
|
||||
perror(s); \
|
||||
endwin(); exit(EXIT_FAILURE); \
|
||||
} while (0)
|
||||
|
||||
static int connect_to_server(struct in6_addr addr, int port) {
|
||||
struct sockaddr_in6 server_addr;
|
||||
|
||||
server_fd = socket(AF_INET6, SOCK_STREAM, 0);
|
||||
if (server_fd < 0) exit_perror("Socket creation failed");
|
||||
const int server_fd2 = dup(server_fd);
|
||||
if (server_fd2 < 0) exit_perror("dup");
|
||||
|
||||
server_addr.sin6_family = AF_INET6;
|
||||
server_addr.sin6_addr = addr;
|
||||
server_addr.sin6_port = htons(port);
|
||||
|
||||
int opt = 0;
|
||||
if (setsockopt(server_fd, IPPROTO_IPV6, IPV6_V6ONLY, &opt, sizeof(opt)) < 0) {
|
||||
// i guess so bro
|
||||
}
|
||||
|
||||
if (connect(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) exit_perror("connect");
|
||||
|
||||
if ((reader = fdopen(server_fd, "r")) == NULL) exit_perror("fdopen reader");
|
||||
if ((writer = fdopen(server_fd2, "w")) == NULL) exit_perror("fdopen writer");
|
||||
return 0;
|
||||
}
|
||||
|
||||
void *reader_thread(void *data) {
|
||||
char buffer[1078];
|
||||
while (fgets(buffer, sizeof(buffer), reader) != NULL) {
|
||||
const size_t len = strlen(buffer);
|
||||
if (buffer[len - 1] == '\n') buffer[len - 1] = '\0';
|
||||
add_new_message(buffer);
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int resolve_domain_to_ipv6(const char *hostname, struct in6_addr *server_addr) {
|
||||
struct addrinfo hints, *res;
|
||||
int found = 0;
|
||||
|
||||
memset(&hints, 0, sizeof hints);
|
||||
hints.ai_family = AF_UNSPEC;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
|
||||
if (getaddrinfo(hostname, NULL, &hints, &res) != 0) return 0;
|
||||
|
||||
for (struct addrinfo *p = res; p != NULL; p = p->ai_next) {
|
||||
if (p->ai_family == AF_INET6) {
|
||||
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
|
||||
memcpy(server_addr, &ipv6->sin6_addr, sizeof(struct in6_addr));
|
||||
found = 1;
|
||||
break;
|
||||
}
|
||||
if (p->ai_family == AF_INET && !found) {
|
||||
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
|
||||
memset(server_addr, 0, sizeof(struct in6_addr));
|
||||
server_addr->s6_addr[10] = 0xff;
|
||||
server_addr->s6_addr[11] = 0xff;
|
||||
memcpy(&server_addr->s6_addr[12], &(ipv4->sin_addr), 4);
|
||||
found = 1;
|
||||
}
|
||||
}
|
||||
|
||||
freeaddrinfo(res);
|
||||
return found;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct in6_addr server_addr = in6addr_any;
|
||||
int port = 5000;
|
||||
if (argc >= 2) {
|
||||
if (inet_pton(AF_INET6, argv[1], &server_addr) != 1) {
|
||||
struct in_addr v4;
|
||||
if (inet_pton(AF_INET, argv[1], &v4) != 1) {
|
||||
if (!resolve_domain_to_ipv6(argv[1], &server_addr)) {
|
||||
exit_error("invalid address or domain: %s\n", argv[1]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
memset(&server_addr, 0, sizeof(server_addr));
|
||||
server_addr.s6_addr[10] = 0xff;
|
||||
server_addr.s6_addr[11] = 0xff;
|
||||
memcpy(&server_addr.s6_addr[12], &v4, 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (argc >= 3) {
|
||||
port = atoi(argv[2]);
|
||||
if (port <= 0) exit_error("unknown argument: %s\n", argv[2]);
|
||||
}
|
||||
setlocale(LC_ALL, "");
|
||||
initscr();
|
||||
start_color();
|
||||
use_default_colors();
|
||||
init_pair(CC_COLOR_PAIR_TITLE_HEADER, COLOR_BLACK, COLOR_CYAN);
|
||||
init_pair(CC_COLOR_PAIR_INPUT, COLOR_GREEN, -1);
|
||||
cbreak();
|
||||
noecho();
|
||||
curs_set(1);
|
||||
keypad(stdscr, TRUE);
|
||||
|
||||
init_messages();
|
||||
|
||||
connect_to_server(server_addr, port);
|
||||
pthread_t reader_thread_id;
|
||||
pthread_create(&reader_thread_id, NULL, reader_thread, NULL);
|
||||
|
||||
time_t last_second = 0;
|
||||
|
||||
while (true) {
|
||||
cc_render_ui(0, 0, COLS, LINES);
|
||||
|
||||
const int input = handle_input();
|
||||
|
||||
if (input_state_handle_input_line(input)) {
|
||||
if (input_state.content_length > 0) {
|
||||
fprintf(writer, "%s\n", input_state.buffer);
|
||||
fflush(writer);
|
||||
input_state = (struct input_state_t){{0}, sizeof(input_state.buffer) - 1, 0, 0, 0};
|
||||
render_state.message_field = false;
|
||||
render_state.unread = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Redraw title every second
|
||||
const time_t now = time(NULL);
|
||||
if (now > last_second) {
|
||||
last_second = now;
|
||||
render_state.title_header = false;
|
||||
}
|
||||
}
|
||||
|
||||
endwin();
|
||||
return 0;
|
||||
}
|
||||
171
chatserver.c
Normal file
171
chatserver.c
Normal file
@@ -0,0 +1,171 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user