Понадобилось тут вдруг у одного проекта переписать его сетевую часть, т.к. старая не справлялась, долго искал в инете примеры реализации, но увы безуспешно, поэтому и дал себе обещание написать об этом в блоге. Статья предназначена для тех кто уже имеет представление о сетевом программировании на Си.
Задачи:
libev:
Чем ещё хорош libev, кроме простоты написания с помощью его API, так это то, что библиотека может использовать различные методы получения состояния сокетов (select, kqueue, epoll(что в нашем случае), и некоторые другие).
FAQ appendix 1: как писать сервера:
https://groups.google.com/group/fido7.ru.unix.prog/browse_thread/thread/e8f8edf4f2f2447b/?hl=ru&pli=1
Linux: epoll performance and gotcha's:
http://radialmind.blogspot.com/2009/09/linux-epoll-performance-and-gotchas.html
Хабр. Еще раз об архитектуре сетевых демонов:
http://habrahabr.ru/blogs/hi/108294/
The C10K problem: (старый, но познавательный документ)
http://www.kegel.com/c10k.html
POSIX thread (pthread) libraries:
http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html
Расписывать участки кода не буду, думаю комментариев в нём должно хватить, если нет - спрашивайте.
Версии софта:
Linux 2.6.37-lqx x86_64 AMD Athlon(tm) 64 X2 Dual Core Processor 3800+ AuthenticAMD GNU/Linux
gcc (GCC) 4.5.2
Отдельное спасибо людям с ЛОРа.
Задачи:
- Сервер должен иметь реализацию сокетов через libev
- Сокеты должны быть не блокирующими (non-blocking)
- Должно быть N нитей (pthread), которые независимо будут слать сообщения всем подключенным клиентам
- Как минимум, не гнутся от DoS`елки по типу slowloris.
libev:
A full-featured and high-performance (see benchmark) event loop that is loosely modelled after libevent, but without its limitations and bugs. It is used, among others, in the GNU Virtual Private Ethernet and rxvt-unicodepackages, and in the Deliantra MORPG Server and Client.Документация, достаточно полная: http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod
Чем ещё хорош libev, кроме простоты написания с помощью его API, так это то, что библиотека может использовать различные методы получения состояния сокетов (select, kqueue, epoll(что в нашем случае), и некоторые другие).
FAQ appendix 1: как писать сервера:
https://groups.google.com/group/fido7.ru.unix.prog/browse_thread/thread/e8f8edf4f2f2447b/?hl=ru&pli=1
Linux: epoll performance and gotcha's:
http://radialmind.blogspot.com/2009/09/linux-epoll-performance-and-gotchas.html
Хабр. Еще раз об архитектуре сетевых демонов:
http://habrahabr.ru/blogs/hi/108294/
The C10K problem: (старый, но познавательный документ)
http://www.kegel.com/c10k.html
POSIX thread (pthread) libraries:
http://www.yolinux.com/TUTORIALS/LinuxTutorialPosixThreads.html
Расписывать участки кода не буду, думаю комментариев в нём должно хватить, если нет - спрашивайте.
libev_server_nonblock.c :
#include <stdio.h>Компилируем командой `gcc -o "libev_server_nonblock" "libev_server_nonblock.c" -lpthread -lev`,
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <ev.h>
#define BIND_PORT 12345
struct ev_loop *loop;
long minfd, maxfd;
char fds[4096]; // Массив с подключенными клиентами.
/*
* Нить, отсылающая текущее время каждому подключенному клиенту.
*/
static void *sender(void *_arg)
{
time_t now;
char *ct, buf[1024];
long i;
while (1) {
now = time(NULL);
ct = ctime(&now);
sprintf(buf, "%ld: %s\n", (long)_arg, ct);
/* Цикл поиска клиентов, шелестит массив fds[] */
for (i = minfd; i <= maxfd; i++)
if (fds[i] == 1) {
write(i, buf, strlen(buf));
printf("sended to fd %ld\n", i);
}
sleep((long)_arg);
}
return 0;
}
/*
* Функция, которую вызываем когда сокет переключается на приём.
*/
void read_connection(EV_P_ struct ev_io *w, int revents)
{
int size, buf_size = 1024;
char buf[1024];
size = read(w->fd, buf, buf_size);
printf("read message - '%s' from fd #%i\n", buf, w->fd);
/* Если размер пришедшего пакета <= 0 - отрубаем. */
if (size <= 0) {
if( size == -1 && errno == EAGAIN )
printf("\t EAGAIN\n");
fds[(long)w->fd] = 0;
ev_io_stop(loop, w);
close(w->fd);
free(w);
printf("\t -> closed connection (fd %i)\n", w->fd);
return;
}
write(w->fd, "Hi\n", strlen("Hi\n")); // Отправка пакета
}
/*
* Функция, вызываемая при инициализации соединения.
*/
void accept_connection(EV_P_ struct ev_io *w, int revents)
{
printf("accept connection from fd #%i\n", w->fd);
struct ev_io *io = malloc(sizeof(struct ev_io));
struct sockaddr sa;
socklen_t sizeof_sa = sizeof(sa);
long fd = accept(w->fd, &sa, &sizeof_sa);
if (fd <= 0) return;
fds[fd] = 1; // Делаем пометку что клиент подключен.
printf("fds[%ld] = 1;\n", fd);
if (fd > maxfd)
maxfd = fd;
fcntl(fd, F_SETFL, O_NONBLOCK); // Превращаем сокет fd в неблокирующий.
ev_io_init(io, read_connection, fd, EV_READ);
ev_io_start(loop, io);
}
/*
* Мейн, он и в Африке мейн.
*/
int main()
{
pthread_t thread_sender;
struct sockaddr_in addr;
int fd;
/* Создание и запуск нитей, с передачей им времени рецикла */
pthread_create( &thread_sender, NULL, sender, (void *)5);
pthread_create( &thread_sender, NULL, sender, (void *)3);
/* Создаём сокет сервера */
if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
perror("socket error");
return -1;
}
/* Создание сокета сервера и привязки к адресу сокета */
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(BIND_PORT);
addr.sin_addr.s_addr = INADDR_ANY;
/* Биндимся */
if (bind(fd, (struct sockaddr*) &addr, sizeof(addr)) != 0) {
perror("bind error");
return -1;
}
/* Начинаем слушать сокет, сервер стартовал.
* Чтобы мы могли одновременно принимать много соединений,
* выставляем backlog на 128 */
if (listen(fd, 128) < 0) {
perror("listen error");
return -1;
}
minfd = fd + 1;
maxfd = minfd;
loop = ev_default_loop(EVBACKEND_EPOLL);
/* Создаём наблюдателя за принятыми соединениями,
* принимаем соединения от клиентов
* с использованием 'accept' API libev. */
struct ev_io *io = malloc(sizeof(struct ev_io));
ev_io_init(io, accept_connection, fd, EV_READ);
ev_io_start(loop, io);
/* Начало цикла событий.
* Последним шагом будет запустить
* бесконечный цикл событий ждать их.*/
while (1)
ev_loop(loop, 10);
return 0;
}
Версии софта:
Linux 2.6.37-lqx x86_64 AMD Athlon(tm) 64 X2 Dual Core Processor 3800+ AuthenticAMD GNU/Linux
gcc (GCC) 4.5.2
Отдельное спасибо людям с ЛОРа.
Скоро подправлю/дополню эту версию, т.к. в ней есть несколько не очень хороших моментов.
ОтветитьУдалитьА можно на пальцах в чём примущество над libevent? И что-то я не нашёл, кто её использует в своих проектах.
ОтветитьУдалитьhttp://libev.schmorp.de/bench.html
ОтветитьУдалитьПеред выбором libev, я пробежался по обсуждениям по этой теме, в основном аргументы были "libev написан адекватнее, меньше воды/мусора, перспективнее"
Сам не сравнивал их, особо сказать ничего не могу, только пожалуй то, что меня вполне устраивает (не считая малой распространённости, в частности рунета)
Вдруг кто будет пробовать на FreeBSD
ОтветитьУдалитьчуток нужно поправить:
diff -r 0b9505dab680 test-libev-nonblock/compile-server
--- a/test-libev-nonblock/compile-server Thu Oct 25 15:09:37 2012 +0400
+++ b/test-libev-nonblock/compile-server Thu Oct 25 12:37:25 2012 +0100
@@ -1,1 +1,2 @@
-gcc -o "libev_server_nonblock" "libev_server_nonblock.c" -lpthread -lev
+gcc -o "libev_server_nonblock" "libev_server_nonblock.c" -lpthread -lev -I/usr/local/include -L/usr/local/lib
+
diff -r 0b9505dab680 test-libev-nonblock/libev_server_nonblock.c
--- a/test-libev-nonblock/libev_server_nonblock.c Thu Oct 25 15:09:37 2012 +0400
+++ b/test-libev-nonblock/libev_server_nonblock.c Thu Oct 25 12:37:25 2012 +0100
@@ -1,4 +1,4 @@
- #include
+ #include
#include
#include
#include
@@ -7,6 +7,9 @@
#include
#include
#include
+ #include //[+]PPA
+ #include
+
Упс.
ОтветитьУдалитьСожрал блог инклуды.
#include netinet/in.h
#include sys/socket.h
А что это за магическое число 10 передается вторым параметром в ev_loop? Поделитесь сакральным знанием, пожалуйста
ОтветитьУдалитьЭто ад, как можно такое вообще выкладывать, не понимаю.
ОтветитьУдалитьне правильно как-то все...
ОтветитьУдалитьпотоки сами по себе шерстят дескрипторы
Я тупой. Нет, правда. 2016-ый год уже скоро.
ОтветитьУдалитьА что значит (void*) 5, и (void*) 3 ? Я правильно понимаю что вы кастуете число 5 как адрес ? как 0x5. А что там по этому адресу ?
pthread_create( &thread_sender, NULL, sender, (void *)5);
pthread_create( &thread_sender, NULL, sender, (void *)3);
Видимо, да. И, судя по всему, потом забираем как "(long)_arg".
Удалить