LINUX.ORG.RU

Разработка TCP-сервера: схема обработки потока байт

 


0

1

Допустим, надо написать TCP-сервер, который будет слать на GET-запрос клиента ряд DATA-пакетов, после чего клиент (через неопределенное время) отключается.
Также, возможна отправка других команд от клиента к сервера, и от сервера к клиенту.

Ткните в examples на Си (gcc), или в tutorials по реализациям и схемам работы таких простеньких серверов.
Интересует то, как в таких программах красиво пишется обработка TCP-потока (выделение пакетов) на стороне сервера и на стороне клиента.

Пакеты типа:
struct {
uint8_t Type;
uint32_t DataSize;
uint8_t Data[0];
}

(то есть, KLV/TLV-схема записи)

★★★★★
Ответ на: комментарий от beastie

final state machine

по-русски, обычные конечные автоматы?

У меня получается такая схема:
1) хранить указатели на начало/конец принятых байт и номер состояния стейт-машины для каждого соединения в массиве соединений (25-50к штук);
2) запросы от каждого клиента (сокета) записывать в общую очередь, бегать по очереди и удалять при завершении выполнения;
3) состояние буферов отдачи хранить в элементе очереди, соответствующему запросу.

проблема может возникнуть лишь, если запросов от клиентов будет слишком много ... тогда не будем успевать отправлять данные обратно

pacify ★★★★★
() автор топика
Ответ на: комментарий от beastie

советую посмотреть на queue.h

Уже использую.
Кстати, они не тормозят? Реализация queue.h через malloc()?

pacify ★★★★★
() автор топика
Ответ на: комментарий от pacify

а чему там тормозить? это чистые макросы. что на вход подашь, то и будет.

по скорости они конечно все слегка отличаются друг от друга. (один или два линк-поинтера, смотри польный ман, линуксовский ман — это жалкий огзызок), но на фоне сетевых затыков всем этим можно пренебречь, в том числе и static vs. malloc. т.ч. не занимайся premature optimization.

вот тебе ещё пару примеров их использования.

beastie ★★★★★
()
Последнее исправление: beastie (всего исправлений: 1)
Ответ на: комментарий от beastie

а чему там тормозить? это чистые макросы

ок. думаю на 50к элементах даже поиск можно тупым FOREACH делать :)

pacify ★★★★★
() автор топика
Ответ на: комментарий от pacify
Port:   protobuf-c-0.15
Path:   /usr/ports/devel/protobuf-c
Info:   This package provides a code generator and runtime libraries to use Protocol Buffers from pure C (not C++)
slovazap ★★★★★
()
Последнее исправление: slovazap (всего исправлений: 1)

А клиенты какие? Браузеры? Если да, то примеров есть. Например, если хочется асинхронности, то libevent и примеры httpd к нему. Если хочется отдельных потоков на запрос(или соединение, если keep-alive) - http://tinyhttpd.sourceforge.net/, например. Ну или Стивенс, там тоже есть. Опять же, если это веб, вот такими «пакетами» обычно обмениваются в виде JSON

Deleted
()
Ответ на: комментарий от Deleted

Ну или Стивенс, там тоже есть.

Спасибо, почитаю. Я привык к бумажным книгам - Стивенса купил лет 8 назад, но почти не изучал.

pacify ★★★★★
() автор топика
Ответ на: комментарий от pacify

Вы какой-то потребительский софт разрабатываете? Или что-то узкоспециальное, с возможностью контроля инсталляций?

Если потребительский, для широких народных масс, то кроме HTTP-подобных (ну или для чего-то специфичного UDP с возможностью отступить к HTTP) вариантов почти и нет. Во-первых есть много уже готовых библиотек, веб-серверов, веб-клиентов и прочего барахла которые облегчают разработку и отладку. Во-вторых реально много пользователей сидят за прокси, и их будет удивлять неработоспособность программы

Deleted
()

я делаю так (выделение пакетов из потока байтов):

1) заводится буфер с размером равным размеру самого большого пакета

2) пишется функция, которая получает на вход указатель на начало пакета, количество полученных байтов и возвращает размер пакета. Если количество полученных байт недостаточно для определения размера, возвращается максимальный размер типа (MAX_INT или что-то в этом роде, в общем чтобы больше размера буфера)

3) делаем в цикле

ret = recv (socket, buf + pos, bufsize - pos, 0);

pos += ret;

while(pos > 0) //цикл потому что одним куском может прийти несколько пакетов
{
msgsize = getpacketsize(buf, pos);

if (msgsize > pos)
  break;

//здесь обрабатываем полученный пакет
...

pos -= msgsize;

//сдвигаем оставшиеся данные к началу буфера
memmove(buf, buf + msgsize, pos);//тупо, можно замутить кольцевой буфер
}

Harald ★★★★★
()
Ответ на: комментарий от Harald

//сдвигаем оставшиеся данные к началу буфера

ок, спасибо за поддержку. Видимо, zero-copy всё-таки не получится. Буду делать как и ты.

pacify ★★★★★
() автор топика
Ответ на: комментарий от Deleted

Или что-то узкоспециальное, с возможностью контроля инсталляций?

Узкоспециализированное.

Во-вторых реально много пользователей сидят за прокси

Эм ... сделать через HTTP - это не проблема, меня волнует главная
задача - это написание стейт-машины для моего протокола.

pacify ★★★★★
() автор топика
Ответ на: комментарий от pacify

я тут в процессе задумался, как избавиться от memmove(). Можно убирая обработанный пакет передвигать указатель buf, но возможна такая ситуация - приходит небольшой пакет, вслед за ним пакет максимального размера, и писать его некуда, упираемся в конец буфера. Тогда надо либо увеличивать на лету буфер, либо копировать хвост пакета в начало буфера, и потом склеивать пакет из двух кусков идущих в обратном порядке, что потребует копирования большего числа байт, чем в случае с memmove()

Harald ★★★★★
()
Последнее исправление: Harald (всего исправлений: 1)
Ответ на: комментарий от pacify

Прохождение HTTP через прокси - это, скорее, такой бонус. Пойнт был не в этом. Использование HTTP (точнее, готовых компонентов для него) сильно упрощает сетевое программирование. Не нужно заморачиваться всякой низкоуровневой и нудной ерундой типа «как узнать что клиент отвалился», «что делать когда клиент передал не весь пакет и завис», и т.п.

В более-менее современных веб-серверах или библиотеках для их написания все это (как и прочие c10k проблемы) давно решено. libevent, 30 строк кода на сишечке из примера - и мы уже получаем полное сообщение, его размер, отправителя, опционально что-нибудь еще в куке.

Ну и чтоб забыть про порядок байтов на разных архитектурах все это можно гонять в виде JSON.

Понятно, что у вас там могут быть какие-то свои соображения, но я бы делал прототип в виде веб-сервера, и только потом, если вдруг уже упрется во что-то, переписывал

Deleted
()
Ответ на: комментарий от Harald

А почему по пакетам не читать?

Читаем 5 байт, узнаем DataSize, читаем DataSize байт.

Есть пакет, можно обрабатывать и получать следующий.

nocomer
()
Ответ на: комментарий от pacify

// //memmove(buf, buf + msgsize, pos);//тупо, можно замутить кольцевой буфер

//сдвигаем оставшиеся данные к началу буфера

ок, спасибо за поддержку. Видимо, zero-copy всё-таки не получится. Буду делать как и ты.

Замути лучше кольцевой буфер.

pathfinder ★★★★
()
Последнее исправление: pathfinder (всего исправлений: 1)
Ответ на: комментарий от Deleted

Использование HTTP (точнее, готовых компонентов для него) сильно упрощает сетевое программирование.

HTTP, это лишний оверхед и накладываение ненужных архитектурных ограничений. Не всех это может порадовать.

pathfinder ★★★★
()
Ответ на: комментарий от Harald

заведи буфер размером 2 * PACKET_SIZE_MAX :)

shty ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.