mirror of
https://github.com/ultimatepp/ultimatepp.git
synced 2026-05-15 06:05:58 -06:00
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:
parent
5124794175
commit
e038550cb2
6 changed files with 167 additions and 122 deletions
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue