0

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)
Lokno
  • 590
  • 3
  • 15
  • 1
    Your server doesn't seem to have any way to wait for a message to be received. It checks for a message without waiting and, if it doesn't find one already there, gives up immediately. – David Schwartz Mar 22 '19 at 19:02
  • The use case is that both client and server are on the same machine, and the message is sent immediately from the client on as you can see in receive_control_message(). The message sends from the client without issue before the timeout, but the server never sees it. – Lokno Mar 22 '19 at 19:11
  • 1
    None of that guarantees that the message will be ready to be received when the server calls `recv`. Frankly, I'm astounded this ever worked. – David Schwartz Mar 22 '19 at 19:50
  • I have updated the server code such that it waits for messages to be ready using tornado.ioloop. Same problem persists. – Lokno Mar 22 '19 at 21:50

1 Answers1

0

I managed to solve this on my own, so here is the answer for anyone seeking insight on a similar problem in the future.

socket.recv() in Python 2 returns a str object representing the data received.

In Python 3, this has changed, and socket.recv() returns a bytes object.

So the following needed to be added to handle_data above:

if isinstance(data,bytes):
    data = data.decode("utf-8") 
Lokno
  • 590
  • 3
  • 15