I have a Python server communicating with a C++ client using a unix datagram socket connection. The following code set-ups a socket, and then sends and receives one message from the client. This script works in python 2.7, however, when testing it in python 3, the call to recv() times out waiting for messages from the client. The client, however, does receive messages from the server without issue. I've tested this in two separate machines using both 3.5.2 and 3.7.1 with the same results.
UPDATE: I added an ioloop (wrapper for asyncio event loop) to create a callback system for when the messages on the socket are ready. Messages from client appear when the server is run with Python 2.7, but using Python 3.7.1 nothing is ever received.
C++ Client Source:
// Socket Client
#include <stdio.h>
#include <stdlib.h>
#include <cerrno>
#include <vector>
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <cstdarg>
using namespace std;
int sock;
bool await_connection = true;
vector<sockaddr_un> dest_addrs;
int max_msg_size = 2048;
string msg_buf;
string sock_name = "test.sock";
void receive_control_message()
{
char buf[4096]; // Should be enough for client->server messages
sockaddr_un srcaddr;
socklen_t srcaddr_len;
srcaddr_len = sizeof(srcaddr);
int len = recvfrom(sock, buf, sizeof(buf),
0, (sockaddr *) &srcaddr, &srcaddr_len);
if (len == -1)
{
fprintf(stderr,"Socket read error: %s\n", strerror(errno));
exit(-1);
}
printf("sockaddr_un sun_path: '%s'\n", srcaddr.sun_path);
dest_addrs.push_back(srcaddr);
string data(buf, len);
fprintf(stderr, "websocket: Received control message in %d byte.\n", (int) data.size());
printf("%s\n", data.c_str());
}
void finish_message()
{
if (msg_buf.size() == 0)
return;
const int initial_buf_size = msg_buf.size();
fprintf(stderr, "websocket: About to send %d bytes.\n", initial_buf_size);
if (sock_name.empty())
{
msg_buf.clear();
return;
}
msg_buf.append("\n");
const char* fragment_start = msg_buf.data();
const char* data_end = msg_buf.data() + msg_buf.size();
int fragments = 0;
while (fragment_start < data_end)
{
int fragment_size = data_end - fragment_start;
if (fragment_size > max_msg_size)
fragment_size = max_msg_size;
fragments++;
for (unsigned int i = 0; i < dest_addrs.size(); ++i)
{
int retries = 30;
ssize_t sent = 0;
while (sent < fragment_size)
{
ssize_t retval = sendto(sock, fragment_start + sent,
fragment_size - sent, 0, (sockaddr*) &dest_addrs[i],
sizeof(sockaddr_un));
fprintf(stderr, " trying to send fragment to client %d...\n", i);
if (retval <= 0)
{
const char *errmsg = retval == 0 ? "No bytes sent"
: strerror(errno);
if (--retries <= 0)
{
fprintf(stderr,"Socket write error: %s\n", errmsg );
exit(-1);
}
if (retval == 0 || errno == ENOBUFS || errno == EWOULDBLOCK
|| errno == EINTR || errno == EAGAIN)
{
// Wait for half a second at first (up to five), then
// try again.
const int sleep_time = retries > 25 ? 2 * 1000
: retries > 10 ? 500 * 1000
: 5000 * 1000;
fprintf(stderr, "failed (%s), sleeping for %dms.\n",
errmsg, sleep_time / 1000);
usleep(sleep_time);
}
else if (errno == ECONNREFUSED || errno == ENOENT)
{
// the other side is dead
fprintf(stderr, "failed (%s), breaking.\n", errmsg);
dest_addrs.erase(dest_addrs.begin() + i);
i--;
break;
}
else
{
fprintf(stderr,"Socket write error: %s\n", errmsg);
}
}
else
{
fprintf(stderr, "fragment size %d sent.\n", fragment_size);
sent += retval;
}
}
}
fragment_start += fragment_size;
}
msg_buf.clear();
fprintf(stderr, "websocket: Sent %d bytes in %d fragments.\n", initial_buf_size, fragments);
}
void send_message(const char *format, ...)
{
char buf[2048];
int len;
va_list argp;
va_start(argp, format);
if ((len = vsnprintf(buf, sizeof(buf), format, argp)) >= (int)sizeof(buf)
|| len == -1)
{
if (len == -1)
{
fprintf(stderr, "Webtiles message format error! (%s)", format);
exit(-1);
}
else
{
fprintf(stderr, "Webtiles message too long! (%d)", len);
exit(-1);
}
}
va_end(argp);
msg_buf.append(buf);
finish_message();
}
int main()
{
if (sock_name.empty()) return 0;
sock = socket(PF_UNIX, SOCK_DGRAM, 0);
if (sock < 0)
{
printf("Can't open the webtiles socket!\n");
exit(-1);
}
sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, sock_name.c_str());
if (::bind(sock, (sockaddr*) &addr, sizeof(sockaddr_un)))
{
printf("Can't bind the webtiles socket!\n");
exit(-1);
}
int bufsize = 64 * 1024;
if (setsockopt(sock, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(bufsize)))
{
printf("Can't set buffer size!\n");
exit(-1);
}
// Need small maximum message size to avoid crashes in OS X
max_msg_size = 2048;
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) < 0)
{
printf("Can't set send timeout!\n");
exit(-1);
}
printf("Awaiting connection...\n");
if (await_connection)
{
while (dest_addrs.size() == 0) receive_control_message();
}
send_message("{\"foo\":\"grapes\",\"boo\":\"chicken\"}");
send_message("*{\"msg\":\"flush_messages\"}");
return 0;
}
Server Script
# Socket Server
import socket
import json
from datetime import datetime, timedelta
import warnings
import os,sys
from tornado.ioloop import IOLoop
crawl_socket = None
crawl_socketpath = 'test.sock'
socketpath = 'crawl_socket'
msg_buffer = None
ioloop = IOLoop.instance()
def json_encode(value):
return json.dumps(value).replace("</", "<\\/")
def close():
global crawl_socket
if crawl_socket:
print ("Closing socket...")
crawl_socket.close()
os.unlink(socketpath)
crawl_socket = None
def message_callback(data):
if len(data) > 0 and not data.startswith("*"):
print(json.loads(data))
elif data.startswith("*"):
print(json.loads(data[1:]))
def handle_data(data):
global msg_buffer
if msg_buffer is not None:
data = msg_buffer + data
if data[-1] != "\n":
# All messages from crawl end with \n.
# If this one doesn't, it's fragmented.
msg_buffer = data
else:
msg_buffer = None
message_callback(data)
def handle_read(fd, events):
if events & ioloop.READ:
data = crawl_socket.recv(128 * 1024, socket.MSG_DONTWAIT)
handle_data(data)
if events & ioloop.ERROR:
pass
try:
os.unlink(socketpath)
except OSError:
if os.path.exists(socketpath):
raise
if os.path.exists(crawl_socketpath) and not os.path.exists(socketpath):
crawl_socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
crawl_socket.settimeout(10)
crawl_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
if (crawl_socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF) < 2048):
crawl_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF, 2048)
if (crawl_socket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF) < 212992):
crawl_socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 212992)
msg = json_encode({ "msg": "attach", "primary": True })
with warnings.catch_warnings():
warnings.simplefilter("ignore")
crawl_socket.bind(socketpath)
try:
crawl_socket.sendto(msg.encode('utf-8'), crawl_socketpath)
except socket.timeout:
print("ERROR: in send_message() - Game socket send timeout")
close()
sys.exit(-1)
ioloop.add_handler(crawl_socket.fileno(),
handle_read,
ioloop.ERROR | ioloop.READ)
ioloop.start()
else:
print('%s does not exist' % crawl_socketpath)