Created
March 19, 2020 02:25
-
-
Save SeungheonOh/fe7f34e394b23064a64b20226729519a to your computer and use it in GitHub Desktop.
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 <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <ctype.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <sys/epoll.h> | |
#include <sys/fcntl.h> | |
#include <netinet/in.h> | |
#include <netinet/ip.h> | |
#include <arpa/inet.h> | |
#include <unistd.h> | |
#define LISTENQ 1 // maximum length of queue of pending connection | |
#define MAXEVENTS 5 // maximum event at a time | |
#define MAXLINE 4069 //maximum characters for each line | |
#define MAXCONNECTIONS 5 | |
#define UNNAMED_USER "unnamed_user" | |
#define UNNAMED_CHANNEL "*" | |
#define UNNAMED_HOST "unknown_host" | |
// util.c | |
void string_foreach(char *s, int (*func)(int)) { | |
do | |
*s = (*func)(*s); | |
while(*s++); | |
} | |
void str_append(char *s, char *s1) { | |
s = realloc(s, sizeof(char) * (strlen(s) + strlen(s1))); | |
strcat(s, s1); | |
} | |
void str_set(char *d, char *s) { | |
d = realloc(d, sizeof(char) * strlen(s)); | |
strcpy(d, s); | |
} | |
// user.c | |
typedef struct user_t { | |
char *nick; | |
char *hostname; | |
char *channel; | |
int id; | |
int socket_fd; | |
} user_t; | |
typedef struct user_list_t { | |
int size; | |
user_t *list; | |
} user_list_t; | |
void user_list_init(user_list_t *l, int size) { | |
l->list = malloc(size * sizeof(user_t)); | |
memset(l->list, 0, sizeof(*l->list)); | |
l->size = size; | |
} | |
void user_list_free(user_list_t *l) { | |
free(l->list); | |
} | |
int user_append_user(user_list_t *l, user_t user) { | |
user_t *lp = l->list; | |
while(lp->socket_fd) ++lp; | |
if(lp - l->list >= l->size) return -1; // if it goes over the limit | |
*lp = user; | |
lp->id = lp - l->list; | |
return 0; | |
} | |
int user_add(user_list_t *l, char *nick, char *hostname, char *channel, int socket_fd) { | |
user_t user; | |
user.nick = malloc(1), | |
user.hostname = malloc(1), | |
user.channel = malloc(1), | |
user.socket_fd = socket_fd; | |
str_set(user.nick, nick); | |
str_set(user.hostname, hostname); | |
str_set(user.channel, channel); | |
return user_append_user(l, user); | |
} | |
int user_remove(user_list_t *l, int id) { | |
if(id >= l->size) return -1; | |
l->list[id] = (user_t){0}; | |
return 0; | |
} | |
user_t *get_user_by_id(user_list_t *l, int id) { | |
if(id >= l->size) return NULL; | |
return &l->list[id]; | |
} | |
user_t *get_user_by_nick(user_list_t *l, char* nick) { | |
for(int i = 0; i < l->size; i++){ | |
if(l->list[i].nick != NULL && strcmp(l->list[i].nick, nick) == 0) | |
return &l->list[i]; | |
} | |
return NULL; | |
} | |
user_t *get_user_by_socket_fd(user_list_t *l, int sfd) { | |
for(int i = 0; i < l->size; i++) | |
if(l->list[i].socket_fd == sfd) | |
return &l->list[i]; | |
return NULL; | |
} | |
int make_nonblock(int fd) { | |
int flags; | |
if((flags = fcntl(fd, F_GETFL, 0)) == -1) return -1; | |
flags |= O_NONBLOCK; | |
if(fcntl(fd, F_SETFL, flags) == -1) return -1; | |
return 0; | |
} | |
void send_to_channel(user_list_t *l, char *channel, char *message) { | |
for(int i = 0; i < l->size; i++) | |
if(l->list[i].channel != NULL && strcmp(l->list[i].channel, channel) == 0) | |
write(l->list[i].socket_fd, message, strlen(message)); | |
} | |
void send_to_channel_excluding(user_list_t *l, user_t *exclusion, char *channel, char *message) { | |
for(int i = 0; i < l->size; i++){ | |
if(l->list[i].channel != NULL && strcmp(l->list[i].channel, channel) == 0 | |
&& exclusion->id != i){ | |
printf("::::::sending to %s\n", l->list[i].nick); | |
write(l->list[i].socket_fd, message, strlen(message)); | |
} | |
} | |
} | |
// Server | |
int init_server(int port) { | |
int server_socket; // server fd | |
struct sockaddr_in server_address; | |
// Open socket | |
if((server_socket = socket(AF_INET, SOCK_STREAM, 0)) == -1) { // -1 on failure | |
fprintf(stderr, "Failed to open socket\n"); | |
return -1; | |
} | |
// Configure socket address to bind | |
bzero(&server_address, sizeof(server_address)); // clear struct first | |
server_address.sin_family = AF_INET; | |
server_address.sin_addr.s_addr = htonl(INADDR_ANY); | |
server_address.sin_port = htons(port); | |
if(bind(server_socket, (struct sockaddr *) &server_address, sizeof(server_address)) == -1) { // Bind address to socket | |
fprintf(stderr, "Failed to bind socket\n"); | |
return -1; | |
} | |
// Start listen from socket | |
if(listen(server_socket, LISTENQ) == -1) { | |
fprintf(stderr, "Failed to listen\n"); | |
return -1; | |
} | |
make_nonblock(server_socket); | |
return server_socket; | |
} | |
int main() { | |
// server init | |
int server_socket = init_server(6667); | |
if(server_socket == -1) { | |
printf("failed to init"); | |
exit(2); | |
} | |
// epoll init | |
int epoll_fd; | |
struct epoll_event event; | |
struct epoll_event *events; | |
if((epoll_fd = epoll_create1(0)) == -1) { // create epoll | |
fprintf(stderr, "Failed to create epoll\n"); | |
exit(2); | |
} | |
event.data.fd = server_socket; | |
event.events = EPOLLIN | EPOLLET; | |
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_socket, &event) == -1) { | |
fprintf(stderr, "Failed to add server socket to epoll\n"); | |
exit(2); | |
} | |
events = calloc(MAXEVENTS, sizeof(event)); | |
// user list init | |
user_list_t users; | |
user_list_init(&users, MAXCONNECTIONS); | |
while(1) { | |
int events_number = epoll_wait(epoll_fd, events, MAXEVENTS, -1); | |
for(int i = 0; i < events_number; i++) { | |
if((events[i].events & EPOLLERR) || | |
(events[i].events & EPOLLHUP) //|| | |
/*(!(events[i].events & EPOLLIN))*/){ // Unknown event or error | |
fprintf(stderr, "epoll error\n"); | |
user_remove(&users, get_user_by_socket_fd(&users, events[i].data.fd)->id); | |
close(events[i].data.fd); | |
continue; | |
} else if (events[i].data.fd == server_socket) { // if the events if from the server socket | |
struct sockaddr_in client_address; | |
socklen_t client_socket_length = sizeof(client_address); | |
int client_socket; | |
if(( client_socket | |
= accept(server_socket, (struct sockaddr *)&client_address, &client_socket_length)) == -1) { // accept pending connection request | |
fprintf(stderr, "Failed to accept request\n"); | |
exit(2); | |
} | |
make_nonblock(client_socket); | |
event.data.fd = client_socket; | |
event.events = EPOLLIN | EPOLLOUT | EPOLLET; | |
if(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event) == -1) { | |
fprintf(stderr, "Failed to add client socket to epoll"); | |
continue; | |
} | |
printf("New Client added\n"); | |
printf("Hostname: %s\n", inet_ntoa(client_address.sin_addr)); | |
user_add(&users, UNNAMED_USER, inet_ntoa(client_address.sin_addr), UNNAMED_CHANNEL, client_socket); | |
for(int i = 0; i < users.size; i++) { | |
printf(":%s \n", users.list[i].nick); | |
} | |
fflush(stdout); | |
} else { | |
user_t *user = get_user_by_socket_fd(&users, events[i].data.fd); | |
char lines[MAXLINE+1] = "\0"; | |
if(read(events[i].data.fd, lines, MAXLINE) <= 0) continue; | |
char *rest = lines; //rest of lines | |
char *line; | |
while((line = strtok_r(rest , "\n", &rest))) { | |
printf("=====Command recived=====\n"); | |
char message[MAXLINE] = "\0"; | |
char *command = strtok(line, " "); | |
string_foreach(command, &toupper); | |
printf("COMAND: %s\n", command); | |
if(strcmp(command, "NICK") == 0) { | |
char *desired_nick = strtok(NULL, " \r\n"); | |
if(get_user_by_nick(&users, desired_nick) != NULL) { | |
// When its taken by other user | |
sprintf(message, ":%s!%s %s %s %s\n", user->nick, user->hostname, "433", desired_nick, desired_nick); | |
} else if (desired_nick == NULL) { | |
// When no nickname is given | |
sprintf(message, ":%s!%s %s%s\n", user->nick, user->hostname, "431", " :No nickname given\n"); | |
} else { | |
// Setting new nickname | |
sprintf(message, ":%s!%s NICK :%s\n", user->nick, user->hostname, desired_nick); | |
str_set(user->nick, desired_nick); | |
send_to_channel(&users, user->channel, message); | |
printf("Success: changed the nickname\n"); | |
continue; | |
} | |
write(events[i].data.fd, message, strlen(message)); | |
if(strcmp(user->channel, UNNAMED_CHANNEL) != 0) { | |
} | |
} else if(strcmp(command, "USER") == 0) { | |
sprintf(message, ":server NOTICE * :*** WELCOME!\n:server NOTICE * :*** This is test!\n:server NOTICE * :*** You are %s at %s\n", | |
user->nick, user->hostname); | |
write(events[i].data.fd, message, strlen(message)); | |
} else if(strcmp(command, "JOIN") == 0) { | |
printf("JOIN\n"); | |
char *channel = strtok(NULL, " \r\n"); | |
sprintf(message, ":%s!%s JOIN #%s\n", user->nick, user->hostname, channel); | |
str_set(user->channel, channel); | |
printf("channel: %s\n", channel); | |
write(events[i].data.fd, message, strlen(message)); | |
} else if(strcmp(command, "PRIVMSG") == 0) { | |
char *m = strtok(NULL, ":"); | |
char *message_sum = malloc(1); | |
m = strtok(NULL, ":"); | |
while(m != NULL){ | |
str_append(message_sum, m); | |
m = strtok(NULL, ":"); | |
} | |
sprintf(message, ":%s!%s PRIVMSG #%s :%s\n", user->nick, user->hostname, user->channel, message_sum); | |
send_to_channel_excluding(&users, user, user->channel, message); | |
for(int i = 0; i < users.size; i++) { | |
printf("nick; %s channel: %s\n", users.list[i].nick, users.list[i].channel); | |
} | |
free(message_sum); | |
} else if (strcmp(command, "PING") == 0) { | |
sprintf(message, "%s%s\n", ":server PONG server :", strtok(NULL, " \n\r")); | |
printf("%s\n", message); | |
write(user->socket_fd, message, strlen(message)); | |
} else { | |
printf("unknown command: %s\n", command); | |
} | |
printf("=====Command Ended=====\n"); | |
fflush(stdout); | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment