authorgravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2020-11-16 10:19:00+01:00
committergravatar for thatlemon@gmail.comLemonBoy <thatlemon@gmail.com> 2021-01-11 21:43:09+01:00
log89d6317b93bbe5aac00c6af5833b3e74211ab37f
tree4cffb9731dc8522e46c95c58813a94af66187ae6
parentcc2981edfc4c0e36d3ce6d564f7a36caae9b61b7

std: Decouple network streams from fs.File

The overlap between files and sockets is minimal and lumping them together means supporting only a small subset of the functionalities provided by the OS. Moreover the socket and file handles are not always interchangeable: on Windows one should use Winsock's close() call rather than the one used for common files.

2 files changed, 59 insertions(+), 9 deletions(-)

lib/std/net.zig+57-7
...@@ -10,6 +10,7 @@ const net = @This();...@@ -10,6 +10,7 @@ const net = @This();
10const mem = std.mem;10const mem = std.mem;
11const os = std.os;11const os = std.os;
12const fs = std.fs;12const fs = std.fs;
13const io = std.io;
1314
14pub const has_unix_sockets = @hasDecl(os, "sockaddr_un");15pub const has_unix_sockets = @hasDecl(os, "sockaddr_un");
1516
...@@ -596,7 +597,7 @@ pub const Ip6Address = extern struct {...@@ -596,7 +597,7 @@ pub const Ip6Address = extern struct {
596 }597 }
597};598};
598599
599pub fn connectUnixSocket(path: []const u8) !fs.File {600pub fn connectUnixSocket(path: []const u8) !Stream {
600 const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;601 const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
601 const sockfd = try os.socket(602 const sockfd = try os.socket(
602 os.AF_UNIX,603 os.AF_UNIX,
...@@ -614,7 +615,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {...@@ -614,7 +615,7 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
614 try os.connect(sockfd, &addr.any, addr.getOsSockLen());615 try os.connect(sockfd, &addr.any, addr.getOsSockLen());
615 }616 }
616617
617 return fs.File{618 return Stream{
618 .handle = sockfd,619 .handle = sockfd,
619 };620 };
620}621}
...@@ -648,7 +649,7 @@ pub const AddressList = struct {...@@ -648,7 +649,7 @@ pub const AddressList = struct {
648};649};
649650
650/// All memory allocated with `allocator` will be freed before this function returns.651/// All memory allocated with `allocator` will be freed before this function returns.
651pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) !fs.File {652pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16) !Stream {
652 const list = try getAddressList(allocator, name, port);653 const list = try getAddressList(allocator, name, port);
653 defer list.deinit();654 defer list.deinit();
654655
...@@ -665,7 +666,7 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16)...@@ -665,7 +666,7 @@ pub fn tcpConnectToHost(allocator: *mem.Allocator, name: []const u8, port: u16)
665 return std.os.ConnectError.ConnectionRefused;666 return std.os.ConnectError.ConnectionRefused;
666}667}
667668
668pub fn tcpConnectToAddress(address: Address) !fs.File {669pub fn tcpConnectToAddress(address: Address) !Stream {
669 const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;670 const nonblock = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
670 const sock_flags = os.SOCK_STREAM | nonblock |671 const sock_flags = os.SOCK_STREAM | nonblock |
671 (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC);672 (if (builtin.os.tag == .windows) 0 else os.SOCK_CLOEXEC);
...@@ -679,7 +680,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {...@@ -679,7 +680,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
679 try os.connect(sockfd, &address.any, address.getOsSockLen());680 try os.connect(sockfd, &address.any, address.getOsSockLen());
680 }681 }
681682
682 return fs.File{ .handle = sockfd };683 return Stream{ .handle = sockfd };
683}684}
684685
685/// Call `AddressList.deinit` on the result.686/// Call `AddressList.deinit` on the result.
...@@ -1580,6 +1581,55 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)...@@ -1580,6 +1581,55 @@ fn dnsParseCallback(ctx: dpc_ctx, rr: u8, data: []const u8, packet: []const u8)
1580 }1581 }
1581}1582}
15821583
1584pub const Stream = struct {
1585 // Underlying socket descriptor.
1586 // Note that on some platforms this may not be interchangeable with a
1587 // regular files descriptor.
1588 handle: os.socket_t,
1589
1590 pub fn close(self: Stream) void {
1591 os.closeSocket(self.handle);
1592 }
1593
1594 pub const ReadError = os.ReadError;
1595 pub const WriteError = os.WriteError;
1596
1597 pub const Reader = io.Reader(Stream, ReadError, read);
1598 pub const Writer = io.Writer(Stream, WriteError, write);
1599
1600 pub fn reader(self: Stream) Reader {
1601 return .{ .context = self };
1602 }
1603
1604 pub fn writer(self: Stream) Writer {
1605 return .{ .context = self };
1606 }
1607
1608 pub fn read(self: Stream, buffer: []u8) ReadError!usize {
1609 if (std.Target.current.os.tag == .windows) {
1610 return os.windows.ReadFile(self.handle, buffer, null, io.default_mode);
1611 }
1612
1613 if (std.io.is_async) {
1614 return std.event.Loop.instance.?.read(self.handle, buffer, false);
1615 } else {
1616 return os.read(self.handle, buffer);
1617 }
1618 }
1619
1620 pub fn write(self: Stream, buffer: []const u8) WriteError!usize {
1621 if (std.Target.current.os.tag == .windows) {
1622 return os.windows.WriteFile(self.handle, buffer, null, io.default_mode);
1623 }
1624
1625 if (std.io.is_async) {
1626 return std.event.Loop.instance.?.write(self.handle, buffer, false);
1627 } else {
1628 return os.write(self.handle, buffer);
1629 }
1630 }
1631};
1632
1583pub const StreamServer = struct {1633pub const StreamServer = struct {
1584 /// Copied from `Options` on `init`.1634 /// Copied from `Options` on `init`.
1585 kernel_backlog: u31,1635 kernel_backlog: u31,
...@@ -1686,7 +1736,7 @@ pub const StreamServer = struct {...@@ -1686,7 +1736,7 @@ pub const StreamServer = struct {
1686 } || os.UnexpectedError;1736 } || os.UnexpectedError;
16871737
1688 pub const Connection = struct {1738 pub const Connection = struct {
1689 file: fs.File,1739 stream: Stream,
1690 address: Address,1740 address: Address,
1691 };1741 };
16921742
...@@ -1705,7 +1755,7 @@ pub const StreamServer = struct {...@@ -1705,7 +1755,7 @@ pub const StreamServer = struct {
17051755
1706 if (accept_result) |fd| {1756 if (accept_result) |fd| {
1707 return Connection{1757 return Connection{
1708 .file = fs.File{ .handle = fd },1758 .stream = Stream{ .handle = fd },
1709 .address = accepted_addr,1759 .address = accepted_addr,
1710 };1760 };
1711 } else |err| switch (err) {1761 } else |err| switch (err) {
lib/std/net/test.zig+2-2
...@@ -166,7 +166,7 @@ test "listen on a port, send bytes, receive bytes" {...@@ -166,7 +166,7 @@ test "listen on a port, send bytes, receive bytes" {
166166
167 var client = try server.accept();167 var client = try server.accept();
168 var buf: [16]u8 = undefined;168 var buf: [16]u8 = undefined;
169 const n = try client.file.reader().read(&buf);169 const n = try client.stream.reader().read(&buf);
170170
171 testing.expectEqual(@as(usize, 12), n);171 testing.expectEqual(@as(usize, 12), n);
172 testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);172 testing.expectEqualSlices(u8, "Hello world!", buf[0..n]);
...@@ -249,6 +249,6 @@ fn testServer(server: *net.StreamServer) anyerror!void {...@@ -249,6 +249,6 @@ fn testServer(server: *net.StreamServer) anyerror!void {
249249
250 var client = try server.accept();250 var client = try server.accept();
251251
252 const stream = client.file.writer();252 const stream = client.stream.writer();
253 try stream.print("hello from server\n", .{});253 try stream.print("hello from server\n", .{});
254}254}