python-3.xpyqtpyqt5qtcpsocketqtcpserver

PyQt Subclassing QTcpServer and QTcpSocket


I am trying to implement a custom TCP Socket and Server using the PyQt5.QtNetwork.QTcpSocket and PyQt5.QtNetwork.QTcpServer as baseclasses. The documentation states one has to override QTcpServer.incomingConnection to return custom Socket objects. This seems to work fine in principle, but I keep stumbling over some odd behavior. Whenn calling QTcpServer.nextPendingConnection, the method does indeed seem to return the expected MyNewSocket instance, however when one accesses any attribute or method of the new socket, the interpreter first returns said attribute / method, but then goes on to inform you that AttributeError: 'NoneType' object has no attribute 'anyAttribute' which I can find no reasonable explanation for so far. Here is a MWE that shows the behavior. I am using PyQt5 v5.15.

tcpmodule.py:

from PyQt5.QtNetwork import QTcpSockert, QTcpServer

class MyNewSocket(QTcpSocket):
    """do some additional stuff to the
       original QTcpSocket stuff"""
    pass

class MyNewServer(QTcpServer):
    def incomingConnection(self, handle):
        """Returns a MyNewSocket instance instead of
           original QTcpSocket instance"""
        socket = MyNewSocket(self)
        socket.setSocketDescriptor(handle)
        self.addPendingConnection(socket)
        self.newConnection.emit()

server.py

from tcpmodule import MyNewServer
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtNetwork import QHostAddress

app = QApplication([])
win = QMainWindow()
server = MyNewServer(win)
server.listen(QHostAddress.SpecialAddress.LocalHost, 9999)

def newConnection():
    socket = server.nextPendingConnection()
    print("Acces any Attribute ", socket.connected)
    print("Socket is ", socket, " Socket Type is", type(socket))

server.newConnection.connect(newConnection)

win.show()
app.exec()

client.py

#!/usr/bin/env python3
from tcpmodule import MyNewSocket
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtNetwork import QHostAddress

app = QApplication([])
win = QMainWindow()

socket = MyNewSocket(win)
socket.connectToHost(QHostAddress.SpecialAddress.LocalHost, 9999)

def connected():
    socket.write(b"Hello, World!")

socket.connected.connect(connected)

win.show()
app.exec()

After running the server.py and subsequent client.py connection, I get the following output


Acces any Attribute  <bound PYQT_SIGNAL connected of MyNewSocket object at 0x7f4be28597e0>                                                                                        
Socket is  <tcpmodule.MyNewSocket object at 0x7f4be28597e0>  Socket Type is <class 'tcpmodule.MyNewSocket'>                                                                     
Traceback (most recent call last):                                                       
  File "./server.py", line 13, in newConnection        
    print("Acces any Attribute ", socket.connected)                                      
AttributeError: 'NoneType' object has no attribute 'connected' 

Can anyone please tell me what is going on?!


Solution

  • According to the documentation, it seems that incomingConnection() emits the newConnection() signal, but, in fact it does not.

    By looking at the sources, we see that it just creates the socket, sets its descriptor and appends it to the pending connections:

    void QTcpServer::incomingConnection(qintptr socketDescriptor)
    {
    #if defined (QTCPSERVER_DEBUG)
        qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor);
    #endif
    
        QTcpSocket *socket = new QTcpSocket(this);
        socket->setSocketDescriptor(socketDescriptor);
        addPendingConnection(socket);
    }
    

    In fact, the signal is only emitted by the private function readNotification(), which calls incomingConnection() and finally emits the signal.

    So, you don't have to emit it on your own, because in that case the second time you call nextPendingConnection() you'll get a nullptr (None) as the socket has been already read in the previous call, and there are no more connections available.