Избитая тема Cowboy+Gproc+Websockets прошу помощи

194 views
Skip to first unread message

Vladimir Drjupin

unread,
Oct 2, 2014, 10:42:47 AM10/2/14
to erlang-...@googlegroups.com
Здравствуйте, уважаемые участники, прошу помощи в изложенной в заголовке теме, я начинающий, разбирал пример чата отсюда: http://maxim.livejournal.com/382376.html

-module(websocket_chat).
-export([init/3]).
-export([websocket_init/3, websocket_handle/3,
websocket_info/3, websocket_terminate/3]).

init(_Any, _Req, _Opt) ->
{upgrade, protocol, cowboy_http_websocket}.

websocket_init(_Any, Req, _Opt) ->
gproc:reg({p,l, main_room}),
{ok, Req, undefined, hibernate}.

websocket_handle({text,Data}, Req, State) ->
gproc:send({p,l,main_room},Data),
{ok, Req,State, hibernate};
websocket_handle(_Any, Req, State) ->
{ok, Req, State, hibernate}.

websocket_info(_Info, Req, State) ->
io:format("~p~n",[_Info]),
{reply, {text,_Info}, Req, State, hibernate}.

websocket_terminate(_Reason, _Req, _State) ->
ok.

Сломал голову, в попытках как-то прикрутить ID пользователей, или сессии их хотя-бы, что-то, что можно было бы привязать к другому серверу, потому как авторизация проходит на стандартном PHP+MySQL, на хостинге, а чат-сервер пока что на домашней машине. Предполагал ведение какого-то dict'а в глобальном State, соответствие ПИДов, статуса, и имен, но в примере, в регистрации gproc, для всех процессов идет одно имя, и инициализация проходит в момент, когда какого-то глобального стэйта нету. Приходить эта инфа будет после подключения, первым JSON пакетом, разобрать его не проблема, проанализировать тоже, но как реализовать привязку конкретного ID пользователя, под которым он в базе хостинга, к его ПИД на чат-сервере, чтобы сменить статус, или отослать ЕМУ сообщение, или вообще узнать его - никаких мыслей. Буду благодарен за пример кода или пинок на страницу, где есть что-то за что можно зацепиться. В эрланге не первый день, но опыта крайне мало, поэтому прошу не ругаться...

Paul Peregud

unread,
Oct 2, 2014, 5:11:03 PM10/2/14
to erlang-...@googlegroups.com
gproc служит для регистрации процесов. Если вам подходит однозначная
регистрация (имя -> pid()) то можете сделать следующее:
1) получаете ник пользователя на данном нике
2) из процеса пользователя регистрируете его: gproc:reg({n, l,
{username, Nick}})
3) когда вам нужно получить обратно пид по нику просто обратитесь к
gproc:where({n, l, Nick}), который вернет вам pid().

Если не подходит однозначная (вы позволяете пользователю подключаться
многократно), тогда вместо name воспользуйтесь property.

Документация по gproc
здесь:https://github.com/uwiger/gproc/blob/master/doc/gproc.md
> --
> Вы получили это сообщение, поскольку подписаны на группу "Erlang по-русски".
> Чтобы отменить подписку на эту группу и больше не получать от нее сообщения,
> отправьте письмо на электронный адрес
> erlang-russia...@googlegroups.com.
> Чтобы отправлять сообщения в эту группу, отправьте письмо на электронный
> адрес erlang-...@googlegroups.com.
> Чтобы настроить другие параметры, перейдите по ссылке
> https://groups.google.com/d/optout.



--
Best regards,
Paul Peregud
+48602112091

Vladimir Drjupin

unread,
Oct 3, 2014, 1:48:37 AM10/3/14
to
Мануал-то понятен, что и для чего. Я не до конца понимаю процессов, которые организует Ковбой, фактически, первая регистрация в примере идет в блоке init, при "рукопожатии" браузера с сервером. Там у нас есть Pid процесса, который регистрируется в gproc. 

websocket_init(_Any, Req, _Opt) ->
gproc:reg({p,l, main_room}),
{ok, Req, undefined, hibernate}.

Т.е. тут PID вызывающей функции -> websocket_init -> gproc регистрация этого вызывающего PID под property main_room.

А вот следующий блок:

websocket_handle({text,Data}, Req, State) ->
gproc:send({p,l,main_room},Data),
{ok, Req,State, hibernate};
websocket_handle(_Any, Req, State) ->
{ok, Req, State, hibernate}.

прием сообщения и далее логика - рассылка или анализ, можно ли в этой функции провести регистрацию, тот же будет Pid процесса, что при websocket_init или нет. Как-то так:

websocket_handle({text,Data}, Req, State) ->
Dt = jiffy:decode(Data),
case Dt of
{<<act>>,<<register>>,_,Name} -> 
   gproc:reg({p,l, main_room}), % - регистрация в комнате для групповой рассылки по всем в комнате
   gproc:reg({n,l, Name}); % - уникальная регистрация для сообщения конкретному пользователю
_ -> 
   gproc:send({p,l,main_room},Data);
end.

Aleksey Kluchnikov

unread,
Oct 3, 2014, 5:39:23 AM10/3/14
to erlang-russian
Не очень понятно что нужно.

Ели у вас сторонний сервер авторизации, то пусть он выдает токены.
Когда приходит месага с этим токеном, валидатите токен на сервере
авторизации, стартуете сессию для
пользователя, регистрируете ее на этот токен. Далее из этой сессии
пересылаете месагу в комнату, к месаге прибавляете пид. Комната путь
держит пиды тех кто в нее писал. процесс пользователя хранит пиды
комнат в которые были посланы месаги. По истечению сессии пользователя
он должен послать сигнал во все комнаты в которые он подписан, о
завершении процесса. Соответственно на любое событие в комнате
оповещаете всех по списку пидов. В сесси пользователя также держите
пид вебсокета ковбоя, чтобы отсылать, пуши.

Как то оно не просто, но интерактивность она такая..



3 октября 2014 г., 9:48 пользователь Vladimir Drjupin
<demx...@gmail.com> написал:
> Мануал-то понятен, что и для чего. Я не до конца понимаю процессов, которые
> организует Ковбой, фактически, первая регистрация в примере идет в блоке
> init, при "рукопожатии" браузера с сервером. Там у нас есть Pid процесса,
> который регистрируется в gproc.
>
> websocket_init(_Any, Req, _Opt) ->
> gproc:reg({p,l, main_room}),
> {ok, Req, undefined, hibernate}.
>
> Т.е. тут PID вызывающей функции -> websocket_init -> gproc регистрация этого
> вызывающего PID под property main_room.
>
> А вот следующий блок:
>
> websocket_handle({text,Data}, Req, State) ->
> gproc:send({p,l,main_room},Data),
> {ok, Req,State, hibernate};
> websocket_handle(_Any, Req, State) ->
> {ok, Req, State, hibernate}.
>
> прием сообщения и далее логика - рассылка или анализ, можно ли в этой
> функции провести регистрацию, тот же будет Pid процесса, что при
> websocket_init или нет. Как-то так:
>
> websocket_handle({text,Data}, Req, State) ->
> Dt = jiffy:decode(Data).
> case Dt of
> {<<act>>,<<register>>,_,Name} ->
> gproc:reg({p,l, main_room}), % - регистрация в комнате для групповой
> рассылки по всем в комнате
> gproc:reg({n,l, Name}); % - уникальная регистрация для сообщения
> конкретному пользователю
> _ ->
> gproc:send({p,l,main_room},Data);
> end.
> {ok, Req,State, hibernate};
> websocket_handle(_Any, Req, State) ->
> {ok, Req, State, hibernate}.
>

Aleksey Kluchnikov

unread,
Oct 3, 2014, 5:48:13 AM10/3/14
to erlang-russian
Подумал.. наверно не нужно делать отдельную сессию для пользователя,
все можно сделать в процессе вебсокета ковбоя. гпроц получается нужен
только для регистрации комнат

3 октября 2014 г., 13:39 пользователь Aleksey Kluchnikov
<kluchn...@gmail.com> написал:

zheka_13

unread,
Oct 3, 2014, 6:10:13 AM10/3/14
to erlang-...@googlegroups.com
Мое мнение, использовать ковбой только в виде транспорта - подключение, отключение, пересылка месаг. А собственно организацией логики, комнат и тд. и тп - должен заниматься какой то другой gen_server куда ковбой собственно и сваливает все месаги. В том ген_сервере уже можно накрутить чего душа пожелает.




3 октября 2014 г., 12:48 пользователь Aleksey Kluchnikov <kluchn...@gmail.com> написал:
Чтобы добавлять сообщения в эту группу, отправьте письмо по адресу erlang-...@googlegroups.com.
Настройки подписки и доставки писем: https://groups.google.com/d/optout.

Aleksey Kluchnikov

unread,
Oct 3, 2014, 6:21:38 AM10/3/14
to erlang-russian
Тут уже надо шанс прикидывать, отдельно что вроде спокойнее но x2
процессов или вместе и съекономить

3 октября 2014 г., 14:10 пользователь zheka_13 <pol...@gmail.com> написал:

zheka_13

unread,
Oct 3, 2014, 6:23:59 AM10/3/14
to erlang-...@googlegroups.com
Почему это х2 процессов? 
х процессов ковбоя и 1  - обработчик сообщений.
получается х+1

3 октября 2014 г., 13:21 пользователь Aleksey Kluchnikov <kluchn...@gmail.com> написал:

Aleksey Kluchnikov

unread,
Oct 3, 2014, 6:26:04 AM10/3/14
to erlang-russian
Если для каждого пользователя держать процесс. А смысл в одном
обработчике событий? Зачем все через него прогонять?

3 октября 2014 г., 14:23 пользователь zheka_13 <pol...@gmail.com> написал:

zheka_13

unread,
Oct 3, 2014, 6:38:50 AM10/3/14
to erlang-...@googlegroups.com
Он позволяет навернуть любую логику обмена сообщениями между пользователями, с комнатами, правами, ролями, очередями .... Туда передается пид вебсокетного процесса и мессаги, обработчик к этому пиду цепляет юзера(авторизует) со всеми параметрами, и вперед  - получаем и отправляем как угодно согласно той логике которую мы выстроим в этом обработчике.

3 октября 2014 г., 13:26 пользователь Aleksey Kluchnikov <kluchn...@gmail.com> написал:

Aleksey Kluchnikov

unread,
Oct 3, 2014, 6:41:15 AM10/3/14
to erlang-russian
Если он будет один то это узкое место (особенно если он будет
запрашивать внешнюю авторизацию например), их надо делать пул, что
тоже не просто

3 октября 2014 г., 14:38 пользователь zheka_13 <pol...@gmail.com> написал:

Vladimir Drjupin

unread,
Oct 3, 2014, 10:37:28 AM10/3/14
to erlang-...@googlegroups.com
Согласен с вами, Алексей, узкое место, даже очень узкое. Пытался делать прототип на мочивебе, с одним рассыльщиком и анализатором - захлебывается от 6 десятков пользователей, переполнение очереди. Потому и пробую на ковбое, у него на пользователя отдельный процесс, в котором можно накрутить логику. Печально что без централизованного списка в State, но его можно выдрать из gproc при нужде. С изначальным вопросом разобрался - проблема исчезла, понял логику работы, теперь долбаюсь с самим ковбоем - не хочет без релиза работать, статику отдает, а на хэндлер вебсокета вываливает простыню ошибок. Причем непонятно что не нравится, если релизить через relx это дело, как во всех примерах ковбоя, то все работает...

Ranch listener http had connection process started with cowboy_protocol:start_link/4 at <0.181.0> exit with reason: {[{reason,undef},{mfa,{ws_handler,init,2}},{stacktrace,[{ws_handler,init,[{http_req,#Port<0.1546>,ranch_tcp,keepalive,<0.181.0>,<<"GET">>,'HTTP/1.1',{{192,168,1,3},64082},<<"192.168.1.1">>,undefined,8118,<<"/websocket">>,undefined,<<>>,[],[{<<"upgrade">>,<<"websocket">>},{<<"connection">>,<<"Upgrade">>},{<<"host">>,<<"192.168.1.1:8118">>},{<<"origin">>,<<"http://192.168.1.1:8118">>},{<<"cookie">>,<<"ci_testCookie=1; show_filter=true">>},{<<"sec-websocket-key">>,<<"eVzmUWPb0ZrCi0C4na+EUw==">>},{<<"sec-websocket-version">>,<<"13">>}],[],waiting,<<>>,undefined,false,waiting,[],<<>>,undefined},[]],[]},{cowboy_handler,execute,2,[{file,"src/cowboy_handler.erl"},{line,39}]},{cowboy_protocol,execute,4,[{file,"src/cowboy_protocol.erl"},{line,424}]}]},{req,[{socket,#Port<0.1546>},{transport,ranch_tcp},{connection,keepalive},{pid,<0.181.0>},{method,<<"GET">>},{version,'HTTP/1.1'},{peer,{{192,168,1,3},64082}},{host,<<"192.168.1.1">>},{host_info,undefined},{port,8118},{path,<<"/websocket">>},{path_info,undefined},{qs,<<>>},{bindings,[]},{headers,[{<<"upgrade">>,<<"websocket">>},{<<"connection">>,<<"Upgrade">>},{<<"host">>,<<"192.168.1.1:8118">>},{<<"origin">>,<<"http://192.168.1.1:8118">>},{<<"cookie">>,<<"ci_testCookie=1; show_filter=true">>},{<<"sec-websocket-key">>,<<"eVzmUWPb0ZrCi0C4na+EUw==">>},{<<"sec-websocket-version">>,<<"13">>}]},{meta,[]},{body_state,waiting},{buffer,<<>>},{multipart,undefined},{resp_compress,false},{resp_state,waiting},{resp_headers,[]},{resp_body,<<>>},{onresponse,undefined}]},{opts,[]}],[{cowboy_protocol,execute,4,[{file,"src/cowboy_protocol.erl"},{line,424}]}]}


Спасибо за советы всем!

zheka_13

unread,
Oct 3, 2014, 11:14:54 AM10/3/14
to erlang-...@googlegroups.com
а можно узнать каков поток сообщений в системе? Потому что у меня как раз так и реализовано. Для каждого пользователя  - процесс ковбоя и плюс один на всех менеджер сообщений который между пользователями их рассылает. При чем юзеры побиты определенным образом по группам и менеджер это разруливает и одно поступающее сообщение иногда веером кидает на целые группы вебсокетов. Авторизация у меня вообще на БД. Сейчас работает 250 пользователей, вижу по нагрузке что тысячу выдержит без проблем, это с учетом того что код у меня не ахти, я новичок и вроде как оптимизацией ваще не занимался. 

3 октября 2014 г., 17:37 пользователь Vladimir Drjupin <demx...@gmail.com> написал:
Согласен с вами, Алексей, узкое место, даже очень узкое. Пытался делать прототип на мочивебе, с одним рассыльщиком и анализатором - захлебывается от 6 десятков пользователей, переполнение очереди. Потому и пробую на ковбое, у него на пользователя отдельный процесс, в котором можно накрутить логику. Печально что без централизованного списка в State, но его можно выдрать из gproc при нужде. С изначальным вопросом разобрался - проблема исчезла, понял логику работы, теперь долбаюсь с самим ковбоем - не хочет без релиза работать, статику отдает, а на хэндлер вебсокета вываливает простыню ошибок. Причем непонятно что не нравится, если релизить через relx это дело, как во всех примерах ковбоя, то все работает...

Ranch listener http had connection process started with cowboy_protocol:start_link/4 at <0.181.0> exit with reason: {[{reason,undef},{mfa,{ws_handler,init,2}},{stacktrace,[{ws_handler,init,[{http_req,#Port<0.1546>,ranch_tcp,keepalive,<0.181.0>,<<"GET">>,'HTTP/1.1',{{192,168,1,3},64082},<<"192.168.1.1">>,undefined,8118,<<"/websocket">>,undefined,<<>>,[],[{<<"upgrade">>,<<"websocket">>},{<<"connection">>,<<"Upgrade">>},{<<"host">>,<<"192.168.1.1:8118">>},{<<"origin">>,<<"http://192.168.1.1:8118">>},{<<"cookie">>,<<"ci_testCookie=1; show_filter=true">>},{<<"sec-websocket-key">>,<<"eVzmUWPb0ZrCi0C4na+EUw==">>},{<<"sec-websocket-version">>,<<"13">>}],[],waiting,<<>>,undefined,false,waiting,[],<<>>,undefined},[]],[]},{cowboy_handler,execute,2,[{file,"src/cowboy_handler.erl"},{line,39}]},{cowboy_protocol,execute,4,[{file,"src/cowboy_protocol.erl"},{line,424}]}]},{req,[{socket,#Port<0.1546>},{transport,ranch_tcp},{connection,keepalive},{pid,<0.181.0>},{method,<<"GET">>},{version,'HTTP/1.1'},{peer,{{192,168,1,3},64082}},{host,<<"192.168.1.1">>},{host_info,undefined},{port,8118},{path,<<"/websocket">>},{path_info,undefined},{qs,<<>>},{bindings,[]},{headers,[{<<"upgrade">>,<<"websocket">>},{<<"connection">>,<<"Upgrade">>},{<<"host">>,<<"192.168.1.1:8118">>},{<<"origin">>,<<"http://192.168.1.1:8118">>},{<<"cookie">>,<<"ci_testCookie=1; show_filter=true">>},{<<"sec-websocket-key">>,<<"eVzmUWPb0ZrCi0C4na+EUw==">>},{<<"sec-websocket-version">>,<<"13">>}]},{meta,[]},{body_state,waiting},{buffer,<<>>},{multipart,undefined},{resp_compress,false},{resp_state,waiting},{resp_headers,[]},{resp_body,<<>>},{onresponse,undefined}]},{opts,[]}],[{cowboy_protocol,execute,4,[{file,"src/cowboy_protocol.erl"},{line,424}]}]}


Спасибо за советы всем!

--

Vladimir Drjupin

unread,
Oct 3, 2014, 11:27:11 AM10/3/14
to erlang-...@googlegroups.com
Ориентировочно вместимость до 1500 человек, по условиям задачи, планируется вебинарная площадка, а там народу много бывает. Но я еще больший новичок, чем вы, мой гамнокод на основе мочивебовского примера не справился с 60 - сервер тупо перестал отвечать, точнее перестал отвечать рассыльщик, а коннектилось по вебсокету влет - по статусу было видно. Хотя логика вроде как несложная была.

пятница, 3 октября 2014 г., 21:14:54 UTC+6 пользователь Evgen Polivoda написал:

mprize

unread,
Oct 3, 2014, 3:41:13 PM10/3/14
to erlang-...@googlegroups.com
ws_handler:init нужной размерности есть и экспортирован?

Vladimir Drjupin

unread,
Oct 3, 2014, 3:57:23 PM10/3/14
to
Самое интересное, что это был чистый ковбоевский пример по вебсокетам, заработало, только когда перезалил чистый из архива, так и не понял, в чем была проблема, но после кучи матов, заработало. Какой-то непонятный баг - где-то стопудово что-то упустил, или лишнее, хотя на первый взгляд нормально. Еще такой вопрос, можно из gproc выдрать по ПИДу имя?

суббота, 4 октября 2014 г., 1:41:13 UTC+6 пользователь mprize написал:
Reply all
Reply to author
Forward
0 new messages