Instant Messaging изнутри
Косякин Антон
Xakep, номер #060, стр. 060-090-3
В нашем случае основной проблемой являются сокеты. В каком-нибудь Borland C++ Builder’е или Visual C++ все просто: воспользовался стандартным компонентом (классом), переопределил функцию OnRead и больше особо не заморачиваешься. Но мы-то ребята непростые и поэтому будем использовать BSD-style sockets (Microsoft, кстати, открыто признается, что их WinSock был… мм… частично скопирован с BSD :)). При таком раскладе возникает одна проблема - при вызове функции recvfrom() (для TCP - recv()) работа программы блокируется, пока на сокет не придут данные. А нам этого не нужно. Поэтому ищем выход. Тут есть два способа решения проблемы: пометить сокет как non-blocking (не блокирующий) или использовать функцию select(). В первом варианте придется дополнительно написать функцию unblock_socket(), реализация которой для win32 и *nix будет различаться. В качестве домашнего задания предлагаю тебе сходить на msdn.microsoft.com и заглянуть в man’ы :). А вот со вторым вариантом все намного проще - они идентичны для обеих ОС. Вот это решение:
struct timeval time_val;
fd_set read_fds;
while (true) {
time_val.tv_sec = 1;
time_val.tv_usec = 0;
FD_ZERO(&read_fds);
FD_SET(our_socket, &read_fds);
select(our_socket+1, &read_fds, NULL, NULL, &time_val);
if (FD_ISSET(our_socket, &read_fds)) {
do_smth();
};
};
В первых двух строках после объявления цикла while мы задаем таймаут для функции select(), т.е. время, на которое она приостановит выполнение программы. Далее мы работаем с переменной read_fs - засовываем туда идентификатор нашего сокета (our_socket) и вызываем select() (заметь, что первый параметр - «our_socket+1»; это важно!). Если на наш сокет пришли данные, то макрос FD_ISSET() выдаст true, а дальше выполнится функция do_smth(). Все просто.
У этого варианта есть еще одно преимущество - в FD_SET можно также передать дескриптор стандартного ввода, чтобы одновременно обрабатывать нажатые клавиши. Поясню, для чего это нужно: когда-нибудь тебе придется выключить сервер. И совсем не исключено, что он будет держать в памяти важные данные. А их, конечно же, нельзя терять. Поэтому, обработав определенную комбинацию клавиш, означающую команду завершения работы, мы сначала освободим память и скинем нужную информацию на диск, а уже потом завершим работу сервера. Можем заодно и клиентов отключить 8).
О юзерах
Как ты понимаешь, главная часть нашей системы – это юзеры. Из-за них и возникает ряд проблем :). Первый трабл. Допустим, кто-то активно общается с народом, а потом бац - и отключается от интернета. И как тут узнать, в онлайне он или нет? В принципе, легко! Нужно просто заставить наших юзеров, например, каждые две минуты отсылать серверу сообщение, типа «вот он я, я еще тут!». И если время истекает, то насильно выкидываем юзера. Звучит заманчиво и довольно просто. Но вот с реализацией (во всяком случае, у меня) возникают небольшие проблемы. Есть два варианта: хранить в памяти время последнего пришедшего сообщения от каждого пользователя и каждые несколько секунд пробегаться по этому списку на предмет трупиков. А теперь представь, что у тебя в системе в онлайне находится тысяча (!) пользователей. И каждые несколько секунд ты пробегаешься по списку в тысячу элементов и сверяешь «последнее время» каждого с текущим. Ну как? А помимо этого, весь «зверинец» ведь общается друг с другом! Ужас.