├── 26Oct2011_MarkusGoetz_BasicNetworkingWithQt.pdf ├── 6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf ├── QLocalChat.pro ├── README ├── deployment.pri ├── http_request.txt ├── main.cpp ├── main.qml └── qml.qrc /26Oct2011_MarkusGoetz_BasicNetworkingWithQt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woboq/basic_networking_with_qt/6853b05551e453108c1969dddf6b4e26e9e036fc/26Oct2011_MarkusGoetz_BasicNetworkingWithQt.pdf -------------------------------------------------------------------------------- /6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/woboq/basic_networking_with_qt/6853b05551e453108c1969dddf6b4e26e9e036fc/6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf -------------------------------------------------------------------------------- /QLocalChat.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick network 4 | 5 | SOURCES += main.cpp 6 | 7 | RESOURCES += qml.qrc 8 | 9 | # Additional import path used to resolve QML modules in Qt Creator's code model 10 | QML_IMPORT_PATH = 11 | 12 | # Default rules for deployment. 13 | include(deployment.pri) 14 | 15 | DISTFILES += \ 16 | http_request.txt 17 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Code for Qt Developer Days 2011 talk "Basic networking with Qt" (45 min session) 2 | video: https://www.youtube.com/watch?v=cNHAFbG3iEQ 3 | slides: 26Oct2011_MarkusGoetz_BasicNetworkingWithQt.pdf 4 | tag: git checkout 2011 5 | 6 | NEW NEW NEW! Also the 2015 Code "Basic networking with Qt" (25 min session) 7 | https://www.qtworldsummit.com/cpt-sessions/basic-networking-with-qt/ 8 | video: to be uploaded 9 | slides: 6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf 10 | tag: git checkout 2015 11 | 12 | More info: 13 | http://woboq.com/ 14 | -------------------------------------------------------------------------------- /deployment.pri: -------------------------------------------------------------------------------- 1 | android-no-sdk { 2 | target.path = /data/user/qt 3 | export(target.path) 4 | INSTALLS += target 5 | } else:android { 6 | x86 { 7 | target.path = /libs/x86 8 | } else: armeabi-v7a { 9 | target.path = /libs/armeabi-v7a 10 | } else { 11 | target.path = /libs/armeabi 12 | } 13 | export(target.path) 14 | INSTALLS += target 15 | } else:unix { 16 | isEmpty(target.path) { 17 | qnx { 18 | target.path = /tmp/$${TARGET}/bin 19 | } else { 20 | target.path = /opt/$${TARGET}/bin 21 | } 22 | export(target.path) 23 | } 24 | INSTALLS += target 25 | } 26 | 27 | export(INSTALLS) 28 | -------------------------------------------------------------------------------- /http_request.txt: -------------------------------------------------------------------------------- 1 | POST /chat HTTP/1.0 2 | Content-Length: 32 3 | 4 | {"chat":{"message":"Hi buddy"}} 5 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // This code is released as public domain. 2 | // -- Markus Goetz 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | ////////////////////////////////////////////////////////////////////////////////////////// 16 | ////////////////////////////////////////////////////////////////////////////////////////// 17 | class NodeList : public QAbstractListModel { 18 | Q_OBJECT 19 | public: 20 | enum NodeDataRoles { 21 | AddrRole = Qt::UserRole + 1, 22 | ChatLogRole 23 | }; 24 | 25 | NodeList() { 26 | } 27 | 28 | QHash roleNames() const { 29 | QHash roles; 30 | roles[AddrRole] = "addr"; 31 | roles[ChatLogRole] = "log"; 32 | return roles; 33 | } 34 | 35 | int rowCount(const QModelIndex &parent = QModelIndex()) const { 36 | Q_UNUSED(parent); 37 | return nodes.size(); 38 | } 39 | 40 | int columnCount(const QModelIndex &parent = QModelIndex()) const { 41 | Q_UNUSED(parent); 42 | return 1; 43 | } 44 | 45 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const { 46 | QMap::const_iterator i = nodes.begin(); 47 | i += index.row(); 48 | if (role == AddrRole) 49 | return i.key(); 50 | else if (role == ChatLogRole) 51 | return i.value(); 52 | else 53 | return ""; 54 | } 55 | 56 | 57 | public slots: 58 | void nodeDiscoveredSlot(QHostAddress addr) { 59 | if (!nodes.contains(addr.toString())) { 60 | beginResetModel(); 61 | nodes.insert(addr.toString(), ""); 62 | endResetModel(); 63 | } 64 | } 65 | 66 | void chatMessageReceivedSlot(QHostAddress addr, QByteArray json) { 67 | QJsonDocument jsonDocument = QJsonDocument::fromJson(json); 68 | QString msg = jsonDocument.object().value("chat").toObject().value("message").toString(); 69 | appendLog(addr.toString(), "Remote", msg.simplified()); 70 | } 71 | 72 | void appendLog(QString addr, QString who, QString s) { 73 | beginResetModel(); 74 | nodes[addr] = nodes[addr] + "<" + who + "> "+ s + "\n"; 75 | endResetModel(); 76 | } 77 | 78 | private: 79 | QMap nodes; 80 | }; 81 | 82 | ////////////////////////////////////////////////////////////////////////////////////////// 83 | ////////////////////////////////////////////////////////////////////////////////////////// 84 | 85 | class Discovery : public QObject { 86 | Q_OBJECT 87 | public: 88 | Discovery() { 89 | socket.bind(31337); 90 | connect(&socket, &QUdpSocket::readyRead, this, &Discovery::datagramReceived); 91 | sendHelloDatagram(); 92 | 93 | helloTimer.setInterval(30*1000); 94 | connect(&helloTimer, &QTimer::timeout, this, &Discovery::sendHelloDatagram); 95 | helloTimer.start(); 96 | } 97 | 98 | public slots: 99 | void sendHelloDatagram() { 100 | QByteArray helloDatagram("QLocalChat Hello"); 101 | socket.writeDatagram(helloDatagram, QHostAddress::Broadcast, 31337); 102 | } 103 | 104 | void datagramReceived() { 105 | qint64 datagramSize = socket.pendingDatagramSize(); 106 | QByteArray datagram; 107 | datagram.resize(datagramSize); 108 | QHostAddress addr; 109 | socket.readDatagram(datagram.data(), datagramSize, &addr); 110 | if (datagram.startsWith("QLocalChat Hello")) { 111 | emit nodeDiscovered(addr); 112 | } 113 | } 114 | signals: 115 | void nodeDiscovered(QHostAddress addr); 116 | 117 | private: 118 | QUdpSocket socket; 119 | QTimer helloTimer; 120 | 121 | }; 122 | 123 | ////////////////////////////////////////////////////////////////////////////////////////// 124 | ////////////////////////////////////////////////////////////////////////////////////////// 125 | 126 | class HttpHandler : public QObject { 127 | Q_OBJECT 128 | public: 129 | HttpHandler(QTcpSocket *so) 130 | : state(Connected), socket(so), contentLength(0) 131 | { 132 | socket->setParent(this); 133 | connect(socket, &QTcpSocket::readyRead, this, &HttpHandler::readyReadSlot); 134 | } 135 | public slots: 136 | void readyReadSlot() { 137 | while (state != ReadingData && socket->canReadLine()) { 138 | QByteArray line = socket->readLine().simplified(); 139 | if (state == Connected && (line == "POST /chat HTTP/1.0" || line == "POST /chat HTTP/1.1")) { 140 | state = ReadingHeaders; 141 | } else if (state == Connected) { 142 | delete this; 143 | return; 144 | } else if (state == ReadingHeaders && line.length() > 0) { 145 | if (line.startsWith("Content-Length:")) { 146 | contentLength = line.mid(15).toInt(); 147 | } 148 | state = ReadingHeaders; 149 | } else if (state == ReadingHeaders && line.length() == 0) { 150 | state = ReadingData; 151 | // send reply 152 | socket->write("HTTP/1.0 200 OK\r\n"); 153 | socket->write("Connection: close\r\n"); 154 | socket->write("\r\n"); 155 | } 156 | } 157 | if (state == ReadingData && socket->bytesAvailable()) { 158 | if (socket->bytesAvailable() >= contentLength) { 159 | QByteArray data = socket->readAll(); 160 | emit chatMessageReceived(socket->peerAddress(), data); 161 | QObject::connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater); 162 | socket->disconnectFromHost(); 163 | return; 164 | } 165 | } 166 | } 167 | 168 | signals: 169 | void chatMessageReceived(QHostAddress, QByteArray); 170 | 171 | private: 172 | enum ConnectionState {Connected, ReadingHeaders, 173 | ReadingData, Closed}; 174 | ConnectionState state; 175 | QTcpSocket *socket; 176 | int contentLength; 177 | }; 178 | 179 | ////////////////////////////////////////////////////////////////////////////////////////// 180 | ////////////////////////////////////////////////////////////////////////////////////////// 181 | 182 | class HttpServer : public QObject { 183 | Q_OBJECT 184 | public: 185 | HttpServer() { 186 | QObject::connect(&serverSocket,&QTcpServer::newConnection, 187 | this, &HttpServer::incomingConnection); 188 | serverSocket.listen(QHostAddress::Any, 31337); 189 | } 190 | 191 | public slots: 192 | void incomingConnection() { 193 | QTcpSocket *incomingSocket = serverSocket.nextPendingConnection(); 194 | HttpHandler *httpHandler = new HttpHandler(incomingSocket); 195 | httpHandler->setParent(this); 196 | // Just forward the received message as signal 197 | QObject::connect(httpHandler, &HttpHandler::chatMessageReceived, 198 | this, &HttpServer::chatMessageReceived); 199 | } 200 | 201 | signals: 202 | void chatMessageReceived(QHostAddress, QByteArray); 203 | 204 | private: 205 | QTcpServer serverSocket; 206 | }; 207 | 208 | ////////////////////////////////////////////////////////////////////////////////////////// 209 | ////////////////////////////////////////////////////////////////////////////////////////// 210 | 211 | int main(int argc, char *argv[]) 212 | { 213 | QGuiApplication app(argc, argv); 214 | 215 | QQmlApplicationEngine engine; 216 | 217 | NodeList nodeList; 218 | engine.rootContext()->setContextProperty("nodeList", &nodeList); 219 | 220 | Discovery localChatDiscovery; 221 | QObject::connect(&localChatDiscovery, &Discovery::nodeDiscovered, 222 | &nodeList, &NodeList::nodeDiscoveredSlot); 223 | 224 | HttpServer localChatHttpServer; 225 | QObject::connect(&localChatHttpServer, &HttpServer::chatMessageReceived, 226 | &nodeList, &NodeList::chatMessageReceivedSlot); 227 | 228 | // QML loading 229 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 230 | return app.exec(); 231 | } 232 | 233 | #include "main.moc" 234 | -------------------------------------------------------------------------------- /main.qml: -------------------------------------------------------------------------------- 1 | // This code is released as public domain. 2 | // -- Markus Goetz 3 | import QtQuick 2.4 4 | import QtQuick.Window 2.2 5 | 6 | Window { 7 | width: 800 8 | height: 600 9 | visible: true 10 | 11 | 12 | Rectangle { 13 | id: leftRect 14 | ListView { 15 | id: nodeView 16 | model: nodeList 17 | anchors.fill: parent 18 | anchors.margins: 10 19 | highlightFollowsCurrentItem: true 20 | onCurrentItemChanged : { 21 | chatField.text = nodeView.currentItem.log 22 | } 23 | focus: true 24 | 25 | delegate: Text { 26 | font.pointSize: 30 27 | text: addr 28 | horizontalAlignment: Text.AlignHCenter 29 | verticalAlignment: Text.AlignVCenter 30 | property string log: model.log 31 | property string addr: model.addr 32 | height: 50 33 | width: parent.width 34 | MouseArea { 35 | anchors.fill: parent 36 | onClicked: nodeView.currentIndex = index 37 | } 38 | } 39 | 40 | highlight: Rectangle { color: "lightsteelblue"; radius: 5 } 41 | } 42 | color: "white" 43 | border.color: "grey" 44 | radius:10 45 | 46 | anchors.left: parent.left 47 | anchors.top: parent.top 48 | anchors.bottom: parent.bottom 49 | width: 250 50 | } 51 | 52 | 53 | Rectangle { 54 | color: "white" 55 | border.color: "grey" 56 | radius:10 57 | anchors.left: leftRect.right 58 | anchors.top: parent.top 59 | anchors.right: parent.right 60 | anchors.bottom: inputRect.top 61 | 62 | Text { 63 | font.pointSize: 25 64 | id: chatField 65 | anchors.fill: parent 66 | anchors.margins: 10 67 | 68 | text: "Select a host on the left.
Then use the input below to send a message
" 69 | } 70 | } 71 | 72 | Rectangle { 73 | id: inputRect 74 | TextInput { 75 | id: input 76 | font.pointSize: 25 77 | anchors.fill: parent 78 | anchors.margins: 10 79 | Keys.onPressed: { 80 | if (event.key == Qt.Key_Return) { 81 | if (!nodeView.currentItem) 82 | return; 83 | 84 | var chatText = input.text; 85 | input.text = ""; 86 | var addr = nodeView.currentItem.addr; 87 | 88 | var request = new XMLHttpRequest(); 89 | request.onreadystatechange = function() { 90 | if (request.readyState == 4) { 91 | nodeList.appendLog(addr, "You", chatText); 92 | chatField.text = nodeView.currentItem.log; 93 | } 94 | } 95 | 96 | var url = "http://" + addr + ":31337/chat"; 97 | request.open("POST", url); 98 | var json = JSON.stringify({ "chat": {"message":chatText} }); 99 | console.log(json); 100 | request.send(json); 101 | } 102 | } 103 | } 104 | color: "white" 105 | border.color: "grey" 106 | radius:10 107 | anchors.left: leftRect.right 108 | anchors.bottom: parent.bottom 109 | anchors.right: parent.right 110 | height: 60 111 | focus: true 112 | } 113 | 114 | 115 | } 116 | -------------------------------------------------------------------------------- /qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | --------------------------------------------------------------------------------