Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sockets refactor: allow any family/type/protocol association #3750

Merged
merged 3 commits into from
Dec 23, 2016

Conversation

ysbaddaden
Copy link
Contributor

@ysbaddaden ysbaddaden commented Dec 21, 2016

The whole idea behing this refactor is to give more possibilities over sockets, for some less common scenarios (eg: DGRAM UNIX sockets) as well as cleanup the sockets implementation, by avoiding some repetition.

Refactor:

  • Socket is now enough to create, configure and use any kind of socket association of family, type and protocol is also possible, as long as it's supported by the underlying OS implementation;
    • as a consequence the connect, bind, listen, accept, send and receive methods were moved from subclasses into Socket itself;
  • The TCPSocket, TCPServer, UDPSocket, UNIXSocket and UNIXServer classes are merely sugar to avoid having to deal with socket details;
  • UNIXSocket and UNIXServer can now be used in DGRAM type, in addition to the default STREAM type.

Features:

  • Addrinfo DNS resolver, that wraps results from getaddrinfo;
  • Socket#receive : {String, IPAddress} to receive a String (like we can send(String);
  • Socket#bind(Int) to bind to all available interfaces;
  • Socket::Server module, included by both TCPServer and UNIXServer;
  • SO_REUSEPORT socket option; automatically set for TCPServer —see http://stackoverflow.com/a/14388707/199791

Breaking Change:

  • IPAddress now automatically detects the address family, so the argument was removed (limited impact).

Examples:

sock = UNIXSocket.new(Socket::Family::DGRAM)
sock.connect("/tmp/service.sock")
sock.send(message)
sock = Socket.tcp(Socket::Family::INET6)
# set some socket options (e.g. SO_REUSEPORT)
sock.bind(80)
sock.listen

closes #3214
closes #3495
refs #3586

@maxpowa
Copy link
Contributor

maxpowa commented Dec 22, 2016

Also should close #3495, if bind is expanded to be interface/port instead of just port.

@ysbaddaden
Copy link
Contributor Author

Thanks, I couldn't find this issue anymore.


property family : Family
property type : Type
property protocol : Protocol
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these three be changed after creating a socket? If not, they should probably be getters.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I'll change that.

# Force opened sockets to be closed on `exec(2)`. Only for platforms that don't
# support `SOCK_CLOEXEC` (e.g., Darwin).
protected def init_close_on_exec(fd : Int32)
{% unless LibC.constants.includes?("SOCK_CLOEXEC".id) %}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also use LibC.has_constant?("SOCK_CLOEXEC") (shorter plus probably a bit faster.) Not sure it works on the old compiler without .id, so it's probably better to leave the .id for now

#
# ```
# sock = Socket.tcp(Socket::Family::INET6)
# sock.bind "localhost", 1234
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this doc comment just be sock.bind 1234?

@@ -1,139 +1,25 @@
class IPSocket < Socket
# Returns the `IPAddress` for the local end of the IP socket.
def local_address
sockaddr = uninitialized LibC::SockaddrIn6
sockaddr = Pointer(LibC::SockaddrIn6).malloc.as(LibC::Sockaddr*)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this was changed? It's an extra allocation (tiny, but still...)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I ran into some issues. I hope I can change it back. Same below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries, we can take care of this later. It's just a tiny allocation and only used if you do request the local address :-)

end

# Returns the `IPAddress` for the remote end of the IP socket.
def remote_address
sockaddr = uninitialized LibC::SockaddrIn6
addrlen = LibC::SocklenT.new(sizeof(LibC::SockaddrIn6))
sockaddr = Pointer(LibC::SockaddrIn6).malloc.as(LibC::Sockaddr*)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, why the change?

@asterite
Copy link
Member

Looks really good to me! It enables some new stuff, like #3495 and the code looks much nicer, readable and organized. I just made a few comments.

def receive(max_message_size = 512) : {String, IPAddress}
bytes = Bytes.new(max_message_size)
bytes_read, sockaddr, addrlen = recvfrom(bytes)
{String.new(bytes.to_unsafe, bytes_read), IPAddress.from(sockaddr, addrlen)}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here there's a double allocation: Bytes.new and String.new (it copies the bytes). You can do something like:

str = String.new(max_message_size) do |buffer|
  bytes = Slice.new(buffer, max_message_size)
  bytes_read, sockaddr, addrlen = recvfrom(bytes)
  # etc.
  {bytes_read, 0}
end

The only "problem" with this is that this will maybe allocate more bytes than needed, so maybe occupy more memory until the string is GC'ed. However, I'll soon change String#new to realloc the pointer if the resulting bytesize is less than the requested capacity. I even did a few benchmarks and this makes a program go much faster as later less memory needs to be reclaimed.

@ysbaddaden
Copy link
Contributor Author

@asterite I applied all your suggestions, and skipped a few more extra allocations!

I also added support for SO_REUSEPORT which is a nice feature, and applied it automatically to TCPServer in addition to SO_REUSEADDR.

@asterite
Copy link
Member

@ysbaddaden Oops, my bad! It seems has_constant? can be passed a string literal, and it works fine in both the current and next versions of the compiler. So changing those has_constant?("SOME".id) to has_constant?("SOME") should make travis happy, and then I'll merge this :-)

Refactor:

- Socket is now enough to create, configure and use any kind of socket
  association of family, type and protocol is also possible, as long
  as it's supported by the underlying OS implementation.
- The TCPSocket, TCPServer, UDPSocket, UNIXSocket and UNIXServer
  classes are merely sugar to avoid having to deal with socket details.
- UNIXSocket and UNIXServer can now be used in DGRAM type, in addition
  to the default STREAM type.

Features:

- Adds Socket::Server type, included by both TCPServer and UNIXServer.
- Adds Addrinfo DNS resolver, that wraps results from `getaddrinfo`.

Breaking Changes:

- IPAddress now automatically detects the address family, so the
  argument was removed (limited impact).
@ysbaddaden
Copy link
Contributor Author

Fixed! Let's wait for Travis :-)

@asterite
Copy link
Member

Almost! Just missing crystal tool format

@asterite
Copy link
Member

Ah, never mind. I'll make a release today so i'll merge this and then run the formatter.

@ysbaddaden Thank you so much for this! ❤️

@asterite asterite added this to the 0.20.2 milestone Dec 23, 2016
@asterite asterite merged commit d1159e5 into crystal-lang:master Dec 23, 2016
@ysbaddaden ysbaddaden deleted the std-sockets-refactor branch December 23, 2016 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UNIXSocket does not work when socket type is DGRAM
3 participants