├── .hgignore
├── src
├── qmldir
├── Sockets.pluginspec
├── qml-sockets.h
├── qml
│ └── TCPSocket.qml
├── tcp.h
├── udp_multicast.h
└── tcp_server.h
├── TODO.md
├── test
├── tst_tcp_initial.qml
├── ServerTestSocket.qml
├── tst_tcp.qml
├── tst_udp_multicast.qml
├── tst_tcp_server.qml
├── tst_tcp_server_clientDelegate.qml
├── tst_tcp_server_maxClients.qml
└── tst_tcp_match.qml
├── Rakefile
├── qml-sockets.pro
├── LICENSE
└── README.md
/.hgignore:
--------------------------------------------------------------------------------
1 | ^Makefile$
2 | ^build/
3 | \.sublime-\w+$
4 | ^\.git/
5 | ^plugin\.pro\.user$
6 |
--------------------------------------------------------------------------------
/src/qmldir:
--------------------------------------------------------------------------------
1 | module org.jemc.qml.Sockets
2 | plugin qml-sockets
3 |
4 | TCPSocket 1.0 qml/TCPSocket.qml
5 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 |
2 | - Finish match functionality for TCPSocket
3 |
4 | - Add match functionality to other socket types
5 |
6 | - Add method to TCPServer to stop listening (opposite of listen())
7 |
--------------------------------------------------------------------------------
/test/tst_tcp_initial.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "TCPSocket initial"
10 |
11 | TCPSocket {
12 | id: socket
13 | }
14 |
15 | function test_state() {
16 | compare(socket.state, 0)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Sockets.pluginspec:
--------------------------------------------------------------------------------
1 |
2 | Joe McIlvain
3 | Copyright 2013 Joe McIlvain
4 | MIT License
5 | Qt Quick
6 | Exposing Qt's C++ socket objects to QML for declarative use.
7 | https://github.com/jemc/qml-sockets
8 |
9 |
10 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 |
2 | require 'qt/commander'
3 |
4 | task :default => :test
5 |
6 | task :android do
7 | Qt::Commander::Creator.profiles.select(&:android?).each do |profile|
8 | profile.toolchain.env do
9 | system "#{profile.version.qmake} *.pro -spec android-g++" and
10 | system "make && make install"
11 | end
12 | end
13 | end
14 |
15 | task :install do
16 | system "qmake *.pro && make && make install"
17 | end
18 |
19 | task :test => :install do
20 | system "qmltestrunner"
21 | end
22 |
23 | task :clean do
24 | `make clean && rm Makefile`
25 | end
26 |
27 | task :cleantest => [:clean, :test]
28 |
--------------------------------------------------------------------------------
/src/qml-sockets.h:
--------------------------------------------------------------------------------
1 |
2 | #include
3 | #include
4 |
5 |
6 | #include "tcp.h"
7 | #include "tcp_server.h"
8 | #include "udp_multicast.h"
9 |
10 |
11 | class QmlSocketsPlugin : public QQmlExtensionPlugin
12 | {
13 | Q_OBJECT
14 | Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QQmlExtensionInterface")
15 |
16 | public:
17 | void registerTypes(const char *uri)
18 | {
19 | qmlRegisterType(uri, 1, 0, "AbstractTCPSocket");
20 | qmlRegisterType(uri, 1, 0, "TCPServer");
21 | qmlRegisterType(uri, 1, 0, "UDPMulticastSocket");
22 | };
23 | };
24 |
--------------------------------------------------------------------------------
/test/ServerTestSocket.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TCPSocket {
8 | host: "localhost"
9 | port: server.port
10 |
11 | property var test
12 | property var verified: false
13 | property var response: ""
14 | property var expected: /Welcome/
15 |
16 | onRead: {
17 | response += message
18 | write("Thanks, Server!")
19 | }
20 | onDisconnected: {
21 | test.verify(response!="", "No response from server")
22 | test.verify(response.search(expected)>=0,
23 | "Unrecognized response from server: %1".arg(response))
24 | verified = true
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/qml-sockets.pro:
--------------------------------------------------------------------------------
1 |
2 | TEMPLATE = lib
3 |
4 | CONFIG += qt plugin c++11
5 | QT += qml quick
6 |
7 | TARGET = $$qtLibraryTarget(qml-sockets)
8 | uri = org.jemc.qml.Sockets
9 |
10 | TARGETDIR = $$[QT_INSTALL_QML]/$$replace(uri, \\., /)
11 | SRCDIR = $$PWD/src
12 | DESTDIR = $$PWD/build/native
13 |
14 | android {
15 | VENDORDIR = $$PWD/vendor/prefix/$$ANDROID_TARGET_ARCH
16 | DESTDIR = $$PWD/build/$$ANDROID_TARGET_ARCH
17 | QMAKE_LIBDIR += $$VENDORDIR/lib
18 | QMAKE_INCDIR += $$VENDORDIR/include
19 | }
20 |
21 | HEADERS += $$SRCDIR/qml-sockets.h \
22 | $$SRCDIR/tcp.h \
23 | $$SRCDIR/tcp_server.h \
24 | $$SRCDIR/udp_multicast.h
25 |
26 | OBJECTS_DIR = $$DESTDIR/.obj
27 | MOC_DIR = $$DESTDIR/.moc
28 | RCC_DIR = $$DESTDIR/.rcc
29 | UI_DIR = $$DESTDIR/.ui
30 |
31 | target.path = $$TARGETDIR
32 | qmldir.files = $$SRCDIR/qmldir
33 | qmldir.path = $$TARGETDIR
34 | qml.files = $$SRCDIR/qml/*
35 | qml.path = $$TARGETDIR/qml
36 |
37 | INSTALLS += target qmldir qml
38 |
--------------------------------------------------------------------------------
/test/tst_tcp.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "TCPSocket"
10 |
11 | TCPSocket {
12 | id: socket
13 | host: "www.example.com"
14 | port: 80
15 |
16 | property var verified: false
17 | property var response: ""
18 | property var expected: /HTTP\/[^\n]* OK/
19 |
20 | onConnected: write("GET / HTTP/1.1\r\nHost: %1\r\nConnection: close\r\n\r\n".arg(host))
21 | onRead: response += message
22 | onDisconnected: {
23 | test.verify(response!=undefined, "No response received")
24 | test.verify(response.search(expected)>=0,
25 | "Unrecognized response received: %1".arg(response))
26 | verified = true
27 | }
28 | }
29 |
30 | function wait_for_disconnect() { while(socket.state!=0) { wait(0) } }
31 |
32 | function test_it() {
33 | socket.connect()
34 | wait_for_disconnect()
35 | verify(socket.verified, "Disconnect hook never ran on socket.")
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/test/tst_udp_multicast.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "UDPMulticastSocket"
10 |
11 | UDPMulticastSocket {
12 | id: socket
13 | group: "239.255.250.250"
14 | port: 9131
15 |
16 | property var lastRead
17 | property var readCount: 0
18 |
19 | onRead: { lastRead = message; readCount += 1 }
20 | }
21 |
22 | function wait_for_connect() { while(socket.state!=4) { wait(0) } }
23 | function wait_for_disconnect() { while(socket.state!=0) { wait(0) } }
24 |
25 | function test_it() {
26 | socket.connect()
27 | wait_for_connect()
28 |
29 | compare(socket.lastRead, undefined)
30 | compare(socket.readCount, 0)
31 | socket.write("test")
32 | wait(100)
33 | compare(socket.lastRead, "test")
34 | compare(socket.readCount, 1)
35 | socket.write("other")
36 | wait(100)
37 | compare(socket.lastRead, "other")
38 | compare(socket.readCount, 2)
39 |
40 | socket.disconnect()
41 | wait_for_disconnect()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Joe Eli McIlvain
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/tst_tcp_server.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "TCPServer"
10 |
11 | TCPServer {
12 | id: server
13 | port: 4998
14 |
15 | property var verified: false
16 | property var response: ""
17 | property var expected: /Thanks/
18 |
19 | onClientConnected: {
20 | client.write("Welcome")
21 | }
22 | onClientRead: {
23 | response += message
24 | client.disconnect()
25 | }
26 | onClientDisconnected: {
27 | test.verify(response!="", "No response from client")
28 | test.verify(response.search(expected)>=0,
29 | "Unrecognized response from client: %1".arg(response))
30 | verified = true
31 | }
32 | }
33 |
34 | ServerTestSocket { id: socket; test: test }
35 |
36 | function wait_for_disconnect() { while(socket.state!=0) { wait(0) } }
37 |
38 | function test_it() {
39 | server.listen()
40 | socket.connect()
41 | wait_for_disconnect()
42 | verify(socket.verified, "Disconnect hook never ran on socket.")
43 | verify(server.verified, "Disconnect hook never ran on server.")
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/qml/TCPSocket.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtQuick 2.1
3 |
4 | import org.jemc.qml.Sockets 1.0
5 |
6 |
7 | AbstractTCPSocket {
8 | // The expression to scan for in the incoming buffer
9 | property var expression
10 |
11 | // When the expression is matched in the buffer,
12 | // match is signalled with the matching string
13 | signal match(string matchString, var matchCaptures, string preMatchString)
14 |
15 | // The buffer of text waiting to be matched.
16 | // This buffer is cleared to an empty string in onConnected
17 | property string matchBuffer: ""
18 |
19 | onConnected: matchBuffer = ""
20 |
21 | onRead: {
22 | // Expression matching is disabled by default to save memory (matchBuffer)
23 | if(expression !== undefined) {
24 | // Append the new message to the buffer
25 | matchBuffer += message
26 |
27 | // Pull out each match and pre-match and trigger the signal
28 | var data, idx, str, pre
29 | while(data = matchBuffer.match(expression)) {
30 | idx = matchBuffer.search(expression)
31 | str = data[0]
32 | pre = matchBuffer.slice(0, idx)
33 | matchBuffer = matchBuffer.slice(str.length+idx)
34 | match(str, data.slice(1), pre)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/test/tst_tcp_server_clientDelegate.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "TCPServer#clientDelegate"
10 |
11 | TCPServer {
12 | id: server
13 | port: 4998
14 |
15 | property var verified: false
16 |
17 | clientDelegate: TCPSocket {
18 | property var response: ""
19 | property var expected: /Thanks/
20 |
21 | onConnected: write("Welcome")
22 |
23 | onRead: {
24 | response += message
25 | disconnect()
26 | }
27 | onDisconnected: {
28 | test.verify(response!="", "No response from client")
29 | test.verify(response.search(expected)>=0,
30 | "Unrecognized response from client: %1".arg(response))
31 | server.verified = true
32 | }
33 | }
34 | }
35 |
36 | ServerTestSocket { id: socket; test: test }
37 |
38 | function wait_for_disconnect() { while(socket.state!=0) { wait(0) } }
39 |
40 | function test_it() {
41 | server.listen()
42 | socket.connect()
43 | wait_for_disconnect()
44 | verify(socket.verified, "Disconnect hook never ran on socket.")
45 | verify(server.verified, "Disconnect hook never ran on server.")
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # QML Sockets
3 |
4 | Exposing Qt's C++ socket objects to QML for declarative use.
5 |
6 | ## Overview
7 |
8 | Qt [says][QmlLang]:
9 |
10 | > QML offers a highly readable, declarative, JSON-like syntax with support for
11 | > imperative JavaScript expressions combined with dynamic property bindings.
12 |
13 | Qt also provides some very useful socket objects in their [QtNetwork] C++ API.
14 |
15 | However, Qt does not provide these objects in a QML API. This plugin is an
16 | attempt do so in order to simplify socket programming for basic applications.
17 | The QML API provided may not be able to handle some of the more advanced use
18 | cases, but the lowered ceremony it brings to socket programming can get you
19 | from idea to implementation in just a few lines:
20 |
21 | ```qml
22 | import Sockets 1.0
23 |
24 | TCPSocket {
25 | host: "www.the-world.com"
26 | port: 1025
27 |
28 | Component.onCompleted: connect()
29 |
30 | onConnected: write("Hello, World!")
31 |
32 | onRead: console.log("The World says:", message)
33 |
34 | onDisconnected: console.log("Goodbye, Cruel World...")
35 | }
36 | ```
37 |
38 | [QmlLang]: http://qt-project.org/doc/qt-5/qmlapplications.html
39 | [QtNetwork]: http://qt-project.org/doc/qt-5/qtnetwork-programming.html
40 |
--------------------------------------------------------------------------------
/src/tcp.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef QML_SOCKETS_TCP
3 | #define QML_SOCKETS_TCP
4 |
5 | #include
6 |
7 |
8 | class TCPSocket : public QObject
9 | {
10 | Q_OBJECT
11 | Q_ENUMS(QAbstractSocket::SocketState)
12 |
13 | Q_PROPERTY(QString host MEMBER m_host NOTIFY hostChanged)
14 | Q_PROPERTY(uint port MEMBER m_port NOTIFY portChanged)
15 | Q_PROPERTY(QAbstractSocket::SocketState state \
16 | MEMBER m_state NOTIFY stateChanged)
17 |
18 | signals:
19 | void hostChanged();
20 | void portChanged();
21 | void stateChanged();
22 |
23 | void read(const QString &message);
24 | void connected();
25 | void disconnected();
26 |
27 | public:
28 | TCPSocket(QObject* parent = 0)
29 | { (void)parent; assignSocket(); };
30 |
31 | ~TCPSocket()
32 | { delete m_socket; m_socket = NULL; }
33 |
34 | void assignSocket(QTcpSocket *socket = NULL)
35 | {
36 | // Delete old socket if existent
37 | if(m_socket!=NULL)
38 | delete m_socket;
39 |
40 | // Create new socket or assign passed socket
41 | if(socket!=NULL)
42 | m_socket = socket;
43 | else
44 | m_socket = new QTcpSocket(this);
45 |
46 | // Register event handlers
47 | QObject::connect(m_socket, &QAbstractSocket::stateChanged,
48 | [=](QAbstractSocket::SocketState state)
49 | { setProperty("state", state); });
50 |
51 | QObject::connect(m_socket, &QAbstractSocket::readyRead,
52 | [=]() { emit read(m_socket->readAll()); });
53 |
54 | QObject::connect(m_socket, &QAbstractSocket::connected,
55 | [=]() { emit connected(); });
56 |
57 | QObject::connect(m_socket, &QAbstractSocket::disconnected,
58 | [=]() { emit disconnected(); });
59 | }
60 |
61 | public slots:
62 | void connect()
63 | { m_socket->connectToHost(m_host, m_port); }
64 |
65 | void disconnect()
66 | { m_socket->disconnectFromHost(); }
67 |
68 | void write(QString message)
69 | { m_socket->write(message.toLocal8Bit()); }
70 |
71 | public:
72 | QString m_host;
73 | uint m_port;
74 | QAbstractSocket::SocketState m_state = QAbstractSocket::UnconnectedState;
75 | QTcpSocket *m_socket = NULL;
76 | };
77 |
78 | #endif
79 |
--------------------------------------------------------------------------------
/test/tst_tcp_server_maxClients.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "TCPServer#maxClients"
10 |
11 | TCPServer {
12 | id: server
13 | port: 4998
14 |
15 | maxClients: 3
16 |
17 | property var verified_count: 0
18 | property var response: ""
19 | property var expected: /Thanks/
20 | property var expected_con_counts: [1,2,3,3,3]
21 | property var expected_dis_counts: [2,2,2,1,0]
22 |
23 | onClientConnected: {
24 | // test.verify(clients.length==expected_con_counts.shift())
25 | client.write("Welcome")
26 | }
27 | onClientRead: {
28 | response += message
29 | client.disconnect()
30 | }
31 | onClientDisconnected: {
32 | // test.verify(clients.length==expected_dis_counts.shift())
33 |
34 | test.verify(response!="", "No response from client")
35 | test.verify(response.search(expected)>=0,
36 | "Unrecognized response from client: %1".arg(response))
37 | verified_count += 1
38 | }
39 |
40 | // This tests that using a delegate doesn't break with multiple clients
41 | clientDelegate: TCPSocket { }
42 | }
43 |
44 | ServerTestSocket { id:socket1; test:test }
45 | ServerTestSocket { id:socket2; test:test }
46 | ServerTestSocket { id:socket3; test:test }
47 | ServerTestSocket { id:socket4; test:test }
48 | ServerTestSocket { id:socket5; test:test }
49 |
50 | function wait_for_disconnect() {
51 | while(socket1.state!=0 ||
52 | socket2.state!=0 ||
53 | socket3.state!=0 ||
54 | socket4.state!=0 ||
55 | socket5.state!=0) { wait(0) }
56 | }
57 |
58 | function test_it() {
59 | server.listen()
60 | socket1.connect()
61 | socket2.connect()
62 | socket3.connect()
63 | socket4.connect()
64 | socket5.connect()
65 | wait_for_disconnect()
66 | verify(socket1.verified, "Disconnect hook never ran on socket1.")
67 | verify(socket2.verified, "Disconnect hook never ran on socket2.")
68 | verify(socket3.verified, "Disconnect hook never ran on socket3.")
69 | verify(socket4.verified, "Disconnect hook never ran on socket4.")
70 | verify(socket5.verified, "Disconnect hook never ran on socket5.")
71 | verify(server.verified_count==5, "The 5 disconnect hooks didn't run.")
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/udp_multicast.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef QML_SOCKETS_UDP_MULTICAST
3 | #define QML_SOCKETS_UDP_MULTICAST
4 |
5 | #include
6 |
7 |
8 | class UDPMulticastSocket : public QObject
9 | {
10 | Q_OBJECT
11 | Q_ENUMS(QAbstractSocket::SocketState)
12 |
13 | Q_PROPERTY(QString group MEMBER m_group NOTIFY groupChanged)
14 | Q_PROPERTY(uint port MEMBER m_port NOTIFY portChanged)
15 | Q_PROPERTY(QAbstractSocket::SocketState state \
16 | MEMBER m_state NOTIFY stateChanged)
17 |
18 | signals:
19 | void groupChanged();
20 | void portChanged();
21 | void stateChanged();
22 |
23 | void read(const QString &message);
24 | void connected();
25 | void disconnected();
26 |
27 | public:
28 | UDPMulticastSocket(QObject* parent = 0)
29 | {
30 | (void)parent;
31 | m_socket = new QUdpSocket(this);
32 |
33 | QObject::connect(m_socket, &QAbstractSocket::stateChanged,
34 | [=](QAbstractSocket::SocketState state)
35 | {
36 | setProperty("state", state);
37 | if(state==QAbstractSocket::BoundState) emit connected();
38 | });
39 |
40 | QObject::connect(m_socket, &QAbstractSocket::readyRead,
41 | [=]()
42 | {
43 | while(m_socket->hasPendingDatagrams()) {
44 | QByteArray datagram;
45 | datagram.resize(m_socket->pendingDatagramSize());
46 | m_socket->readDatagram(datagram.data(), datagram.size());
47 | emit read(datagram.data());
48 | }
49 | });
50 |
51 | QObject::connect(m_socket, &QAbstractSocket::disconnected,
52 | [=]() { emit disconnected(); });
53 | };
54 |
55 | ~UDPMulticastSocket()
56 | { delete m_socket; m_socket = NULL; }
57 |
58 | public slots:
59 | void connect()
60 | {
61 | m_socket->bind(QHostAddress::AnyIPv4, m_port, QUdpSocket::ShareAddress);
62 | m_socket->joinMulticastGroup(QHostAddress(m_group));
63 | }
64 |
65 | void disconnect()
66 | {
67 | m_socket->leaveMulticastGroup(QHostAddress(m_group));
68 | m_socket->disconnectFromHost();
69 | }
70 |
71 | void write(QString message)
72 | {
73 | if(m_state!=QAbstractSocket::BoundState) connect();
74 |
75 | QByteArray datagram = message.toLocal8Bit();
76 | m_socket->writeDatagram(datagram.data(), datagram.size(),
77 | QHostAddress(m_group), m_port);
78 | }
79 |
80 | public:
81 | QString m_group;
82 | uint m_port;
83 | QAbstractSocket::SocketState m_state;
84 | QUdpSocket *m_socket = NULL;
85 | };
86 |
87 | #endif
88 |
--------------------------------------------------------------------------------
/test/tst_tcp_match.qml:
--------------------------------------------------------------------------------
1 |
2 | import QtTest 1.0
3 | import QtQuick 2.0
4 |
5 | import org.jemc.qml.Sockets 1.0
6 |
7 | TestCase {
8 | id: test
9 | name: "TCPSocket#match"
10 |
11 | TCPServer {
12 | id: server
13 | port: 4998
14 |
15 | property string welcome
16 | property string welcome2
17 |
18 | function reset() { welcome = ""; welcome2 = "" }
19 | Component.onCompleted: {
20 | reset()
21 | compare(socket.expression,
22 | undefined, "expression matching is disabled by default")
23 | }
24 |
25 | onClientConnected: {
26 | client.write(welcome)
27 | test.wait(15) // Space between messages to separate as packets
28 | client.write(welcome2)
29 | client.disconnect()
30 | }
31 | }
32 |
33 | TCPSocket {
34 | id: socket
35 | host: "127.0.0.1"
36 | port: 4998
37 |
38 | property var matches
39 | property var preMatches
40 | property var captures
41 |
42 | function reset() { matches = []; preMatches = []; captures = [] }
43 | Component.onCompleted: reset()
44 |
45 | onMatch: {
46 | matches .push( matchString )
47 | preMatches.push(preMatchString )
48 | captures .push( matchCaptures)
49 | }
50 |
51 | onConnected: {
52 | test.compare(socket.matchBuffer, "", "matchBuffer reset on connect")
53 | }
54 | }
55 |
56 | function initTestCase() { server.listen() }
57 |
58 | function wait_for_connect() { while(socket.state!==2) { wait(0) } }
59 | function wait_for_disconnect() { while(socket.state!==0) { wait(0) } }
60 |
61 | function test_match() {
62 | socket.reset()
63 | server.reset()
64 |
65 | socket.expression = /(.*?)[\r\n]+/
66 |
67 | server.welcome = "Welcome\nthe\rlovely "
68 | server.welcome2 = "new\n\rclient\r\nthe_rest"
69 |
70 | socket.connect()
71 | wait_for_disconnect()
72 |
73 | compare(socket.matches, ["Welcome\n","the\r","lovely new\n\r","client\r\n"])
74 | compare(socket.matchBuffer, "the_rest")
75 | }
76 |
77 | function test_prematch() {
78 | socket.reset()
79 | server.reset()
80 |
81 | socket.expression = /[a-z]+(?=[^a-z])/
82 | server.welcome = "!foo_ba"
83 | server.welcome2 = "r_99_baz!!!"
84 |
85 | socket.connect()
86 | wait_for_disconnect()
87 |
88 | compare(socket.matches, ["foo","bar","baz"])
89 | compare(socket.preMatches, ["!","_","_99_"])
90 | compare(socket.captures, [[],[],[]])
91 | compare(socket.matchBuffer, "!!!")
92 | }
93 |
94 | function test_captures() {
95 | socket.reset()
96 | server.reset()
97 |
98 | socket.expression = /([a-z]+)_([a-z]+)[\r\n]+/
99 | server.welcome = "foo_ba"
100 | server.welcome2 = "r\nfoo_baz\n"
101 |
102 | socket.connect()
103 | wait_for_disconnect()
104 |
105 | compare(socket.matches, ["foo_bar\n","foo_baz\n"])
106 | compare(socket.preMatches, ["",""])
107 | compare(socket.captures, [["foo","bar"],["foo","baz"]])
108 | compare(socket.matchBuffer, "")
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/tcp_server.h:
--------------------------------------------------------------------------------
1 |
2 | #ifndef QML_SOCKETS_TCP_SERVER
3 | #define QML_SOCKETS_TCP_SERVER
4 |
5 | #include
6 | #include
7 | #include
8 |
9 | #include "tcp.h"
10 |
11 |
12 | class TCPServer : public QObject
13 | {
14 | Q_OBJECT
15 |
16 | Q_PROPERTY(uint port MEMBER m_port NOTIFY portChanged)
17 | Q_PROPERTY(uint maxClients MEMBER m_maxClients NOTIFY maxClientsChanged)
18 | // Q_PROPERTY(QQmlListProperty clients READ clients)
19 | Q_PROPERTY(QQmlComponent* clientDelegate MEMBER m_clientDelegate NOTIFY clientDelegateChanged)
20 |
21 | signals:
22 | void portChanged();
23 | void maxClientsChanged();
24 | void clientDelegateChanged();
25 |
26 | void clientRead(TCPSocket* client, const QString &message);
27 | void clientConnected(TCPSocket* client);
28 | void clientDisconnected(TCPSocket* client);
29 |
30 | public:
31 | TCPServer(QObject* parent = 0)
32 | {
33 | (void)parent;
34 | m_server = new QTcpServer(this);
35 |
36 | QObject::connect(m_server, &QTcpServer::newConnection,
37 | [=]() {
38 | QTcpSocket *client_sock = NULL;
39 | TCPSocket *client = NULL;
40 |
41 | // Forget the new client if client count is already at max
42 | if(m_maxClients!=0 && (uint)m_clients.count()>=m_maxClients)
43 | return;
44 |
45 | // If the clientDelegate was specified, try to instantiate it
46 | if(m_clientDelegate!=NULL)
47 | {
48 | client = qobject_cast( \
49 | m_clientDelegate->create(QQmlEngine::contextForObject(this)));
50 |
51 | if(client==NULL)
52 | qWarning("TCPServer's clientDelegate component must be"\
53 | " a TCPSocket. Using default TCPSocket instead.\n");
54 | };
55 |
56 | // Otherwise, instantiate the default
57 | if(client==NULL)
58 | client = new TCPSocket(this);
59 |
60 | // Get the next connection, and return if it didn't come through
61 | if((client_sock=m_server->nextPendingConnection())==NULL)
62 | return;
63 |
64 | // Assign the new connection to inside the client wrapper object
65 | client->assignSocket(client_sock);
66 |
67 | // on client.read, emit clientRead
68 | QObject::connect(client, &TCPSocket::read,
69 | [=](const QString &message) {
70 | emit clientRead(client, message);
71 | });
72 |
73 | // on client.disconncted, emit clientDisconnected
74 | QObject::connect(client, &TCPSocket::disconnected,
75 | [=]() {
76 | m_clients.removeAll(client);
77 |
78 | emit clientDisconnected(client);
79 | client->deleteLater();
80 |
81 | m_server->newConnection();
82 | });
83 |
84 | // emit clientConnected
85 | m_clients.append(client);
86 |
87 | emit clientConnected(client);
88 | client->connected();
89 | });
90 | };
91 |
92 | ~TCPServer()
93 | { delete m_server; m_server = NULL; }
94 |
95 | // // Create the clients QML list property to expose the m_clients QList
96 | // QQmlListProperty clients()
97 | // { return QQmlListProperty(
98 | // (QObject*)this,
99 | // (void*)&m_clients,
100 | // [=](QQmlListProperty *prop)
101 | // { return static_cast< QList *>(prop->data)->count(); },
102 | // [=](QQmlListProperty *prop, int index)
103 | // { return static_cast< QList *>(prop->data)->at(index); }); }
104 |
105 | public slots:
106 | void listen()
107 | { m_server->listen(QHostAddress::Any, m_port); }
108 |
109 | public:
110 | uint m_port;
111 | uint m_maxClients = 0;
112 | QList m_clients;
113 | QTcpServer* m_server = NULL;
114 | QQmlComponent* m_clientDelegate = NULL;
115 | };
116 |
117 | #endif
118 |
--------------------------------------------------------------------------------