Core/Socket: Unix domain socket (AF_UNIX) support for Windows (#328)

Refactor UnixSocket.cpp with error handling

Updated UnixSocket.cpp to include error handling and platform-specific path definitions.

UnixSocketClient: Update socket path for cross-platform compatibility

UnixSocketServer: Update socket path for Windows and Unix platforms

Core: UnixSocket example code, socket path fixed

autotest/UnixSocket: path correction and unlink.
This commit is contained in:
İsmail Yılmaz 2026-01-17 18:56:18 +00:00 committed by GitHub
parent 5124794175
commit e038550cb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 167 additions and 122 deletions

View file

@ -2,50 +2,63 @@
using namespace Upp; using namespace Upp;
String GetSocketPath()
{
String temp;
#ifdef PLATFORM_WIN32
temp = GetEnv("TEMP");
#else
temp = GetTempPath();
#endif
return AppendFileName(temp, "upp-unixsocket.socket");
}
CONSOLE_APP_MAIN CONSOLE_APP_MAIN
{ {
#ifdef PLATFORM_POSIX
StdLogSetup(LOG_COUT|LOG_FILE); StdLogSetup(LOG_COUT|LOG_FILE);
String path = Format("/tmp/upp-unixsocket-test-%d", getpid()); String path = GetSocketPath();
Socket server, client;
// Test server listen
if(!server.ListenFileSystem(path)) {
LOG("Server listen failed: " << server.GetErrorDesc());
Exit(1);
}
// Test client connect
if(!client.ConnectFileSystem(path)) {
LOG("Client connect failed: " << client.GetErrorDesc());
Exit(1);
}
// Test data exchange
String test_data = "Hello, world!";
client.Put(test_data + "\n");
Socket accepted;
if(!accepted.Accept(server)) {
LOG("Accept failed: " << accepted.GetErrorDesc());
Exit(1);
}
String received = accepted.GetLine();
DUMP(received);
ASSERT(received == test_data);
// Test peer PID (on supported platforms)
int pid = accepted.GetPeerPid();
DUMP(pid);
if(pid != -1)
ASSERT(pid == getpid()); // Should be our own process in this test
LOG("=========== OK"); try {
Socket server, client;
#endif DeleteFile(path); // "unlink" existing FS socket if possible
}
// Test server listen
if(!server.ListenFileSystem(path, 5, false)) {
throw Exc("Server listen failed: " << server.GetErrorDesc());
}
// Test client connect
if(!client.ConnectFileSystem(path)) {
throw Exc("Client connect failed: " << client.GetErrorDesc());
}
// Test data exchange
String test_data = "Hello, world!";
client.Put(test_data + "\n");
Socket accepted;
if(!accepted.Accept(server)) {
throw Exc("Accept failed: " << accepted.GetErrorDesc());
}
String received = accepted.GetLine();
DUMP(received);
ASSERT(received == test_data);
// Test peer PID (on supported platforms)
int pid = accepted.GetPeerPid();
DUMP(pid);
if(pid != -1)
ASSERT(pid == getpid()); // Should be our own process in this test
LOG("=========== OK");
}
catch(const Exc& e)
{
LOG(e);
SetExitCode(1);
}
}

View file

@ -3,26 +3,31 @@
using namespace Upp; using namespace Upp;
// Start reference/UnixSocketServer before starting this program // Start reference/UnixSocketServer before starting this program
String GetSocketPath()
{
String temp;
#ifdef PLATFORM_WIN32
temp = GetEnv("TEMP");
#else
temp = GetTempPath();
#endif
return AppendFileName(temp, "upp-unixsocket.socket");
}
String Request(const String r)
{
Socket s;
if(!s.ConnectFileSystem(GetSocketPath())) {
Cout() << "Unable to connect to server!\n";
SetExitCode(1);
return String();
}
s.Put(r + '\n');
return s.GetLine();
}
CONSOLE_APP_MAIN CONSOLE_APP_MAIN
{ {
#ifdef PLATFORM_POSIX
auto Request = [](const String& r)
{
Socket s;
if(!s.ConnectFileSystem("/tmp/upp-unixsocket.sock")) {
Cout() << "Unable to connect to server!\n";
SetExitCode(1);
return String();
}
s.Put(r + '\n');
return s.GetLine();
};
Cout() << Request("time") << '\n'; Cout() << Request("time") << '\n';
Cout() << Request("33") << '\n'; Cout() << Request("33") << '\n';
#else
Cout() << "This example requires a POSIX compliant operating system...\r\n"
SetExitCode(1);
#endif
} }

View file

@ -2,13 +2,23 @@
using namespace Upp; using namespace Upp;
String GetSocketPath()
{
String temp;
#ifdef PLATFORM_WIN32
temp = GetEnv("TEMP");
#else
temp = GetTempPath();
#endif
return AppendFileName(temp, "upp-unixsocket.socket");
}
CONSOLE_APP_MAIN CONSOLE_APP_MAIN
{ {
#ifdef PLATFORM_POSIX
const String& path = "/tmp/upp-unixsocket.sock";
Socket server; Socket server;
if(!server.ListenFileSystem(path, 5)) { String path = GetSocketPath();
DeleteFile(path); // "unlink" existing file system socket
if(!server.ListenFileSystem(path, 5, false)) { // Reuse option is not available on Windows
Cout() << "Unable to initialize server socket!\n"; Cout() << "Unable to initialize server socket!\n";
SetExitCode(1); SetExitCode(1);
return; return;
@ -26,8 +36,4 @@ CONSOLE_APP_MAIN
s.Put("\n"); s.Put("\n");
} }
} }
#else
Cout() << "This example requires a POSIX compliant operating system...\r\n"
SetExitCode(1);
#endif
} }

View file

@ -171,11 +171,10 @@ class Socket : NoCopy {
static int GetErrorCode(); static int GetErrorCode();
static void Init(); static void Init();
#ifdef PLATFORM_POSIX // Unix domain socket support // Unix domain socket support
bool NixConnect(const String& path, bool abstract); bool NixConnect(const String& path, bool abstract);
bool NixListen(const String& path, int n, bool reuse, bool abstract); bool NixListen(const String& path, int n, bool reuse, bool abstract);
#endif
Socket(const Socket&); Socket(const Socket&);
@ -217,13 +216,11 @@ public:
void Close(); void Close();
void Shutdown(); void Shutdown();
#ifdef PLATFORM_POSIX
int GetPeerPid() const; int GetPeerPid() const;
bool ConnectFileSystem(const String& path); bool ConnectFileSystem(const String& path);
bool ConnectAbstract(const String& path); bool ConnectAbstract(const String& path);
bool ListenFileSystem(const String& path, int listen_count = 5, bool reuse = true); bool ListenFileSystem(const String& path, int listen_count = 5, bool reuse = true);
bool ListenAbstract(const String& path, int listen_count = 5, bool reuse = true); bool ListenAbstract(const String& path, int listen_count = 5, bool reuse = true);
#endif
void NoDelay(); void NoDelay();
void Linger(int msecs); void Linger(int msecs);

View file

@ -3,7 +3,11 @@
#ifdef PLATFORM_WIN32 #ifdef PLATFORM_WIN32
#include <winsock2.h> #include <winsock2.h>
#include <ws2tcpip.h> #include <ws2tcpip.h>
#ifdef AF_UNIX // Unix domain (AF_UNIX) socket support, Windows 10+
#include <afunix.h>
#endif #endif
#endif
#ifdef PLATFORM_POSIX #ifdef PLATFORM_POSIX
#include <arpa/inet.h> #include <arpa/inet.h>
@ -984,40 +988,43 @@ void Socket::Clear()
Reset(); Reset();
} }
#ifdef PLATFORM_POSIX static socklen_t sSetUnixSockType(Socket& s, const String& path, sockaddr_un& addr, bool abstract)
static bool sSetUnixSockType(Socket& s, const String& path, sockaddr_un& addr, bool abstract)
{ {
memset(&addr, 0, sizeof(addr)); #ifdef AF_UNIX
addr.sun_family = AF_UNIX; memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
#ifndef PLATFORM_LINUX #ifndef PLATFORM_LINUX
if(abstract) { if(abstract) {
s.SetSockError("SetUnixSockType", s.SetSockError("SetUnixSockType",
-1, "Abstract socket is not supported on this platform"); -1, "Abstract socket is not supported on this platform");
return false; return 0;
} }
#endif #endif
const int len = path.GetLength();
if(abstract) { if(abstract) {
addr.sun_path[0] = '\0'; if(len <= 0 || len > int(sizeof(addr.sun_path) - 1)) {
if(path.GetLength() > 0) { s.SetSockError("SetUnixSockType", -1, "Abstract socket name too long");
ASSERT(path.GetLength() < sizeof(addr.sun_path) - 1); return 0;
strncpy(addr.sun_path + 1, ~path, sizeof(addr.sun_path) - 2); }
return true; addr.sun_path[0] = '\0';
} memcpy(addr.sun_path + 1, ~path, len);
} return offsetof(sockaddr_un, sun_path) + 1 + len;
else { }
if(path.GetLength() > 0) { else {
ASSERT(path.GetLength() < sizeof(addr.sun_path)); if(len <= 0 || len >= int(sizeof(addr.sun_path))) {
strncpy(addr.sun_path, ~path, sizeof(addr.sun_path) - 1); s.SetSockError("SetUnixSockType", -1, "Unix socket path too long");
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0'; return 0;
return true; }
} memcpy(addr.sun_path, ~path, len);
} addr.sun_path[len] = '\0';
s.SetSockError("SetUnixSockType", return offsetof(sockaddr_un, sun_path) + len + 1;
-1, "Failed to set unix domain socket type"); }
return false; #else
s.SetSockError("SetUnixSockType",
-1, "Unix domain socket is not supported on this platform");
#endif
return 0;
} }
int Socket::GetPeerPid() const int Socket::GetPeerPid() const
@ -1048,21 +1055,27 @@ bool Socket::NixConnect(const String& path, bool abstract)
Init(); Init();
Reset(); Reset();
#ifdef AF_UNIX
if(!Open(AF_UNIX, SOCK_STREAM, 0)) if(!Open(AF_UNIX, SOCK_STREAM, 0))
return false; return false;
struct sockaddr_un addr; struct sockaddr_un addr;
if(!sSetUnixSockType(*this, path, addr, abstract)) socklen_t addrlen = 0;
if((addrlen = sSetUnixSockType(*this, path, addr, abstract)) == 0)
return false; return false;
if(connect(socket, (sockaddr *) &addr, sizeof(addr)) == 0 || if(connect(socket, (sockaddr *) &addr, addrlen) == 0 ||
GetErrorCode() == EINPROGRESS || GetErrorCode() == EWOULDBLOCK) { findarg(GetErrorCode(), SOCKERR(EINPROGRESS), SOCKERR(EWOULDBLOCK)) >= 0) {
mode = Socket::CONNECT; mode = Socket::CONNECT;
return true; return true;
} }
SetSockError("connect", -1, strerror(GetErrorCode())); SetSockError("connect", -1, strerror(GetErrorCode()));
Close(); Close();
#else
SetSockError("NixConnect",
-1, "Unix domain socket is not supported on this platform");
#endif
return false; return false;
} }
@ -1082,11 +1095,13 @@ bool Socket::NixListen(const String& path, int n, bool reuse, bool abstract)
Init(); Init();
Reset(); Reset();
#ifdef AF_UNIX
if(!Open(AF_UNIX, SOCK_STREAM, 0)) if(!Open(AF_UNIX, SOCK_STREAM, 0))
return false; return false;
struct sockaddr_un addr; struct sockaddr_un addr;
if(!sSetUnixSockType(*this, path, addr, abstract)) socklen_t addrlen = 0;
if((addrlen = sSetUnixSockType(*this, path, addr, abstract)) == 0)
return false; return false;
if(reuse) { if(reuse) {
@ -1094,7 +1109,7 @@ bool Socket::NixListen(const String& path, int n, bool reuse, bool abstract)
setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char *) &optval, sizeof(optval)); setsockopt(socket, SOL_SOCKET, SO_REUSEADDR, (const char *) &optval, sizeof(optval));
} }
if(bind(socket, (const sockaddr *) &addr, sizeof(addr))) { if(bind(socket, (const sockaddr *) &addr, addrlen)) {
SetSockError(Format("bind(path=%s)", path)); SetSockError(Format("bind(path=%s)", path));
return false; return false;
} }
@ -1105,6 +1120,12 @@ bool Socket::NixListen(const String& path, int n, bool reuse, bool abstract)
} }
return true; return true;
#else
SetSockError("NixListen",
-1, "Unix domain socket is not supported on this platform");
return false;
#endif
} }
bool Socket::ListenFileSystem(const String& path, int listen_count, bool reuse) bool Socket::ListenFileSystem(const String& path, int listen_count, bool reuse)
@ -1117,9 +1138,6 @@ bool Socket::ListenAbstract(const String& path, int listen_count, bool reuse)
return NixListen(path, listen_count, reuse, true); return NixListen(path, listen_count, reuse, true);
} }
#endif
int SocketWaitEvent::Wait(int timeout) int SocketWaitEvent::Wait(int timeout)
{ {
FD_ZERO(read); FD_ZERO(read);

View file

@ -34,11 +34,10 @@ tring]_[* GetHostName]()&]
[s4; &] [s4; &]
[s5;:Socket`:`:GetPeerPid`(`)const: [@(0.0.255) int] [* GetPeerPid]() [s5;:Socket`:`:GetPeerPid`(`)const: [@(0.0.255) int] [* GetPeerPid]()
[@(0.0.255) const]&] [@(0.0.255) const]&]
[s6; POSIX only&]
[s2;%% Returns the process ID (pid) of the peer on success, `-1 on [s2;%% Returns the process ID (pid) of the peer on success, `-1 on
failure. On non`-blocking mode, make sure that socket is actually failure. On non`-blocking mode, make sure that socket is actually
connected or accepted. This is only available on unix domain connected or accepted. This is only available on unix domain
(local) sockets.&] (local) sockets and will fail with return code `-1 on Windows.&]
[s3; &] [s3; &]
[s4; &] [s4; &]
[s5;:Socket`:`:GetDone`(`)const: [@(0.0.255) int]_[* GetDone]()_[@(0.0.255) const]&] [s5;:Socket`:`:GetDone`(`)const: [@(0.0.255) int]_[* GetDone]()_[@(0.0.255) const]&]
@ -120,8 +119,7 @@ pAddrInfo][@(0.0.255) `&]_[*@3 info])&]
[s3;%% &] [s3;%% &]
[s4; &] [s4; &]
[s5;:Upp`:`:Socket`:`:ConnectFileSystem`(const String`&`): [@(0.0.255) bool] [s5;:Upp`:`:Socket`:`:ConnectFileSystem`(const String`&`): [@(0.0.255) bool]
[* ConnectFileSystem]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 path])&] [* ConnectFileSystem]([@(0.0.255) const] String[@(0.0.255) `&] [@3 path])&]
[s6;%% POSIX only&]
[s2;%% Connects socket to a Unix domain server bound at the given [s2;%% Connects socket to a Unix domain server bound at the given
file system [%-*@3 path]. The path must exist on the file system. file system [%-*@3 path]. The path must exist on the file system.
Returns true if connection is successful (blocking mode) or connection Returns true if connection is successful (blocking mode) or connection
@ -138,8 +136,8 @@ and does not correspond to a file system path. Returns true if
connection is successful (blocking mode) or connection is in connection is successful (blocking mode) or connection is in
progress (non blocking mode). Abstract sockets exist only in progress (non blocking mode). Abstract sockets exist only in
kernel memory and disappear when processes exit. On non`-Linux kernel memory and disappear when processes exit. On non`-Linux
POSIX systems, this function will fail and set the socket into systems, this function will fail and set the socket into error
error state. &] state. &]
[s3; &] [s3; &]
[s4; &] [s4; &]
[s5;:Socket`:`:WaitConnect`(`): [@(0.0.255) bool]_[* WaitConnect]()&] [s5;:Socket`:`:WaitConnect`(`): [@(0.0.255) bool]_[* WaitConnect]()&]
@ -169,15 +167,23 @@ for ipv6`=`=true.&]
[s3;%% &] [s3;%% &]
[s4; &] [s4; &]
[s5;:Upp`:`:Socket`:`:ListenFileSystem`(const String`&`,int`,bool`): [@(0.0.255) bool] [s5;:Upp`:`:Socket`:`:ListenFileSystem`(const String`&`,int`,bool`): [@(0.0.255) bool]
[* ListenFileSystem]([@(0.0.255) const ]String[@(0.0.255) `&] [*@3 path], [* ListenFileSystem(][*@(0.0.255) const ][* String][*@(0.0.255) `&][*
[@(0.0.255) int] [*@3 listen`_count] [@(0.0.255) `=] [@3 5], [@(0.0.255) bool] ][*@3 path][* , ][*@(0.0.255) int][* ][*@3 listen`_count][* ][*@(0.0.255) `=][*
[*@3 reuse] [@(0.0.255) `=] [@(0.0.255) true])&] ][*@3 5][* , ][*@(0.0.255) bool][* ][*@3 reuse][* ][*@(0.0.255) `=][* ][*@(0.0.255) true][* )]&]
[s6; POSIX only&]
[s2;%% Creates a Unix domain server socket bound to the given file [s2;%% Creates a Unix domain server socket bound to the given file
system [%-*@3 path]. [%-*@3 listen`_count] specifies the maximum system [%-*@3 path]. [%-*@3 listen`_count] specifies the maximum
number of pending connections in the queue. [%-*@3 reuse] indicates number of pending connections in the queue. [%-*@3 reuse] indicates
whether the socket should allow reuse of the address if it already whether the socket should allow reuse of the address if it already
exists. returns true if the listen is successful.&] exists. returns true if the listen is successful. &]
[s2;%% &]
[s2;%% Notes:&]
[s2;i150;O0;%% [%-*@3 reuse] option is not supported on Windows and
if specified the listen will fail and set the socket into error
state. &]
[s2;i150;O0;%% Client code is responsible for the socket [%-*@3 path]s
lifetime. For filesystem`-based Unix domain sockets, the pathname
must be unlinked explicitly after shutdown, as it is not removed
automatically when the socket is closed.&]
[s3; &] [s3; &]
[s4; &] [s4; &]
[s5;:Upp`:`:Socket`:`:ListenAbstract`(const String`&`,int`,bool`): [@(0.0.255) bool] [s5;:Upp`:`:Socket`:`:ListenAbstract`(const String`&`,int`,bool`): [@(0.0.255) bool]
@ -191,8 +197,8 @@ not correspond to a file system path). [%-*@3 listen`_count] specifies
the maximum number of pending connections. [%-*@3 reuse] indicates the maximum number of pending connections. [%-*@3 reuse] indicates
whether the abstract socket name can be reused. Abstract sockets whether the abstract socket name can be reused. Abstract sockets
exist only in kernel memory and disappear when processes exit. exist only in kernel memory and disappear when processes exit.
On non`-Linux POSIX systems, this method will fail and set the On non`-Linux systems, this method will fail and set the socket
socket into error state.&] into error state.&]
[s3; &] [s3; &]
[s4;%% &] [s4;%% &]
[s5;:Socket`:`:Accept`(Socket`&`): [@(0.0.255) bool]_[* Accept]([_^topic`:`/`/Core`/src`/TcpSocket`$en`-us`#TcpSocket`:`:class^ S [s5;:Socket`:`:Accept`(Socket`&`): [@(0.0.255) bool]_[* Accept]([_^topic`:`/`/Core`/src`/TcpSocket`$en`-us`#TcpSocket`:`:class^ S