flush на TCP-сокет

Landstreicher

У меня есть открытый TCP-сокет.
1) Допустим я записал в него некоторые данные (небольшие по размеру) с помощью write(2). Я хочу, чтобы весь текущий накопленный буфер немедленно ушел по сети получателю. Или хотя бы какая-то его порция послалась по сети. Что мне надо сделать?
2) Могу ли я сделать какой-нибудь setsockopt, чтобы подобное поведение делалось на сокете автоматически при всех посылках.
Знаю, что это неоптимально в плане пропускной способности, но так надо в силу специфики задачи.
Все работает под Linux 2.6.

Marinavo_0507

типа man tcp
TCP_NODELAY и TCP_CORK

Werdna

Если я тебя правильно понял, ты имеешь в виду опции SO_RCVLOWAT и SO_SNDLOWAT.
Под Линуксом всегда стоит единичка (1 байт т. е. ты можешь быть уверен что данные пойдут немедленно на интерфейс.
ЗЫ: это моё личное мнение после чтения man 7 socket. я могу пиздеть, но вроде тут всё очевидно.
ЗЗЫ: может тебе хочется получать информацию когда пакет точно ушел? думаю придется писать модуль ядра

Landstreicher

TCP_NODELAY стоит, но не помогает (?).
Что говорит strace:

1156451686.802517 accept(8, 0, NULL) = 10
1156451686.802842 fcntl64(10, F_SETFL, O_RDONLY|O_NONBLOCK) = 0
1156451686.802916 setsockopt(10, SOL_TCP, TCP_NODELAY, [1], 4) = 0
1156451686.802981 getsockopt(10, SOL_SOCKET, SO_SNDBUF, [16384], [4]) = 0
1156451686.803044 setsockopt(10, SOL_SOCKET, SO_SNDBUF, [65560], 4) = 0
1156451686.803110 getsockopt(10, SOL_SOCKET, SO_RCVBUF, [87380], [4]) = 0
....
1156451686.811931 writev(10, [{"\r\0\0"..., 24}, {"t\354\261"..., 13}], 2) = 37
1156451686.813345 writev(10, [{"\25\0\0"..., 24}, {"1\213T"..., 21}], 2) = 45
1156451686.814442 writev(10, [{"\25\0\0"..., 24}, {"1\213T"..., 21}], 2) = 45
1156451686.815047 writev(10, [{"\31\0\0"..., 24}, {"\335\304"..., 25}], 2) = 49
1156451686.815536 writev(10, [{"e\0\0\0"..., 24}, {"N\256\327"..., 101}], 2) = 125
1156451686.815906 writev(10, [{"\25\0\0"..., 24}, {"1\213T"..., 21}], 2) = 45
1156451686.816346 writev(10, [{"e\0\0\0"..., 24}, {"N\256\327"..., 101}], 2) = 125
1156451686.816742 writev(10, [{"\25\0\0"..., 24}, {"1\213T"..., 21}], 2) = 45
1156451687.318626 writev(10, [{"\25\0\0"..., 24}, {"1\213T"..., 21}], 2) = 45

А вот что показывает tcpdump:

00:34:46.812002 IP X > Y: P 163:200(37) ack 297 win 429 <nop,nop,timestamp 786145347 1230635927>
00:34:46.813431 IP X > Y: P 200:245(45) ack 342 win 429 <nop,nop,timestamp 786145347 1230635929>
00:34:46.814534 IP X > Y: P 245:290(45) ack 342 win 429 <nop,nop,timestamp 786145347 1230635929>
00:34:46.815155 IP X > Y: P 290:339(49) ack 342 win 429 <nop,nop,timestamp 786145348 1230635929>
00:34:46.853104 IP X > Y: P 339:679(340) ack 342 win 429 <nop,nop,timestamp 786145357 1230635970>
00:34:46.853879 IP X > Y: P 679:724(45) ack 387 win 429 <nop,nop,timestamp 786145357 1230635970>
00:34:46.892052 IP X > Y: . ack 432 win 429 <nop,nop,timestamp 786145367 1230635971>

Сначала посылается нормально (37, 45, 45, 49). Затем ожидаем ~38 мсек (? и потом посылаем сразу пачкой: 340 = 125 + 45 + 125 + 45. Почему так?
Сдается мне, какой-то у меня ламеризм. Надо читать "TCP/IP для самых маленьких". Сильно не бейте, плз.

Landstreicher

Более точно, это место выглядит так:

00:34:46.812002 IP X > Y: P 163:200(37) ack 297 win 429 <nop,nop,timestamp 786145347 1230635927>
00:34:46.812207 IP Y > X: P 297:342(45) ack 200 win 1460 <nop,nop,timestamp 1230635929 786145346>
00:34:46.813431 IP X > Y: P 200:245(45) ack 342 win 429 <nop,nop,timestamp 786145347 1230635929>
00:34:46.814534 IP X > Y: P 245:290(45) ack 342 win 429 <nop,nop,timestamp 786145347 1230635929>
00:34:46.815155 IP X > Y: P 290:339(49) ack 342 win 429 <nop,nop,timestamp 786145348 1230635929>
00:34:46.853045 IP Y > X: . ack 339 win 1460 <nop,nop,timestamp 1230635970 786145347>
00:34:46.853104 IP X > Y: P 339:679(340) ack 342 win 429 <nop,nop,timestamp 786145357 1230635970>
00:34:46.853226 IP Y > X: . ack 679 win 1728 <nop,nop,timestamp 1230635970 786145357>

Отправка пакета в 340 байт происходит сразу после получения "Y > X: . ack 339 win 1460". То есть оно ждет подтверждения? Как указать объем или количество пакетов, которые можно отсылать без подтверждения? Вроде SNDBUF и так 64 кб...

sergey_m

Всё правильно, TCP_NODELAY работает. TCP стек перестал слать, потому что было послано несколько пакетов и не поступило подтверждения с той стороны. Если бы подтверждения поступали без задержки, то все пакеты совпадали бы. Возможно, когда сессия разгонится, у неё устаканится окно и rtt, то этот эффект пропадёт. Если же сеть работает неравномерно, то не пропадёт.

sergey_m

afaik, TCP_ПРОБКА делает обратное и вообще это фантазия линукса, больше нигде этого нет.

Landstreicher

> TCP стек перестал слать, потому что было послано несколько пакетов и не поступило подтверждения с той стороны.
Я считал, что пакеты посылаются пока не заполнится окно. В данном случае окно 1460 и послано значительно меньше, порядка пары сотен байт. Почему тогда не посылаются данные? Есть еще какой-то фактор, кроме размера окна (в байтах)? Как его можно регулировать?

sergey_m

Чёрт его знает... TCP стек линукса 2.6 может делать много того, про что в книжках не пишут. Возможно, для получения однозначного ответа на вопрос пришлось бы включать дебаг и разбираться.
Это соединение давно образовалось или это самое его начало?

Landstreicher

> Это соединение давно образовалось или это самое его начало?
Самое начало. В ... всего порядка 10 пакетов (в сумме в обе стороны).

Marinavo_0507

Надо спрашивать у отцов из
Если им дамп показать, они обычно быстро отвечают.
Во время ожидания ответа попытаться почитать исходник, может там есть комментарии.

Marinavo_0507

TCP_CORK можно включить, а потом выключить - получится flush

sergey_m

> Самое начало. В ... всего порядка 10 пакетов (в сумме в обе стороны).
Тогда оно может быть еще в состоянии slow start?

Landstreicher

up! До сих пор очень актуально.
Написали программу, которая это воспроизводит. Посылает в цикле несколько маленьких пакетов, затем спит 0.1 с. При корректной работе программы время должно быть порядка 10.0 с. На практике - порядка 14.0 с. С очень большой точностью воспроизводится задержка порядка 40 мс. Самое интересное, что даже не обязательно посылать по Ethernet, поведение такое же при работе через loopback. Если посылать ответы чаще (поменять константу DELIM с 10 на 1 или 2 то эффект исчезает.
Запуск сервера:

$ ./a.out 1 5000
Server: begin send_all
Server: total time 14.216441

Запуск клиента:

$ ./a.out 2 5000 localhost
Client: connected to localhost:5000
Client: begin receive_all
Client: total time 14.223265

Я посмотрел список linux-netdev, но там обсуждают более сложные вопросы, связанные с разработкой драйверов в ядре. Уместно ли послать туда такую программу? Может быть есть какие-то другие списки рассылки?

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <time.h>

int TOTAL_SENDS = 1000;
int DELIM = 10;

int sock = -1;

int init_server(int port)
{
struct sockaddr_in sin;
struct sockaddr_in new_sin;
int new_sock;
int val;
int sockaddrlen;

sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) return -1;

val = 1;
if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val < 0) return -1;

memset(&sin,0,sizeof(struct sockaddr_in;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);

if (-1 == bind(sockstruct sockaddr*)&sin,sizeof(sin return -2;
if (-1 == listen(sock,1 return -3;

sockaddrlen = sizeof(struct sockaddr_in);
new_sock = accept(sockstruct sockaddr*)&new_sinsocklen_t*)&sockaddrlen);
if (new_sock == -1) return -4;

sock = new_sock;
val = 1;
if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val != 0) return -5;
return 0;
}

int init_client(char* hostname, int port)
{
int val;
int res;
struct sockaddr_in sin;

sock = socket(PF_INET, SOCK_STREAM, 0);

memset(&sin, 0, sizeof(sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
memcpy(&sin.sin_addr, gethostbyname(hostname)->h_addr, 4);

val = 1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val;

res = connect(sock, (struct sockaddr*)&sin, sizeof(sin;
printf("Client: connected to %s:%d\n", hostname, port);
if (res == -1) return -1;
setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &val, sizeof(val;
return 0;
}

void send_all(unsigned long delay)
{
int i;
char buf[1024];
printf("Server: begin send_all\n");
for (i = 1; i < TOTAL_SENDS; ++i) {
write(sock, buf, 1);
if (i % DELIM == 0) read(sock, buf, 1);
if (i % 10 == 0) usleep(delay);
}
}

void receive_all(unsigned long delay)
{
int i;
char buf[1024];
printf("Client: begin receive_all\n");
for (i = 1; i < TOTAL_SENDS; ++i) {
read(sock, buf, 1);
if (i % DELIM == 0) write(sock, buf, 1);
if (i % 10 == 0) usleep(delay);
}
}

int main(int argc, char* argv[])
{
int port;
char* host;
int me;
struct timeval tv1, tv2;
double tt;

assert(argc > 2);
me = atoi(argv[1]);
switch (me) {
case 1:
port = atoi(argv[2]);
if (init_server(port {
printf("Server initialization failed!\n");
return 1;
}
gettimeofday(&tv1, 0);
send_all(100000);
gettimeofday(&tv2, 0);
tt = tv2.tv_sec - tv1.tv_sec + (tv2.tv_usec - tv1.tv_usec) * 0.000001;
printf("Server: total time %f\n", tt);
break;
case 2:
assert(argc == 4);
port = atoi(argv[2]);
host = argv[3];
if (init_client(host, port {
printf("Client initialization failed!\n");
return 1;
}
gettimeofday(&tv1, 0);
receive_all(100000);
gettimeofday(&tv2, 0);
tt = tv2.tv_sec - tv1.tv_sec + (tv2.tv_usec - tv1.tv_usec) * 0.000001;
printf("Client: total time %f\n", tt);

break;
default:
printf("Wrong parameter\n");
return 1;
}
shutdown(sock, SHUT_RDWR);
close(sock);
return 0;
}

Marinavo_0507

Я посмотрел список linux-netdev, но там обсуждают более сложные вопросы, связанные с разработкой драйверов в ядре. Уместно ли послать туда такую программу?
Типа твой вопрос простой что ли? Конечно, уместно.

sergey_m

Конечно уместно! Это же open source! Люди могут задавать вопросы даже ламерские, хотя твой таковым не является, а кому не лень будут на них отвечать.
Кстати тебе респект, ты написал портабельную программу, которая собралась на другой операционной системе с -Wall -pedantic без единого warning! Блин, впервые столкнулся с тем, что человек написал программу под линукс, и она у меня собралась без правок. Респект!
Кстати, вот результат работы на FreeBSD:

Client: connected to localhost:5000
Client: begin receive_all
Client: total time 9.997108

Похоже, ты действительно прав, стек ведёт себя не так, как надо (не так, как хотелось бы).

Spin

Все работает под Linux 2.6.
А у тебя какое 2.6? У меня на генту с 2.6.11.11 работает правильно.
 nix ~ $ ./a.out 2 5000 localhost
Client: connected to localhost:5000
Client: begin receive_all
Client: total time 10.376747
nix ~ $

Landstreicher

Проверял на 2.6.17.

Marinavo_0507

там отцы вроде разобрались с проблемой
надо отключить tcp_abc

sergey_m

А что такое tcp_abc?

Marinavo_0507

какая-то фича
почитай лучше сам тред, там отцы спорят про неё

Marinavo_0507

Алекс, я думаю, тебе для твоей фигни нужен протокол на базе UDP с собственными (простыми и понятными) механизмами потверждения и flow control.
Потому как тебе важны границы сообщений, но не важен их порядок, в то время как TCP даёт совершенно противоположные гарантии.
В этом поможет измерялка RTT и PL, так что и этот пункт будет закрыт

sergey_m

А в Линуксе же уже есть SCTP?

Marinavo_0507

SCTP есть, но я в него не верю.
Ещё есть DCCP, вроде занятная штука, но потребовать, чтобы везде, где надо, его включили в ядро, может оказаться затруднительно.

Landstreicher

> Алекс, я думаю, тебе для твоей фигни нужен протокол на базе UDP с собственными (простыми и понятными) механизмами потверждения и flow control.
Полностью согласен с тобой. Тут такая проблема --- TCP используется не в самой программе, а в библиотеке. Причем это библиотека используется множеством людьми. Переписывать заново всю библиотеку очень не хочется (она большая). Авторы библиотеки тоже наверное ничего менять не станут --- скажут, что мой случай нетипичный

Landstreicher

Да, с отключенным tcp_abc заметно лучше (задержки вместо 40 мс стали порядка 2,3 мс)

Marinavo_0507

Да, с отключенным tcp_abc заметно лучше (задержки вместо 40 мс стали порядка 2,3 мс)
Теперь всё понятно, нормальный slow start работает.
Видно, что с каждой итерацией cwnd растёт на один пакет (в линуксе оно считается в пакетах начиная с двух. Соответственно, через короткое время сессия разгоняется, и пакеты уходят без задержек.
В BSD, как сказано в том треде, cwnd считается в байтах, и потому можно сразу, без задержки, несколько тысяч пакетов, по одному байту в каждом, отправить.
Оставить комментарий
Имя или ник:
Комментарий: