├── http_request.txt
├── qml.qrc
├── 6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf
├── 26Oct2011_MarkusGoetz_BasicNetworkingWithQt.pdf
├── QLocalChat.pro
├── README
├── deployment.pri
├── main.qml
└── main.cpp
/http_request.txt:
--------------------------------------------------------------------------------
1 | POST /chat HTTP/1.0
2 | Content-Length: 32
3 |
4 | {"chat":{"message":"Hi buddy"}}
5 |
--------------------------------------------------------------------------------
/qml.qrc:
--------------------------------------------------------------------------------
1 |
2 |
3 | main.qml
4 |
5 |
6 |
--------------------------------------------------------------------------------
/6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woboq/basic_networking_with_qt/HEAD/6Oct2015_MarkusGoetz_BasicNetworkingWithQt.pdf
--------------------------------------------------------------------------------
/26Oct2011_MarkusGoetz_BasicNetworkingWithQt.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/woboq/basic_networking_with_qt/HEAD/26Oct2011_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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------