RPC не может декодировать аргументы для транспорта TCP
Я работаю над многопоточным сервером RPC на основе примера с этой страницы: http://bderzhavets.blogspot.ca/2005/11/multithreaded-rpc-server-in-white-box.html
К сожалению, это не совсем работало из коробки, и после некоторой погони за ошибками я обнаружил, что серверу не удается декодировать аргументы (на основе кода возврата из squareproc_2
). Казалось, выполнение на стороне сервера прекращается после вызова squareproc_2_svc
в функции serv_request
, Увидеть case: SQUAREPROC
в приведенном ниже коде из square_svc.c
void *serv_request(void *data)
{
struct thr_data *ptr_data = (struct thr_data *)data;
{
square_in argument;
square_out result;
bool_t retval;
xdrproc_t _xdr_argument, _xdr_result;
bool_t (*local)(char *, void *, struct svc_req *);
struct svc_req *rqstp = ptr_data->rqstp;
register SVCXPRT *transp = ptr_data->transp;
switch (rqstp->rq_proc) {
case NULLPROC:
printf("NULLPROC called\n");
(void) svc_sendreply (transp, (xdrproc_t) xdr_void, (char *)NULL);
return;
case SQUAREPROC:
_xdr_argument = (xdrproc_t) xdr_square_in;
_xdr_result = (xdrproc_t) xdr_square_out;
printf("_xdr_result = %ld\n",_xdr_result);
local = (bool_t (*) (char *, void *, struct svc_req *))squareproc_2_svc;
break;
default:
printf("default case executed");
svcerr_noproc (transp);
return;
}
memset ((void *)&argument, 0, sizeof (argument));
if (!svc_getargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
printf("svc_getargs failed");
svcerr_decode (transp);
return;
}
retval = (bool_t) (*local)((char *)&argument, (void *)&result, rqstp);
printf("serv_request result: %d\n",retval);
if (retval > 0 && !svc_sendreply(transp, (xdrproc_t) _xdr_result, (char *)&result))
{
printf("something happened...\n");
svcerr_systemerr (transp);
}
if (!svc_freeargs (transp, (xdrproc_t) _xdr_argument, (caddr_t) &argument)) {
fprintf (stderr, "%s", "unable to free arguments");
exit (1);
}
if (!square_prog_2_freeresult (transp, _xdr_result, (caddr_t) &result))
fprintf (stderr, "%s", "unable to free results");
return;
}
}
Вот реализация squareproc_2_svc
из файла square_server.c:
bool_t squareproc_2_svc(square_in *inp,square_out *outp,struct svc_req *rqstp)
{
printf("Thread id = '%ld' started, arg = %ld\n",pthread_self(),inp->arg1);
sleep(5);
outp->res1=inp->arg1*inp->arg1;
printf("Thread id = '%ld' is done %ld \n",pthread_self(),outp->res1);
return(TRUE);
}
Вывод на стороне клиента:
yak@AcerPC:~/RPC/multithread_example$ ./ClientSQUARE localhost 2
squareproc_2 called
xdr_square_in result: 1
function call failed; code: 11
Вывод на стороне сервера:
yak@AcerPC:~/RPC/multithread_example$ sudo ./ServerSQUARE
creating threads
SQUAREPROC called
xdr_square_in result: 0
Как видите, xdr_square_in возвращает ЛОЖНЫЙ результат на стороне сервера. Вот квадрат.х
struct square_in {
long arg1;
};
struct square_out {
long res1;
};
program SQUARE_PROG {
version SQUARE_VERS {
square_out SQUAREPROC(square_in) = 1;
} = 2 ;
} = 0x31230000;
и square_xdr.c
/*
* Please do not edit this file.
* It was generated using rpcgen.
*/
#include "square.h"
bool_t
xdr_square_in (XDR *xdrs, square_in *objp)
{
register int32_t *buf;
int retval;
if (!xdr_long (xdrs, &objp->arg1)) retval = FALSE;
else retval = TRUE;
printf("xdr_square_in result: %d\n",retval);
return retval;
}
bool_t
xdr_square_out (XDR *xdrs, square_out *objp)
{
register int32_t *buf;
int retval;
if (!xdr_long (xdrs, &objp->res1)) retval = FALSE;
else retval = TRUE;
printf("xdr_square_out result: %d\n",retval);
return retval;
}
Я работаю в Ubuntu 14.04 LTS, создавая заглушки и код XDR с rpcgen -a -M
и компилировать с gcc
,
Эта ошибка возникает только при использовании TCP в качестве метода транспорта. Я могу получить результаты, используя UDP в качестве транспорта, но некоторые вызовы не выполняются, когда запросы от нескольких клиентов поступают одновременно. Я хотел бы иметь возможность поддерживать до 15 клиентов. Когда я попытался использовать UDP и 10 клиентов, 2 из 10 вызовов потерпели неудачу с другим кодом возврата из squareproc_2
,
1 ответ
У вас есть несколько вопросов.
Со страницы xen, когда он выполняет pthread_create в square_prog_2, он сначала вызывает pthread_attr_setdetachstate, но перед этим ему нужно выполнить pthread_attr_init. Кроме того, attr выглядит статическим / глобальным - поместите его в кадр стека функции.
square_prog_2 получает два аргумента: rqstp и transp. Они сохраняются в malloc'е struct data_str [поэтому каждый поток имеет свою собственную копию]. Но мне интересно, что такое значения rqstp и transp (например, printf("%p")). Они должны различаться, или каждый поток будет сталкиваться друг с другом при попытке их использовать [поэтому требуется pthread_mutex_lock]. Malloc не клонирует rqstp/transp, поэтому, если они одинаковые, это проблема, потому что у вас может быть два потока, пытающихся риффить на одни и те же буферы одновременно.
Существует код возврата 11. За исключением некоторого специального кода, который выглядит подозрительно как SIGSEGV в потоке. Это было бы полностью учтено перекрытием rqstp/transp.
Возможно, вам придется пересмотреть архитектуру, так как я подозреваю, что XDR не является поточно-ориентированным - и не должно быть. Кроме того, я не думаю, что svc_* потокобезопасен / осведомлен.
Пуск однопоточный. В качестве теста сделайте так, чтобы square_prog_2 напрямую вызывал serv_request (например, не делайте pthread_*). Могу поспорить, что работает во всех режимах.
Если это так, держитесь за свою шляпу - пример кода, использующего потоки, не работает - полный условий гонки, и он будет зависеть от ошибки и т. Д. Если вы не зациклены на использовании потоков (нет необходимости в такой легкой задаче, как x * х), вы можете просто наслаждаться, как есть.
В противном случае решение будет немного сложнее. Основной поток должен делать весь доступ к сокету и весь анализ / кодирование XDR. Он не может использовать svc_run - вы должны бросить свой собственный. Ребенок может выполнять только реальную работу (например, x * x) и не может касаться гнезда /req/transp и т. Д.
Основная тема:
while (1) {
if (svc_getreq_poll()) {
// parse XDR
// create data/return struct for child thread
// create thread
// add struct to list of "in-flight" requests
}
forall struct in inflight {
if (reqdone) {
// take result from struct
// encode into XDR
// do send_reply
// remove struct from list
}
}
}
Для дочерней структуры это будет выглядеть так:
struct child_struct {
int num;
int num_squared;
};
И функция дочернего потока становится однострочной: ptr->num_squared = ptr->num * ptr->num
ОБНОВЛЕНИЕ: Многопоточные RPC-серверы, по-видимому, не поддерживаются в Linux или FreeBSD
Вот документ: https://www.redhat.com/archives/redhat-list/2004-June/msg00439.html Это более чистый пример, с которого можно начать.
Из этого: Помните, что опция rpcgen не поддерживается в Linux. Вызовы библиотеки, предоставляемые SunOS RPC для создания многопоточного сервера RPC, также недоступны в Linux
Вот справочная страница Linux rpcgen: http://linux.die.net/man/1/rpcgen Нет упоминания о -M. IMO, это означает, что программа rpcgen имеет опцию и генерирует заглушки, но основной поддержки нет, поэтому они оставили ее вне документа.
Вот справочная страница FreeBSD [и причина, по которой нет поддержки]: http://www.freebsd.org/cgi/man.cgi?query=rpcgen&sektion=1&manpath=FreeBSD+5.0-RELEASE Смотрите документацию для -M в этом:
M - Создать многопоточные заглушки для передачи аргументов и результатов между кодом, сгенерированным rpcgen, и кодом, написанным пользователем. Эта опция полезна для пользователей, которые хотят использовать потоки в своем коде. Однако функции rpc_svc_calls(3) еще не являются MT-безопасными, что означает, что сгенерированный rpcgen код на стороне сервера не будет MT-безопасным.
Альтернативный способ:
Зачем вообще беспокоиться о RPC/XDR? Накладные расходы огромны для больших массивов, которые вы собираетесь использовать. Большинство стандартных применений для таких вещей, как желтые страницы, которые не имеют много данных.
В наши дни большинство систем имеют порядок байтов. Просто поместите собственный буфер в сокет, который вы открываете напрямую. На сервере сделайте, чтобы демон прослушал, затем разветвил дочерний процесс, и чтобы дочерний процесс принял, прочитал данные, сделал вычисления и отправил ответ. В худшем случае, ребенку нужно будет выполнить обратный обмен, но это легко сделать в узком цикле, используя bswap_32.
Простая небольшая управляющая структура в начале каждого сообщения в любом направлении, которая ставит префикс полезных данных:
struct msgcontrol {
int what_i_am;
int operation_to_perform;
int payload_length;
int payload[0];
};
Специальное примечание: я делал это коммерчески раньше (например, MPI и свернул свой собственный), и вам, возможно, придется выполнить вызовы setsockopt, чтобы увеличить размер буфера сокета ядра до чего-то достаточно большого, чтобы выдержать кучу данных
На самом деле, теперь, когда я думаю об этом, если вы не хотите снимать свои собственные, MPI может представлять интерес. Однако, пользуясь им, я не настоящий фанат. У него были неожиданные проблемы, и нам пришлось убрать его в пользу прямого контроля наших сокетов.