Всем привет.
Пытаюсь освоить сетевое программирование на низком уровне(POSIX API). Интересна задача реализовать многопользовательский сервер использующий механизм epoll().
В программировании не новичек. В сетевом программировании есть пробелы :)
Вот что я набросал. Но не все понятно. Если не затруднит, прокоментируйте код, может что-то не правильно сделал, возможно есть более правильное решение.#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>#define ZEROPOD(var) memset(&var, 0, sizeof(var))
const int MYPORT = 1234;
const int qsize = 10;
const int epsize= 10;/** устанавливает неблокирующий режим для указанного сокета */
int setnonblocking(int sock) {
int opts = fcntl(sock,F_GETFL);
if (opts < 0) {
perror("fcntl(F_GETFL)");
return -1;
}
opts = (opts | O_NONBLOCK);
if (fcntl(sock,F_SETFL,opts) < 0) {
perror("fcntl(F_SETFL)");
return -1;
}return 0;
}/** печатает адрес в консоль */
void printf_address(const struct sockaddr_in* sa) {
char buff[INET_ADDRSTRLEN+1] = "\0";
const char* p = inet_ntop(AF_INET, &sa->sin_addr, buff, sizeof(buff));
if ( !p ) {
perror("printf_address()");
return;
}
in_port_t port = ntohs(sa->sin_port);
fprintf(stderr, "%s:%d\n", p, port);
}/** инициализирует серверный сокет */
int initserver(int type, const struct sockaddr* sa, socklen_t alen, int qlen) {
int sock_fd = 0;
int value = 1;if ( (sock_fd=socket(sa->sa_family, type, 0)) < 0 ) {
return -1;
}if ( setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value)) < 0 ) {
close(sock_fd);
return -1;
}if ( setsockopt(sock_fd, SOL_SOCKET, SO_KEEPALIVE, &value, sizeof(value)) < 0 ) {
close(sock_fd);
return -1;
}if ( setnonblocking(sock_fd) < 0 ) {
close(sock_fd);
return -1;
}if ( bind(sock_fd, sa, alen) < 0 ) {
close(sock_fd);
return -1;
}if ( type == SOCK_STREAM || type == SOCK_SEQPACKET ) {
if ( listen(sock_fd, qlen) < 0 ) {
close(sock_fd);
return -1;
}
}return sock_fd;
}/***************************************************************************/
int main() {
/** дескрипторы */
int count_fds = 0;
int base_socket_fd = 0;
int epoll_fd = 0;/** адреса */
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
socklen_t addrlen = sizeof(sockaddr_in);
ZEROPOD(my_addr);
ZEROPOD(their_addr);/** epoll */
struct epoll_event epoll_event;
struct epoll_event static_events[epsize];
struct epoll_event* events = &static_events[0];
ZEROPOD(epoll_event);
ZEROPOD(static_events);/** инициализирую адреса */
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(MYPORT);
my_addr.sin_addr.s_addr = INADDR_ANY;/** создаю слушающий сокет */
base_socket_fd=initserver(SOCK_STREAM, (struct sockaddr*)&my_addr, addrlen, qsize);
if ( base_socket_fd < 0) {
perror("initserver()");
return EXIT_FAILURE;
}/** создаю epoll с начальным размером очереди epsize */
epoll_fd = epoll_create(epsize);
if (epoll_fd == -1) {
perror("epoll_create()");
return EXIT_FAILURE;
}/** создается елемент события для слушающего сокета */
epoll_event.events = EPOLLIN|EPOLLRDHUP|EPOLLET;
epoll_event.data.fd = base_socket_fd;
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, base_socket_fd, &epoll_event) == -1) {
perror("epoll_ctl()");
return EXIT_FAILURE;
}while ( 1 ) {
/** ожидаю события на дескрипторе epoll */
count_fds = epoll_wait(epoll_fd, events, epsize, -1);
if (count_fds == -1) {
perror("epoll_wait()");
return EXIT_FAILURE;
}
/** цикл повторяется "count_fds" раз */
for ( int n = 0; n < count_fds; ++n) {
/* если (events[n].data.fd == base_socket_fd) истинно -
* значит запрос от клиента на подключение
*/
if ( events[n].data.fd == base_socket_fd ) {
addrlen = sizeof(struct sockaddr_in);
/** принимаем запрос на подключение */
int new_socket_fd = accept(base_socket_fd, (struct sockaddr*)&my_addr, &addrlen);
printf_address(&my_addr);
if (new_socket_fd == -1) {
perror("accept()");
return EXIT_FAILURE;
}
if ( setnonblocking(new_socket_fd) < 0 ) {
perror("setnonblocking()");
close(new_socket_fd);
continue;
}
/** указываем какие события на сокете мониторить */
epoll_event.events = EPOLLIN|EPOLLET;
epoll_event.data.fd = new_socket_fd;
/** добавляем в epoll */
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, new_socket_fd, &epoll_event) == -1) {
perror("epoll_ctl()");
return EXIT_FAILURE;
}
} else {
int32_t sock_fd = events[n].data.fd;
uint32_t event = events[n].events;
/* этот дескриптор уже есть в epoll. */
if ( event & EPOLLIN ) {
/* сокет готов для чтения */
printf("descriptor %d ready for read\n", sock_fd);
char buff[32] = "\0";
int rd = read(sock_fd, &buff, sizeof(buff));
fprintf(stderr, "%s\n", buff);
}
if ( event & EPOLLOUT ) {
/* сокет готов для записи */
printf("descriptor %d ready for write\n", sock_fd);
}
if ( event & EPOLLRDHUP ) {
/* как я понял из документации(http://linux.die.net/man/2/epoll_ctl)
* это событие закрытия удаленного сокета. так?
*/
printf("descriptor %d disconnect\n", sock_fd);
}
if ( event & EPOLLPRI ) {
/* срочные данные готовы для считывания. так? */
printf("descriptor %d ready for read urgent data\n", sock_fd);
}
if ( event & EPOLLERR ) {
/* какая-то ошибка произошла на сокете. так?
* какие ошибки могут происходить? и почему?
*/
printf("on descriptor %d error condition\n", sock_fd);
}
if ( event & EPOLLHUP ) {
/* какое-то событие произошло на сокете. так? */
printf("on descriptor %d ???\n", sock_fd);
}
if ( event & EPOLLET ) {
/* как я понял, эта константа используется для установки
* какого-то правила наблюдения за сокетом. и не понимаю,
* о чем это сыбытие может говорить(если оно происходит).
*/
printf("event & EPOLLET\n");
}
if ( event & EPOLLONESHOT ) {
/* по моему, так же как и в предыдущем блоке, это не событие.
* но суть константы понятна.
*/
printf("event & EPOLLONESHOT\n");
}
close(sock_fd);
}
}
}close(epoll_fd);
close(base_socket_fd);return 0;
}
> if ( event & EPOLLIN ) {
> /* сокет готов для чтения */
> printf("descriptor %d ready for read\n", sock_fd);
> char buff[32] = "\0";тут не надо инициализации буфера как "\0" - все равно перезапишется.
> int rd = read(sock_fd, &buff, sizeof(buff));
вызов read принимает вторым аргументом не указатель-на-указатель, а указатель на буфер - он же никуда его двигать не собирается: просто запихнуть в буфер что-то.
тобишь будет строка как:
int rd = read(sock_fd, buff, sizeof(buff));> fprintf(stderr, "%s\n", buff);
> }
> if ( event & EPOLLOUT ) {
> /* сокет готов для записи */
> printf("descriptor %d ready for write\n", sock_fd);вот это лишнее - контроль выходного потока нужно ставить если данных в ответе накопилось больше, чем на одну операцию write. Некоторые предпочитают дождаться EBUSY ошибки, прежде чем подписываться на информирование об этом сигнале.
> }
<кусь> - ибо неважно
> if ( event & EPOLLERR ) {
> /* какая-то ошибка произошла на сокете. так?
>
> * какие ошибки могут происходить? и
>почему?
> */наиболее суперская ошибка - "ошибочная ошибка - никаких ошибок нет", или "превышено число допустимых ошибок", можно поймать в винде - хто его знает какие еще хитроумные сплетения возможны. Но наиболее часто имеется в виду ошибка по потоку, например, если нечего больше мониторить - поток закрылся, разорвалось соединение (характено для SOCK_STREAM - TCP). Или какая-то часть кода не хочет мира во всем мире и злономерено закрыла исходный код дескриптор, посчитав, что номер 666 будет смотреться лучше, чем 13 (close, dup, dup2).
Это же относится к EPOLLHUP и прочих непонятностей - не хочется лезть в хэндбук.