Skip to content

Commit 8d2990d

Browse files
committed
Merge pull request #941 from fadushin/gen-sockets
Opt-in purerlang implementation of gen_tcp and gen_udp using socket API This PR adds the ability to use the `gen_tcp` and `gen_udp` interfaces using the OTP socket interface as a "back-end", eliminating the need for the native C drivers on generic_unix and ESP32. Using the feature also allows use of the `gen_tcp` and `gen_udp` interfaces on the rp2040 platform. This feature is still considered "experimental" and therefore is not currently documented. However, the configuration APIs follow the OTP `inet_backend` configuration option introduced in OTP-23. This PR addresses issue #893 These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents ec9f1aa + 3be5b78 commit 8d2990d

File tree

17 files changed

+1784
-287
lines changed

17 files changed

+1784
-287
lines changed

libs/estdlib/src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ set(ERLANG_MODULES
3333
gen_server
3434
gen_statem
3535
gen_udp
36+
gen_udp_inet
37+
gen_udp_socket
3638
gen_tcp
39+
gen_tcp_inet
40+
gen_tcp_socket
3741
supervisor
3842
inet
3943
io_lib

libs/estdlib/src/gen_tcp.erl

Lines changed: 53 additions & 116 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,14 @@
5252
| {timeout, timeout()}
5353
| list
5454
| binary
55-
| {binary, boolean()}.
55+
| {binary, boolean()}
56+
| {inet_backend, inet | socket}.
5657

5758
-type listen_option() :: option().
5859
-type connect_option() :: option().
5960
-type packet() :: string() | binary().
6061

61-
-define(DEFAULT_PARAMS, [{active, true}, {buffer, 512}, {timeout, infinity}]).
62+
-include("inet-priv.hrl").
6263

6364
%%-----------------------------------------------------------------------------
6465
%% @param Address the address to which to connect
@@ -91,10 +92,14 @@
9192
Options :: [connect_option()]
9293
) ->
9394
{ok, Socket :: inet:socket()} | {error, Reason :: reason()}.
94-
connect(Address, Port, Params0) ->
95-
Socket = open_port({spawn, "socket"}, []),
96-
Params = merge(Params0, ?DEFAULT_PARAMS),
97-
connect(Socket, normalize_address(Address), Port, Params).
95+
connect(Address, Port, Options) ->
96+
Module = get_inet_backend_module(Options),
97+
case Module:connect(Address, Port, Options) of
98+
{ok, Socket} ->
99+
{ok, {?GEN_TCP_MONIKER, Socket, Module}};
100+
Other ->
101+
Other
102+
end.
98103

99104
%%-----------------------------------------------------------------------------
100105
%% @param Socket The Socket obtained via connect/3
@@ -107,13 +112,8 @@ connect(Address, Port, Params0) ->
107112
%% @end
108113
%%-----------------------------------------------------------------------------
109114
-spec send(Socket :: inet:socket(), Packet :: packet()) -> ok | {error, Reason :: reason()}.
110-
send(Socket, Packet) ->
111-
case call(Socket, {send, Packet}) of
112-
{ok, _Len} ->
113-
ok;
114-
Error ->
115-
Error
116-
end.
115+
send({?GEN_TCP_MONIKER, Socket, Module}, Packet) ->
116+
Module:send(Socket, Packet).
117117

118118
%%-----------------------------------------------------------------------------
119119
%% @equiv recv(Socket, Length, infinity)
@@ -122,8 +122,8 @@ send(Socket, Packet) ->
122122
%%-----------------------------------------------------------------------------
123123
-spec recv(Socket :: inet:socket(), Length :: non_neg_integer()) ->
124124
{ok, packet()} | {error, Reason :: reason()}.
125-
recv(Socket, Length) ->
126-
recv(Socket, Length, infinity).
125+
recv({?GEN_TCP_MONIKER, Socket, Module}, Length) ->
126+
Module:recv(Socket, Length).
127127

128128
%%-----------------------------------------------------------------------------
129129
%% @param Socket the socket over which to receive a packet
@@ -146,8 +146,8 @@ recv(Socket, Length) ->
146146
%%-----------------------------------------------------------------------------
147147
-spec recv(Socket :: inet:socket(), Length :: non_neg_integer(), Timeout :: non_neg_integer()) ->
148148
{ok, packet()} | {error, Reason :: reason()}.
149-
recv(Socket, Length, Timeout) ->
150-
call(Socket, {recv, Length, Timeout}).
149+
recv({?GEN_TCP_MONIKER, Socket, Module}, Length, Timeout) ->
150+
Module:recv(Socket, Length, Timeout).
151151

152152
%%-----------------------------------------------------------------------------
153153
%% @param Port the port number on which to listen. Specify 0 to use an OS-assigned
@@ -161,24 +161,14 @@ recv(Socket, Length, Timeout) ->
161161
%% @end
162162
%%-----------------------------------------------------------------------------
163163
-spec listen(Port :: inet:port_number(), Options :: [listen_option()]) ->
164-
{ok, ListeningSocket :: inet:socket()} | {error, Reason :: reason()}.
164+
{ok, Socket :: inet:socket()} | {error, Reason :: reason()}.
165165
listen(Port, Options) ->
166-
Socket = open_port({spawn, "socket"}, []),
167-
Params = merge(Options, ?DEFAULT_PARAMS),
168-
InitParams = [
169-
{proto, tcp},
170-
{listen, true},
171-
{controlling_process, self()},
172-
{port, Port},
173-
{backlog, 5}
174-
| Params
175-
],
176-
case call(Socket, {init, InitParams}) of
177-
ok ->
178-
{ok, Socket};
179-
ErrorReason ->
180-
%% TODO close port
181-
ErrorReason
166+
Module = get_inet_backend_module(Options),
167+
case Module:listen(Port, Options) of
168+
{ok, Socket} ->
169+
{ok, {?GEN_TCP_MONIKER, Socket, Module}};
170+
Other ->
171+
Other
182172
end.
183173

184174
%%-----------------------------------------------------------------------------
@@ -187,10 +177,15 @@ listen(Port, Options) ->
187177
%% @doc Accept a connection on a listening socket.
188178
%% @end
189179
%%-----------------------------------------------------------------------------
190-
-spec accept(ListenSocket :: inet:socket()) ->
180+
-spec accept(Socket :: inet:socket()) ->
191181
{ok, Socket :: inet:socket()} | {error, Reason :: reason()}.
192-
accept(ListenSocket) ->
193-
accept(ListenSocket, infinity).
182+
accept({?GEN_TCP_MONIKER, Socket, Module}) ->
183+
case Module:accept(Socket, infinity) of
184+
{ok, ConnectedSocket} ->
185+
{ok, {?GEN_TCP_MONIKER, ConnectedSocket, Module}};
186+
Error ->
187+
Error
188+
end.
194189

195190
%%-----------------------------------------------------------------------------
196191
%% @param ListenSocket the listening socket.
@@ -199,15 +194,14 @@ accept(ListenSocket) ->
199194
%% @doc Accept a connection on a listening socket.
200195
%% @end
201196
%%-----------------------------------------------------------------------------
202-
-spec accept(ListenSocket :: inet:socket(), Timeout :: timeout()) ->
197+
-spec accept(Socket :: inet:socket(), Timeout :: timeout()) ->
203198
{ok, Socket :: inet:socket()} | {error, Reason :: reason()}.
204-
accept(ListenSocket, Timeout) ->
205-
case call(ListenSocket, {accept, Timeout}) of
206-
{ok, Socket} when is_pid(Socket) ->
207-
{ok, Socket};
208-
ErrorReason ->
209-
%% TODO close port
210-
ErrorReason
199+
accept({?GEN_TCP_MONIKER, Socket, Module}, Timeout) ->
200+
case Module:accept(Socket, Timeout) of
201+
{ok, ConnectedSocket} ->
202+
{ok, {?GEN_TCP_MONIKER, ConnectedSocket, Module}};
203+
Error ->
204+
Error
211205
end.
212206

213207
%%-----------------------------------------------------------------------------
@@ -217,8 +211,8 @@ accept(ListenSocket, Timeout) ->
217211
%% @end
218212
%%-----------------------------------------------------------------------------
219213
-spec close(Socket :: inet:socket()) -> ok.
220-
close(Socket) ->
221-
inet:close(Socket).
214+
close({?GEN_TCP_MONIKER, Socket, Module}) ->
215+
Module:close(Socket).
222216

223217
%%-----------------------------------------------------------------------------
224218
%% @param Socket the socket to which to assign the pid
@@ -236,77 +230,20 @@ close(Socket) ->
236230
%%-----------------------------------------------------------------------------
237231
-spec controlling_process(Socket :: inet:socket(), Pid :: pid()) ->
238232
ok | {error, Reason :: reason()}.
239-
controlling_process(Socket, Pid) ->
240-
call(Socket, {controlling_process, Pid}).
241-
242-
%% internal operations
243-
244-
%% @private
245-
connect(DriverPid, Address, Port, Params) ->
246-
InitParams = [
247-
{proto, tcp},
248-
{connect, true},
249-
{controlling_process, self()},
250-
{address, Address},
251-
{port, Port}
252-
| Params
253-
],
254-
case call(DriverPid, {init, InitParams}) of
255-
ok ->
256-
{ok, DriverPid};
257-
ErrorReason ->
258-
%% TODO close port
259-
ErrorReason
260-
end.
261-
262-
%% TODO implement this in lists
263-
264-
%% @private
265-
merge(Config, Defaults) ->
266-
merge(Config, Defaults, []) ++ Config.
267-
268-
%% @private
269-
merge(_Config, [], Accum) ->
270-
Accum;
271-
merge(Config, [H | T], Accum) ->
272-
Key =
273-
case H of
274-
{K, _V} -> K;
275-
K -> K
276-
end,
277-
case proplists:get_value(Key, Config) of
278-
undefined ->
279-
merge(Config, T, [H | Accum]);
280-
Value ->
281-
merge(Config, T, [{Key, Value} | Accum])
282-
end.
283-
284-
%% @private
285-
normalize_address(localhost) ->
286-
"127.0.0.1";
287-
normalize_address(loopback) ->
288-
"127.0.0.1";
289-
normalize_address(Address) when is_list(Address) ->
290-
Address;
291-
normalize_address({A, B, C, D}) when
292-
is_integer(A) and is_integer(B) and is_integer(C) and is_integer(D)
293-
->
294-
integer_to_list(A) ++
295-
"." ++
296-
integer_to_list(B) ++
297-
"." ++
298-
integer_to_list(C) ++
299-
"." ++ integer_to_list(D).
300-
301-
%% TODO IPv6
233+
controlling_process({?GEN_TCP_MONIKER, Socket, Module}, Pid) ->
234+
Module:controlling_process(Socket, Pid).
302235

303236
%%
304-
%% Internal operations
237+
%% Internal implementation
305238
%%
306239

307-
call(Port, Msg) ->
308-
case port:call(Port, Msg) of
309-
{error, noproc} -> {error, closed};
310-
out_of_memory -> {error, enomem};
311-
Result -> Result
240+
%% @private
241+
get_inet_backend_module(Options) ->
242+
case proplists:get_value(inet_backend, Options) of
243+
undefined ->
244+
gen_tcp_inet;
245+
inet ->
246+
gen_tcp_inet;
247+
socket ->
248+
gen_tcp_socket
312249
end.

0 commit comments

Comments
 (0)