unixdomaindatagramsocket.cpp 3.69 KB
#include "unixdomaindatagramsocket.h"

#include <QVarLengthArray>

#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>

UnixDomainDatagramSocket::UnixDomainDatagramSocket(QObject *parent)
    : QObject(parent),
      sockfd(-1), notifier(Q_NULLPTR)
{

}

UnixDomainDatagramSocket::~UnixDomainDatagramSocket()
{
    if (sockfd >= 0)
        close(sockfd);

    if (!boundPath.isEmpty())
        unlink(boundPath.toLocal8Bit().data());
}

bool UnixDomainDatagramSocket::open()
{
    if (sockfd >= 0)
        return false;

    sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if (sockfd < 0)
        return false;

    notifier = new QSocketNotifier(sockfd, QSocketNotifier::Read, this);
    connect(notifier, SIGNAL(activated(int)), SLOT(emitReadyRead()));

    return true;
}

bool UnixDomainDatagramSocket::bind(const QString &path)
{
    return bind(path.toLocal8Bit().data());
}

bool UnixDomainDatagramSocket::bind(const char *path)
{
    if (sockfd < 0 && !open())
        return false;

    unlink(path);

    struct sockaddr_un address;
    address.sun_family = AF_UNIX;
    strncpy(address.sun_path, path, sizeof(address.sun_path));

    int ret = ::bind(sockfd, reinterpret_cast<struct sockaddr *>(&address), sizeof(address));
    if (ret == -1)
        return false;

    return true;
}

bool UnixDomainDatagramSocket::hasPendingDatagrams()
{
    ssize_t readBytes;
    do
    {
        char c;
        readBytes = ::recv(sockfd, &c, 1, MSG_DONTWAIT | MSG_PEEK);
    }
    while (readBytes == -1 && errno == EINTR);

    return (readBytes != -1) || errno == EMSGSIZE;
}

qint64 UnixDomainDatagramSocket::pendingDatagramSize()
{
    QVarLengthArray<char, 8192> udpMessagePeekBuffer(8192);
    ssize_t recvResult = -1;

    for (;;)
    {
        recvResult = ::recv(sockfd, udpMessagePeekBuffer.data(),
            size_t(udpMessagePeekBuffer.size()), MSG_DONTWAIT | MSG_PEEK);
        if (recvResult == -1 && errno == EINTR)
            continue;

        if (recvResult != ssize_t(udpMessagePeekBuffer.size()))
            break;

        udpMessagePeekBuffer.resize(udpMessagePeekBuffer.size() * 2);
    }

    return qint64(recvResult);
}

qint64 UnixDomainDatagramSocket::readDatagram(QByteArray *datagram, QString *path)
{
    return readDatagram(datagram->data(), datagram->size(), path);
}

qint64 UnixDomainDatagramSocket::readDatagram(char *data, qint64 maxSize, QString *path)
{
    if (!notifier->isEnabled())
        notifier->setEnabled(true);

    struct sockaddr_un address;
    socklen_t length;

    ssize_t recvResult;
    do
    {
        recvResult = ::recvfrom(sockfd, data, size_t(maxSize), 0, reinterpret_cast<struct sockaddr *>(&address), &length);
    }
    while (recvResult == -1 && errno == EINTR);

    if (recvResult == -1)
        return -1;

    if (path)
        *path = QString::fromLocal8Bit(address.sun_path);

    return recvResult;
}

qint64 UnixDomainDatagramSocket::writeDatagram(const QByteArray &datagram, const QString &path)
{
    return writeDatagram(datagram.data(), datagram.size(), path);
}

qint64 UnixDomainDatagramSocket::writeDatagram(const char *data, qint64 size, const QString &path)
{
    return writeDatagram(data, size, path.toLocal8Bit().data());
}

qint64 UnixDomainDatagramSocket::writeDatagram(const char *data, qint64 size, const char *path)
{
    struct sockaddr_un address;
    address.sun_family = AF_UNIX;
    strncpy(address.sun_path, path, sizeof(address.sun_path));

    ssize_t sendResult = ::sendto(sockfd, data, size_t(size), 0, reinterpret_cast<struct sockaddr *>(&address), sizeof(address));
    return qint64(sendResult);
}

void UnixDomainDatagramSocket::emitReadyRead()
{
    notifier->setEnabled(false);
    emit readyRead();
}