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;
String GetSocketPath()
{
String temp;
#ifdef PLATFORM_WIN32
temp = GetEnv("TEMP");
#else
temp = GetTempPath();
#endif
return AppendFileName(temp, "upp-unixsocket.socket");
}
CONSOLE_APP_MAIN
{
#ifdef PLATFORM_POSIX
StdLogSetup(LOG_COUT|LOG_FILE);
String path = Format("/tmp/upp-unixsocket-test-%d", getpid());
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
String path = GetSocketPath();
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;
// 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
{
#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("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;
String GetSocketPath()
{
String temp;
#ifdef PLATFORM_WIN32
temp = GetEnv("TEMP");
#else
temp = GetTempPath();
#endif
return AppendFileName(temp, "upp-unixsocket.socket");
}
CONSOLE_APP_MAIN
{
#ifdef PLATFORM_POSIX
const String& path = "/tmp/upp-unixsocket.sock";
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";
SetExitCode(1);
return;
@ -26,8 +36,4 @@ CONSOLE_APP_MAIN
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 void Init();
#ifdef PLATFORM_POSIX // Unix domain socket support
// Unix domain socket support
bool NixConnect(const String& path, bool abstract);
bool NixListen(const String& path, int n, bool reuse, bool abstract);
#endif
Socket(const Socket&);
@ -217,13 +216,11 @@ public:
void Close();
void Shutdown();
#ifdef PLATFORM_POSIX
int GetPeerPid() const;
bool ConnectFileSystem(const String& path);
bool ConnectAbstract(const String& path);
bool ListenFileSystem(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 Linger(int msecs);

View file

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

View file

@ -34,11 +34,10 @@ tring]_[* GetHostName]()&]
[s4; &]
[s5;:Socket`:`:GetPeerPid`(`)const: [@(0.0.255) int] [* GetPeerPid]()
[@(0.0.255) const]&]
[s6; POSIX only&]
[s2;%% Returns the process ID (pid) of the peer on success, `-1 on
failure. On non`-blocking mode, make sure that socket is actually
connected or accepted. This is only available on unix domain
(local) sockets.&]
(local) sockets and will fail with return code `-1 on Windows.&]
[s3; &]
[s4; &]
[s5;:Socket`:`:GetDone`(`)const: [@(0.0.255) int]_[* GetDone]()_[@(0.0.255) const]&]
@ -120,8 +119,7 @@ pAddrInfo][@(0.0.255) `&]_[*@3 info])&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:Socket`:`:ConnectFileSystem`(const String`&`): [@(0.0.255) bool]
[* ConnectFileSystem]([@(0.0.255) const] String[@(0.0.255) `&] [*@3 path])&]
[s6;%% POSIX only&]
[* ConnectFileSystem]([@(0.0.255) const] String[@(0.0.255) `&] [@3 path])&]
[s2;%% Connects socket to a Unix domain server bound at the given
file system [%-*@3 path]. The path must exist on the file system.
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
progress (non blocking mode). Abstract sockets exist only in
kernel memory and disappear when processes exit. On non`-Linux
POSIX systems, this function will fail and set the socket into
error state. &]
systems, this function will fail and set the socket into error
state. &]
[s3; &]
[s4; &]
[s5;:Socket`:`:WaitConnect`(`): [@(0.0.255) bool]_[* WaitConnect]()&]
@ -169,15 +167,23 @@ for ipv6`=`=true.&]
[s3;%% &]
[s4; &]
[s5;:Upp`:`:Socket`:`:ListenFileSystem`(const String`&`,int`,bool`): [@(0.0.255) bool]
[* ListenFileSystem]([@(0.0.255) const ]String[@(0.0.255) `&] [*@3 path],
[@(0.0.255) int] [*@3 listen`_count] [@(0.0.255) `=] [@3 5], [@(0.0.255) bool]
[*@3 reuse] [@(0.0.255) `=] [@(0.0.255) true])&]
[s6; POSIX only&]
[* ListenFileSystem(][*@(0.0.255) const ][* String][*@(0.0.255) `&][*
][*@3 path][* , ][*@(0.0.255) int][* ][*@3 listen`_count][* ][*@(0.0.255) `=][*
][*@3 5][* , ][*@(0.0.255) bool][* ][*@3 reuse][* ][*@(0.0.255) `=][* ][*@(0.0.255) true][* )]&]
[s2;%% Creates a Unix domain server socket bound to the given file
system [%-*@3 path]. [%-*@3 listen`_count] specifies the maximum
number of pending connections in the queue. [%-*@3 reuse] indicates
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; &]
[s4; &]
[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
whether the abstract socket name can be reused. Abstract sockets
exist only in kernel memory and disappear when processes exit.
On non`-Linux POSIX systems, this method will fail and set the
socket into error state.&]
On non`-Linux systems, this method will fail and set the socket
into error state.&]
[s3; &]
[s4;%% &]
[s5;:Socket`:`:Accept`(Socket`&`): [@(0.0.255) bool]_[* Accept]([_^topic`:`/`/Core`/src`/TcpSocket`$en`-us`#TcpSocket`:`:class^ S