#c #linux
#c #linux
Вопрос:
У меня около 150 различных типов пакетов с идентификаторами 0 — 149. Каждый пакет имеет соответствующую функцию синтаксического анализа.
Всякий раз, когда приходит пакет, я должен вызвать соответствующую ему функцию синтаксического анализа.
Насколько я знаю, есть 3 хороших способа сделать это
- проверяйте коды операций и функции вызова внутри 150 условий if-else
- 150 случаев переключения
- массив указателей на функции с идентификатором пакета в качестве индекса
У меня нет никаких ограничений по памяти, а сервер также очень быстрый.Так что потеря нескольких циклов процессора не является большой проблемой.
Есть ли лучший способ сделать это? Если нет, какой из вышеперечисленных методов является лучшим?
ПРИМЕЧАНИЕ: я использую C, а не C
Комментарии:
1. «У меня нет никаких ограничений в памяти, и сервер также чрезвычайно быстр». переводится как «Кого это волнует? Попробуйте что-нибудь и посмотрите, как это работает «.
switch
Вероятно, это может сделать A, если не какое-то регулярное выражение.2. Честно говоря, похоже, что настоящая проблема здесь не в производительности, а в управлении сложностью. Если вы сможете придумать модель внедрения зависимостей для этого, это значительно упростит добавление и удаление обращений, чем перебор с
if
операторами иswitch
обращениями. Лично я бы предпочел иметь какой-нибудь легкий инструмент, который автоматически генерирует некоторый код на основе шаблона, чтобы при добавлении новых обращений код перестраивался соответствующим образом. Здесь может помочь немного Python, Perl или Ruby, или, в крайнем случае, инструмент поддержки, скомпилированный на C.3. Вариант 3. хорошо работает — простой, эффективный, легко расширяемый
4. Я бы поступил так, как сказал @tadman. В прошлом я делал это с помощью awk. Быстро и просто
Ответ №1:
Я опишу способ, которым я делал это в прошлом, для анализа сообщений между Unix и мэйнфреймом. Тогда это казалось простым и легким в управлении. Использовался годами. Здесь я просто копирую части этого кода. Надеюсь, это может быть полезно
короче говоря
На самом деле это всего лишь 8-строчный awk
скрипт и механика вокруг него. Эта статья — просто описание системы, которая хорошо работала для меня и может быть легко адаптирована: awk
генерирует очень простой заголовочный файл, который связывает функции и идентификаторы, и он интегрирован в процесс сборки, поэтому, если мы что-то изменим или добавим новую функцию, он обновляется сам по себе. Почему awk
? ну, я верю в это A
, W
и K
люди настоящие, и это очень хорошо работает с шаблонами. Но я написал точно такой же файл .h для создания справочных таблиц на C или C или java или Python, когда awk
это не вариант
как это работает
Я использовал таблицу указателей на функции и простую схему аннотаций в исходном коде. Для этого примера функции такие, как function_3()
показано ниже
// function_3 @packet-id 149
// function_3 @packet-id 0
int function_3(Packet* p) // @packet-id 3
{
printf("Hi from function_3(), id is %dn", (int) p->id);
return 0;
};
- Аннотация находится
@packet-id
в той же строке, что и имя функции, какfunction_3()
указано выше - Когда одной и той же функцией обрабатывается более одного идентификатора, я использовал комментарий типа
// function_3 @packet-id 149
часто непосредственно перед самой функцией. В данном случае function_3()
будут вызываться пакеты с id
3, 149 и 0
вызов функции синтаксического анализа на основе идентификатора пакета
Я использовал вызов по идентификатору, например
int callById(const Id, const VFT*);
где Id
— тип сообщения и VFT
справочная таблица, где позиция N
— это адрес функции, которая анализирует Id N
.
On startup a function
VFT* build_VFT(const unsigned);
builds the array based on the annotations in the source code. I will post a small example below
structs for the example
typedef uint8_t Id;
typedef struct
{
Id id;
void* data;
} Packet;
typedef int (*Packet_care)(Packet*);
typedef struct
{
unsigned nIds;
Packet_care* fn;
} VFT;
int function_1(Packet*);
int function_2(Packet*);
int function_3(Packet*);
VFT* build_VFT(const unsigned);
VFT* delete_VFT(VFT*);
int callById(const Id, const VFT*);
Packet_care
является указателем на функцию, которая получает указатель на a Packet
и возвращает an int
. Мой случай был немного сложнее, но приведенный здесь код — всего лишь тестовый пример, поэтому я оставил здесь только минимум: Packet
вот только Id
и некоторые данные. VFT
имеет только счетчик и таблицу указателей
Анализ аннотаций
Здесь речь идет о C и Linux, поэтому awk не совсем не по теме…
BEGIN { found = 0 }
/@packet-id/{
fName = $2
if ( fName ~ /(/ )
{
fName = gensub( /(. )((. )/, "\1", 1, $2)
}
ref = 0
for( i = 3; i < NF; i )
{
if ($i ~ "@packet-id" )
{
ref = i 1
i = NF 1
}
}
print ARRAY, "[", $ref, "] = ", fName, ";"
found = found 1
}
END { exit found }
Это наверняка можно было бы написать на C или на любом другом языке, но awk
было написано для тех же людей, которые одновременно писали C, и использовалось только для такого рода вещей. И это так просто…
- программа просто ищет строки с выбранной аннотацией —
@packet-id
здесь — и принимает следующую строку в качествеId
номера. ARRAY является аргументом выполнения и дает имя массива вVFT
источнике. Вывод — это просто таблицаF[ID] = N
, в которойF
указано имя массива,ID
идентификатор пакета иN
имя функции. - скрипт создает файл, который включается в компиляцию программы. Это однострочная команда. Но я покажу здесь сценарий оболочки из 1 строки, который легче читать:
./build_ref.sh test.h from vft.c using "vft->fn"
И он делает именно это: строит "ref.h"
с использованием аннотаций "vft.c"
, предполагая, что VFT
объявлено как vft
, а массив указателей есть fn[]
.
Единственная команда внутри скрипта
awk -v ARRAY=${5} -f t.awk.sh ${3} > ${1}
просто чтобы свести концы с концами.
тестовый запуск
Processing packets...
Will call function for packet id 0
Hi from function_3(), id is 0
function returned 0 for id 0
Will call function for packet id 1
Hi from function_1(), id is 1
function returned 0 for id 1
Will call function for packet id 2
Hi from function_2(), id is 2
function returned 0 for id 2
Will call function for packet id 3
Hi from function_3(), id is 3
function returned 0 for id 3
function returned -1 for id 4
function returned -1 for id 5
Will call function for packet id 149
Hi from function_3(), id is 149
Will call function for packet id 22
Hi from function_2(), id is 22
Will call function for packet id 12
Hi from function_2(), id is 12
вот main.c
#include "vft.h"
int main(void)
{
VFT* vft = build_VFT(150);
printf("nProcessing packets...n");
for (int i = 0; i <= 5; i = 1)
printf("function returned %d for id %dn",
callById( (Id) i, vft), i);
callById(149,vft);
callById(22,vft);
callById(12,vft);
vft = delete_VFT(vft);
return 0;
};
Создается таблица, и на основе идентификатора вызываются функции синтаксического анализатора. Таблица будет восстановлена, и программа завершится
Конструктор таблиц
VFT* build_VFT(const unsigned size)
{
VFT* vft = (VFT*) malloc(sizeof(VFT));
vft->nIds = size;
vft->fn = (Packet_care*) malloc ( size*sizeof(Packet_care*) );
for( int i=0; i<size; i = 1) *(vft->fn i) = NULL;
#include "ref.h"
return vft;
};
Выделяются struct
необходимые size
указатели, указатели определяются на основе аннотаций в исходном коде. Включенный файл является результатом работы скрипта. Простой и понятный
Исходные файлы
Есть несколько файлов:
- main.c находится выше
- vft.h — это заголовок
- vfc.c имеет
int function_1(Packet*);
int function_2(Packet*);
int function_3(Packet*);
VFT* build_VFT(const unsigned);
VFT* delete_VFT(VFT*);
int callById(const Id, const VFT*);
as expected. Delete_VFT() is trivial. callById() is in fact a one-liner, just a call. but in the example is
int callById(const Id id, const VFT* V)
{
static Packet pck;
pck.id = id;
if( (unsigned) id >= V->nIds) return -1;
if( V->fn[id] == NULL ) return -1;
printf("nWill call function for packet id %dn", (int) id );
return (*V->fn[(unsigned)id])(amp;pck);
};
Поскольку этот код не работает, пакет создается онлайн только с Id
помощью . И вызывается соответствующая функция синтаксического анализа.
Это не было тщательно протестировано. Я просто вырезаю и вставляю из мест здесь. Скомпилирован только под gcc 9.3 с использованием этого makefile
all: main
clear:
rm -f main *.o
$(shell ./build_ref.sh ref.h from vft.c using "vft->fn")
main: main.o vft.o
cc -o main main.o vft.o
main.o: main.c
cc -c main.c
vft.o: vft.c vft.h
cc -c vft.c
vft.c: vft.h
Таблица для этого примера
В конце концов, это всего лишь таблица…
vft->fn [ 1 ] = function_1 ;
vft->fn [ 12 ] = function_2 ;
vft->fn [ 22 ] = function_2 ;
vft->fn [ 2 ] = function_2 ;
vft->fn [ 149 ] = function_3 ;
vft->fn [ 0 ] = function_3 ;
vft->fn [ 3 ] = function_3 ;
все файлы
Ниже приведен один файл со всем текстом. Если вы сочтете полезным, я могу опубликовать ссылку на github
All files:
//////////////////////////////////////////////////////////////1: vft.h
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
typedef uint8_t Id;
typedef struct
{
Id id;
void* data;
} Packet;
typedef int (*Packet_care)(Packet*);
typedef struct
{
unsigned nIds;
Packet_care* fn;
} VFT;
int function_1(Packet*);
int function_2(Packet*);
int function_3(Packet*);
VFT* build_VFT(const unsigned);
VFT* delete_VFT(VFT*);
int callById(const Id, const VFT*);
//////////////////////////////////////////////////////////////2: vft.c
#include "vft.h"
int function_1(Packet* p) // @packet-id 1
{
printf("Hi from function_1(), id is %dn", (int) p->id);
return 0;
};
// function_2 @packet-id 12
// function_2 @packet-id 22
int function_2(Packet* p) // @packet-id 2
{
printf("Hi from function_2(), id is %dn", (int) p->id);
return 0;
};
// function_3 @packet-id 149
// function_3 @packet-id 0
int function_3(Packet* p) // @packet-id 3
{
printf("Hi from function_3(), id is %dn", (int) p->id);
return 0;
};
VFT* build_VFT(const unsigned size)
{
VFT* vft = (VFT*) malloc(sizeof(VFT));
vft->nIds = size;
vft->fn = (Packet_care*) malloc ( size*sizeof(Packet_care*) );
for( int i=0; i<size; i = 1) *(vft->fn i) = NULL;
#include "ref.h"
return vft;
};
VFT* delete_VFT(VFT* vft)
{
if ( vft == NULL ) return NULL;
if ( vft->nIds == 0) return NULL;
free( vft->fn );
free( vft );
return NULL;
};
int callById(const Id id, const VFT* V)
{
static Packet pck;
pck.id = id;
if( (unsigned) id >= V->nIds) return -1;
if( V->fn[id] == NULL ) return -1;
printf("nWill call function for packet id %dn", (int) id );
return (*V->fn[(unsigned)id])(amp;pck);
};
//////////////////////////////////////////////////////////////3: main.c
#include "vft.h"
int main(void)
{
VFT* vft = build_VFT(150);
printf("nProcessing packets...n");
for (int i = 0; i <= 5; i = 1)
printf("function returned %d for id %dn",
callById( (Id) i, vft), i);
callById(149,vft);
callById(22,vft);
callById(12,vft);
vft = delete_VFT(vft);
return 0;
};
//////////////////////////////////////////////////////////////4: t.awk.sh
BEGIN { found = 0 }
/@packet-id/{
fName = $2
if ( fName ~ /(/ )
{
fName = gensub( /(. )((. )/, "\1", 1, $2)
}
ref = 0
for( i = 3; i < NF; i )
{
if ($i ~ "@packet-id" )
{
ref = i 1
i = NF 1
}
}
print ARRAY, "[", $ref, "] = ", fName, ";"
found = found 1
}
END { exit found }
//////////////////////////////////////////////////////////////5: build_ref.sh
awk -v ARRAY=${5} -f t.awk.sh ${3} > ${1}
//////////////////////////////////////////////////////////////6: makefile
all: main
clear:
rm -f main *.o
$(shell ./build_ref.sh ref.h from vft.c using "vft->fn")
main: main.o vft.o
cc -o main main.o vft.o
main.o: main.c
cc -c main.c
vft.o: vft.c vft.h
cc -c vft.c
vft.c: vft.h
//////////////////////////////////////////////////////////////7: ref.h for the example
vft->fn [ 1 ] = function_1 ;
vft->fn [ 12 ] = function_2 ;
vft->fn [ 22 ] = function_2 ;
vft->fn [ 2 ] = function_2 ;
vft->fn [ 149 ] = function_3 ;
vft->fn [ 0 ] = function_3 ;
vft->fn [ 3 ] = function_3 ;