четвер, 30 жовтня 2008 р.

Горе плагламистам, которые думают что волшебный tcp сделает за них всё

Оказывается до сих пор в заповедниках живут программисты, которые уверенны что tcp гарантирует им доставку. Хотя на самом деле tcp гарантирует немного другое: _если_ данные будут доставлены, то они будут доставлены без искажений и в том же порядке.
Возник вопрос из описания вот этого бага в ejabberd: http://www.jabber.ru/bugzilla/show_bug.cgi?id=275
В процессе обсуждения выяснилось, что некоторые уверенны что если send(2) на блокирующем сокете не вернул ошибку, значит данные гарантировано доставлены на принимающую сторону. Для проверки нарисовал вот такие две простеньких программы:
send.c:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char** argv)
{
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(2020);
addr.sin_addr.s_addr=inet_addr("10.1.1.82");
int sock;
sock = socket(PF_INET, SOCK_STREAM, 0);
if( sock <= 0 )
{
fprintf(stderr, "socket(): %s\n", strerror(errno));
return 1;
}
if( connect(sock,(struct sockaddr *)&addr,sizeof(addr)) )
{
fprintf(stderr, "connect(): %s\n", strerror(errno));
return 1;
}
unsigned char i;
for( i=0 ; i < 255 ; i++ )
{
time_t tm = time(0);
ssize_t count = send(sock,&i,sizeof(i), 0);
printf("%d (%d): %d\n", i, tm, count);
if( count <= 0 )
{
fprintf(stderr, "send(): %s\n", strerror(errno) );
return 1;
}
sleep(1);
}
shutdown(sock,0);
close(sock);
return 0;
}


recv.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

int main(int argc, char** argv)
{
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(2020);
addr.sin_addr.s_addr=inet_addr("10.1.1.82");
int sock, c_sock;
sock = socket(PF_INET, SOCK_STREAM, 0);
if( sock <= 0 )
{
fprintf(stderr, "socket(): %s\n", strerror(errno));
return 1;
}
if( bind(sock,(struct sockaddr *)&addr,sizeof(addr)) )
{
fprintf(stderr, "bind(): %s\n", strerror(errno));
return 1;
}
if( listen(sock,5) )
{
fprintf(stderr, "listend(): %s\n", strerror(errno));
return 1;
}
for(;;)
{
c_sock=accept(sock,NULL,NULL);
if( c_sock <= 0)
{
fprintf(stderr, "accept(): %s\n", strerror(errno));
}
unsigned char i;
for(;;)
{
time_t tm = time(0);
ssize_t count = recv(c_sock,&i,sizeof(i), 0);
printf("%d (%d): %d\n", i, tm, count);
if( count <= 0 )
{
fprintf(stderr, "recv(): %s\n", strerror(errno) );
return 1;
}
}
shutdown(c_sock,0);
close(c_sock);
}
return 0;
}

На одном компьютере запущен recv, потом на втором запущен send , потом через некоторое время работы на первом компьютере сделано
ipfw add deny tcp from any to any 2020

В результате recv вывел
0 (1225361963): 1
1 (1225361963): 1
2 (1225361964): 1
..
59 (1225362022): 1
и остановился в ожидании новых данных, а send вывел
0 (1225361963): 1
1 (1225361964): 1
2 (1225361965): 1
...
141 (1225362105): 1
и вывалился (очевидно получив по голове SIGPIPE)
Вот так.

P.S. для тех, кто думает что если мелкими блоками фиксированного размера слать, то и читать можно так же: http://dbg.livejournal.com/76148.html

Немає коментарів: