В свое время я довольно сильно увлекался всевозможной низкоуровщиной — ассемблерами (о чем как бы намекает доменное имя блога), сишечкой и так далее. Даже написал пару несложных драйверов для Windows. Но потом я открыл для себя Perl и понеслось.
Оказалось, что программы можно писать без ручного управления памятью, постоянной борьбы с переполнениями буферов и тп. Позже выяснилось, что выводить типы в уме и иметь побочные эффекты тоже не обязательно, и меня потянуло в сторону функциональщины . К Си, а также C++, я не возвращался (за исключением единичных случаев ) в течение долгого времени.
А недавно я выбрал для легкого чтения перед сном книгу «Компьютерные вирусы и антивирусы, взгляд программиста» . И такая ностальгия на меня нахлынула, что я решил сесть и написать небольшую программку на Си.
Программка банальнейшая — TCP сервер, спрашивающий имя пользователя и говорящий «привет, %пользователь»:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
void handle_error ( char * err_fun ) {
printf ( «%s() failed, errno = %d» , err_fun , errno ) ;
exit ( 1 ) ;
}
void child_proc ( int sock ) {
size_t name_len ;
char msg [ ] = «What is your name? n » ;
char name_buff [ 256 ] ;
char format_buff [ 256 ] ;
write ( sock , msg , sizeof ( msg ) ) ;
name_len = read ( sock , name_buff , sizeof ( name_buff ) ) ;
if ( — 1 == name_len ) {
handle_error ( «read» ) ;
}
while ( name_len > 0 &&
( ‘ n ‘ == name_buff [ name_len — 1 ] ||
‘ r ‘ == name_buff [ name_len — 1 ] ) ) {
name_len —;
}
name_buff [ name_len ] = 0 ;
sprintf ( format_buff , «Hello, %s! n » , name_buff ) ;
write ( sock , format_buff , strlen ( format_buff ) ) ;
close ( sock ) ;
exit ( 0 ) ;
}
int main ( ) {
struct sockaddr_in addr ;
int accepted_socket ;
int fork_pid ;
int listen_socket = socket ( AF_INET , SOCK_STREAM , 0 ) ;
if ( — 1 == listen_socket ) {
handle_error ( «socket» ) ;
}
addr. sin_family = AF_INET ;
addr. sin_port = htons ( 31337 ) ;
addr. sin_addr . s_addr = inet_addr ( «127.0.0.1» ) ;
if ( — 1 == bind ( listen_socket , ( struct sockaddr * ) & addr , sizeof ( addr ) ) ) {
handle_error ( «bind» ) ;
}
if ( — 1 == listen ( listen_socket , 100 ) ) {
handle_error ( «listen» ) ;
}
for ( ;; ) {
accepted_socket = accept ( listen_socket , 0 , 0 ) ;
if ( — 1 == accepted_socket ) {
handle_error ( «accept» ) ;
}
fork_pid = fork ( ) ;
if ( — 1 == fork_pid ) {
handle_error ( «fork» ) ;
} else if ( 0 == fork_pid ) {
close ( listen_socket ) ;
child_proc ( accepted_socket ) ;
} else {
close ( accepted_socket ) ;
}
}
exit ( 0 ) ;
}
Как выяснилось, что-то еще помнится. Писал я все это хозяйство минут тридцать, попутно заполняя пробелы в знаниях с помощью man-страниц и томика Стивенса , который в свое время я прочитал на одном дыхании и до сих пор бережно храню на почетной верхней книжной полке. Эх, наверное, все-таки, прикольно быть сишником…
Вы, конечно же, без труда найдете в этом коде три грубейшие ошибки. Я подскажу первую. Тут есть переполнение буфера. Пользователь может ввести имя длиной 256 байт, которые в сочетании с еще с девятью байтами форматной строки "Hello, %s! n "
переполнят буфер format_buff. Кстати, я обнаружил, что современные версии GCC вставляют рантайм проверки на переполнение буфера. В свое время еще говорили про хардварные защиты, что дескать все новые процессоры будут как-то автоматически детектить выполнение кода в стеке или что-то в этом роде. Кто следит за темой, переполнение буфера еще существует вообще?
Ну и, конечно же, я с нетерпением жду от вас описание еще как минимум двух ошибок, которые мне было лень исправлять в этом коде. В конце поста появятся ссылки на сайты и микроблоги тех, кто первый их опишет в комментариях.
Дополнение: Быстрее всех ошибки нашли smirn0v и rulexec . Молодцы! Описание ошибок вы найдете в заметке Типичные ошибки в сетевых приложениях на C/C++ . Также вас может заинтересовать пост Асинхронная работа с сокетами на C/C++ с libevent .