How handle only one client simultaneously in erlang gen_tcp? - tcp

I have TCP server that listen Ip:Port.
listen(Ip, Port) ->
Opts = [
binary,
{active, false},
{packet, 0},
{reuseaddr, true},
{ip, Ip}
],
case gen_tcp:listen(Port, Opts) of
{ok, ListenSock} ->
?MODULE:loop_accept(ListenSock);
{error, Reason} ->
exit(Reason)
end.
loop_accept(ListenSock) ->
{ok, Sock} = gen_tcp:accept(ListenSock),
?MODULE:loop(Sock),
?MODULE:loop_accept(ListenSock).
loop(Sock) ->
case gen_tcp:recv(Sock, 0) of
{ok, Data} ->
gen_tcp:send(Sock, [<<"Response: ">>, Data]),
?MODULE:loop(Sock);
{error, Reason} ->
ok
end.
Task: when one client connected on Ip:Port (for example telnet Ip Port), another client trying connection must be dropped. In other words, exclusive usage of Ip:Port.
Questions:
How it's implement on Erlang using gen_tcp module?
It is possible resolve by options of gen_tcp:listen?
How to programmaticaly drop trying connection in Erlang?
P.S. I am new in erlang.

First, you can't recv() like that when you specify {packet, 0}. Read this answer about gen_tcp.
The server could:
Pid = spawn(?MODULE, loop, [Sock])
Monitor the process in #1:
Ref = monitor(process, Pid)
But to prevent a race condition, you should perform #1 and #2 in one step:
{Pid, Ref} = spawn_monitor(?MODULE, loop [Sock])
After gen_tcp:accept(ListenSock) executes, do:
gen_tcp:close(ListenSock)
Detect when the client terminates and therefore it's time to start listening for a new client:
receive {'DOWN', Ref, process, Pid, _Reason} ->
listen(Ip, Port)
Or, if the client will not terminate after it is done sending data, then you can detect when the client closes the socket in loop():
case gen_tcp:recv(Sock, 0) of
{ok, Data} ->
gen_tcp:send(Sock, [<<"Response: ">>, Data]),
?MODULE:loop(Sock);
{error, closed} ->
listen(Ip, Port);
{error, Reason} ->
ok
end
=====
backlog socket option (e.g. {backlog, 0}):
The backlog option sets an OS socket configuration parameter. From man listen:
The backlog parameter defines the maximum length for the queue of
pending connections. If a connection request arrives with the queue
full, the client may receive an error with an indication of
ECONNREFUSED. Alternatively, if the underlying protocol supports
retransmission, the request may be ignored so that retries may
succeed.
And, a good read is this thread at Perl Monks: TCP server: How to reject connections when busy? Some snippets about the backlog configuration:
So, it looks like connection request are just ignored (as TCP does
support retransmission)
...when the queue is full, then the system simply stops answering SYN
packets, which triggers their resending. As result, the peer gets no
indication that your server is busy. It simply keeps waiting for
connection to be established.

Related

what is the difference between grpc stream error and TCP error

It is introduced in "google.golang.org/grpc/codes" that some errors can be generated by grpc and some cannot be generated, and the grpc stream should correspond to the stream in http2.0. Then I want to know whether it means that there is an error with the TCP connection when those grpc streams throw exceptions, so do I only need to reconnect the TCP connection, or do I have somes methods for only reconnect stream (such as reconnecting streams, etc.)
for {
request, err3 := stream.Recv()
if err3 == io.EOF {
return nil
}
if err3 != nil {
return err3 // how can i handle this error (grpc generated)
}
do something
}
gRPC-go handles network level reconnections for you: https://pkg.go.dev/google.golang.org/grpc#ClientConn
A ClientConn encapsulates a range of functionality including name resolution, TCP connection establishment (with retries and backoff) and TLS handshakes. It also handles errors on established connections by re-resolving the name and reconnecting.
If the error returns an error and is not type io.EOF, it means that something went wrong (including network errors) and that you have to request a new stream: https://github.com/grpc/grpc-go/blob/master/stream.go#L126 but you don't have to worry to create a new TCP connection.
RecvMsg blocks until it receives a message into m or the stream is done. It returns io.EOF when the stream completes successfully. On any other error, the stream is aborted and the error contains the RPC status.
If it can't get a new stream, means that it can't connect to the server or that something is very wrong with it, but if it is a transient network error, you will eventually be able to get a new stream.

How can I identify a complete response when receive lot of packets in concurrent TCP requests

I have a single TCP connection to a server, but possibly have multiple requests at the same time. Most of the time the response will be so big that I would constantly receive lot of data chunks. It's possible for me to check the data length in order to determine it is the END OF STREAM. But with multiple requests, sometimes packets "mixed" with another requests that causes a lot of failures.
For example,
for a normal request:
-> request #1 1/3
-> request #1 2/3
-> request #1 3/3
-> request #1 completed
in real life:
-> request #1 1/3
-> request #1 2/3
-> request #2 1/3
-> request #2 2/3
-> failure at some point
For now, my only thought is to make it serially by serving the request one after one. However it can't fully solve my problem because sometimes I got subscribed events comes in without control.
How can I solve this TCP problem in general?
I'm showing some of the code below (in case somebody knows erlang and elixir)
# Create TCP connection
{:ok, socket} =
:gen_tcp.connect(host_charlist, port, [:binary, active: true, keepalive: true])
# Send request
def handle_call({:send, msg}, from, state) do
:ok = :gen_tcp.send(state.socket, msg)
new_state = %{state | from: from, msg: ""}
{:noreply, new_state}
end
# When it receive packet
def handle_info({:tcp, _socket, msg}, state) do
new_state = %{state | msg: state.msg <> msg}
current_msg_size = byte_size(new_state.msg)
defined_msg_size = Response.get_body_size(new_state.msg) # this msg size can read from the first packet's header
cond do
current_msg_size == defined_msg_size ->
GenServer.reply(new_state.from, {:ok, new_state.msg})
{:noreply, %{new_state | msg: ""}}
current_msg_size > defined_msg_size ->
GenServer.reply(new_state.from, {:error, "Message size exceed."})
{:noreply, %{new_state | msg: ""}}
true ->
{:noreply, new_state}
end
end
At TCP level, in a connection, request and response do no exist, it's a single tube transferring bytes from one side to the other in order.
In order to handle interleaving over a single connection you have to handle it one level up the stack.
Possible solutions include:
Serializing
Framing: You frame the responses and guarantee somehow that frames are sent completely without interleaving in the server, then your receiver can inspect each frame (maybe spanning multiple receives) and assign it to the corresponding request.
One connection for each request: Let the OS handle the interleaving on the wire at the expense of a socket and the handshake each time
You can change the tcp option from active = true to active = false or to active = once
:gen_tcp.connect(host_charlist, port, [:binary, active: true, keepalive: true])
Then you can control the receiving of message by yourself, not by flooded by incoming messages.
1、receive upper's calling, process your client request, set active = false, waiting for server's response.
2、Try not to receive only related message, ignore these in the process's message queue or save these in the buffer.
3、when receive server's response, process it, then set active = once.
4、if timeout, set active = once.
https://www.erlang.org/doc/man/gen_tcp.html#controlling_process-2
controlling_process(Socket, Pid) -> ok | {error, Reason} Types Socket
= socket() Pid = pid() Reason = closed | not_owner | badarg | inet:posix() Assigns a new controlling process Pid to Socket. The
controlling process is the process that receives messages from the
socket. If called by any other process than the current controlling
process, {error, not_owner} is returned. If the process identified by
Pid is not an existing local pid, {error, badarg} is returned. {error,
badarg} may also be returned in some cases when Socket is closed
during the execution of this function.
If the socket is set in active mode, this function will transfer any
messages in the mailbox of the caller to the new controlling process.
If any other process is interacting with the socket while the transfer
is happening, the transfer may not work correctly and messages may
remain in the caller's mailbox. For instance changing the sockets
active mode before the transfer is complete may cause this.

erlang: how to receive HTTP/RTSP messages from socket?

I want to manage HTTP or RTSP sessions with Erlang.
For example, a standart session for RTSP protocol looks like:
OPTIONS rtsp://192.168.1.55/test/ RTSP/1.0\r\n
CSeq: 1\r\n
User-Agent: VLC media player (LIVE555 Streaming Media v2008.07.24)\r\n
...
PLAY rtsp://192.168.1.55/test/ RTSP/1.0\r\n
CSeq: 5\r\n
Session: 1\r\n
Range: npt=0.000-\r\n
User-Agent: VLC media player (LIVE555 Streaming Media v2008.07.24)\r\n
The length of the every message is different.
For erlang, gen_server:listen uses an option {active, true} (to allow getting of an unlimited qantity of data) or {active, false} (for getting fixed length of data).
Is there a recommended method how to get and parse such messages with variable length?
For HTTP, use one of the HTTP packet modes documented for the inet:setopts/2 function. For example, to set a socket to receive HTTP messages as binaries, you can set the {packet, http_bin} on the socket. Have a look at my simple web server example to see how to use the HTTP packet modes.
For RTSP, there's no built-in packet parser, but because RTSP headers are line-oriented like HTTP, you can do your own header parsing using the {packet, line} mode. In that mode, you'll receive one header at a time until you receive an empty line indicating the end of the headers. You can then change the socket to {packet, raw} mode to receive any message body. The Content-Length header if present indicates the size of any message body.
The {active, true} vs {active, false} socket modes you mention control how data arrive at the controlling process (owner) of the socket.
The {active, true} mode sends all data from the socket to the controlling process as soon as they arrive. In this mode, data arrive as messages on the owner's message queue. Receiving messages on the process message queue is great because it allows the process to also handle other non-socket-related Erlang messages while handling socket data, but {active, true} isn't used that often because it provides no TCP back-pressure to the sender, and so a fast sender can overrun the receiver.
The {active, false} mode requires the receiver to call gen_tcp:recv/2,3 on the socket to retrieve data. This doesn't have the back-pressure problem of {active, true} but it can make message handling awkward since the Erlang process has to actively request the socket data rather than just sitting in a receive loop as it can with the other active modes.
Two other active modes you didn't mention are {active, once} and {active, N}. In {active, once} mode, the receiving process gets a single message via its message queue at a time, with the socket moving to the passive {active, false} mode after each message. To get another message, the receiver has to set {active, once} on the socket again when it's ready for the next message. This mode is nice because messages arrive on the process message queue same as they do with {active, true} mode, but back-pressure still works. The {active, N} mode is similar except that N messages, rather than just one, are received before the socket reverts to passive mode.

Erlang: exit/shutdown synchronous or asynchronous?

You have a process tree you want to kill, so you send an exit(PID, shutdown) to the supervisor. There's other stuff you need to do, but it can't be done until this process tree is shutdown. For instance, let's say this process tree writes to a database. You want to shut everything down cleanly. You want to shut down the database, but obviously you need to shut down the process tree first, else the tree could be in the middle of a write to the database.
My question is, when I send the exit signal, is it synchronous or asynchronous? If it is synchronous, it seems I have no worries, but if it is asynchronous, I will need to do something like establish a process monitor and check whether the tree shut down before I proceed with database shutdown, correct?
Thanks.
Short answer: OTP shutdown is synchronous. exit/2 is a single asynchronous message.
Long answer: All messages in Erlang are asynchronous. The shutdown message is no different. However, there is more to shutdown than just sending a message. The supervisor listens for {'DOWN', ...} messages after sending the exit signal. Only after it receives a 'DOWN' message or times out does it proceed, so in effect it is synchronous. Checkout the supervisor source code. On line 894 is where the functions that actually makes the exit call is defined:
shutdown(Pid, Time) ->
case monitor_child(Pid) of
ok ->
exit(Pid, shutdown), %% Try to shutdown gracefully
receive
{'DOWN', _MRef, process, Pid, shutdown} ->
ok;
{'DOWN', _MRef, process, Pid, OtherReason} ->
{error, OtherReason}
after Time ->
exit(Pid, kill), %% Force termination.
receive
{'DOWN', _MRef, process, Pid, OtherReason} ->
{error, OtherReason}
end
end;
{error, Reason} ->
{error, Reason}
end.
The source code can be viewed on GitHub here: https://github.com/erlang/otp/blob/maint/lib/stdlib/src/supervisor.erl#L894
erlang:exit/2 calls on the other hand is simply an asynchronous exit signal
If you need to manage this yourself, do your own monitoring:
sneak_attack(BankGuard) ->
monitor(process, BankGuard),
exit(BankGuard, kill),
Cash = receive {'DOWN', _, process, BankGuard, _} -> rob_bank() end,
send_to_bahamas(Cash).
In this example rob_bank() and anything after is blocked waiting on the 'DOWN' message from BankGuard.
Also, note that this is a much more general concept than just shutting something down. All messages in Erlang are asynchronous but unlike UDP, ordering (between two processes) and delivery (so long as the destination is alive) is guaranteed. So synchronous messaging is simply monitoring the target, sending a tagged message, and blocking on receipt of the return message.

How do I detect tcp-client disconnect with gen_tcp?

I'am trying to use gen_tcp module.
There is example of server-side code, which I have troubles.
%% First, I bind server port and wait for peer connection
{ok, Sock} = gen_tcp:listen(7890, [{active, false}]),
{ok, Peer} = gen_tcp:accept(Sock),
%% Here client calls `gen_tcp:close/1` on socket and goes away.
%% After that I am tryin' send some message to client
SendResult = gen_server:send(Peer, <<"HELLO">>),
%% Now I guess that send is failed with {error, closed}, but...
ok = SendResult.
When I call gen_tcp:send/2 again, second call wil return {error, closed} as expected. But I want to understand, why first call succeeded? Am I missing some tcp-specific details?
This strange (for me) behavior is only for {active, false} connection.
In short, the reason for this is that there's no activity on the socket that can determine that the other end has closed. The first send appears to work because it operates on a socket that, for all intents and purposes, appears to be connected and operational. But that write activity determines that the other end is closed, which is why the second send fails as expected.
If you were to first read or recv from the socket, you'd quickly learn the other end was closed. Alternatively, if the socket were in an Erlang active mode, then you'd also learn of the other end closing because active modes poll the socket.
Aside from whether the socket is in an active mode or not, this has nothing to do with Erlang. If you were to write C code directly to the sockets API, for example, you'd see the same behavior.

Resources