QtのSocketクラスを使えば、その辺をラップしてくれて楽ができます。ちょっと試しにServerのconsoleプログラムを作ってみました。telnet等をclientにして簡単に実験ができます。
proファイルですが、networkの追加を忘れずに。
MyTcpServer.pro
- QT -= gui
- QT += core
- QT += network
- CONFIG += c++11 console
- CONFIG -= app_bundle
mainは以下の通りです。
main.cpp
- #include <qcoreapplication>
- #include "mytcpserver.h"
- int main(int argc, char *argv[])
- {
- QCoreApplication a(argc, argv);
- MyTcpServer server;
- return a.exec();
- }
MyTcpServerクラスのヘッダは最低限の関数だけ定義します。
mytcpserver.h
- #ifndef MYTCPSERVER_H
- #define MYTCPSERVER_H
- #include <qobject>
- #include <qtcpsocket>
- #include <qtcpserver>
- #include <qdebug>
- class MyTcpServer : public QObject
- {
- Q_OBJECT
- public:
- explicit MyTcpServer(QObject *parent = 0);
- signals:
- public slots:
- void newConnection();
- void readData();
- void socketError(QAbstractSocket::SocketError);
- void deleteLater();
- private:
- QTcpServer *server;
- QTcpSocket *socket;
- };
- #endif // MYTCPSERVER_H
本体です。connectして文字列を送ったら、後は受信するだけのものです。相手がcloseしたり、勝手に切ったり、エラーが起きたらServer側はcloseするようにしてあります。また、"bye"を受信したらやはりcloseします。(windowsだと、受信文字列の最後にCR/LFが含まれますが、Linux等では行末記号は含まれてきません。)
mytcpserver.cpp
- // mytcpserver.cpp
- #include "mytcpserver.h"
- #include <qdatastream .h>
- MyTcpServer::MyTcpServer(QObject *parent) :
- QObject(parent)
- {
- server = new QTcpServer(this);
- // 誰かがconnectしてきたら、signalを出してnewConnection()をCall
- connect(server, SIGNAL(newConnection()),
- this, SLOT(newConnection()));
- if(!server->listen(QHostAddress::Any, 9999))
- {
- qDebug() << "Server could not start";
- }
- else
- {
- qDebug() << "Server started!";
- }
- }
- void MyTcpServer::newConnection()
- {
- // socketのインスタンスを生成
- socket = server->nextPendingConnection();
- qDebug() << " connect";
- // 切断した時の処理を定義
- connect(socket, SIGNAL(disconnected()), socket, SLOT(deleteLater()));
- // 受信した時の処理を定義
- connect(socket, SIGNAL(readyRead()), this, SLOT(readData()));
- // エラー処理の定義
- connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)));
- // 切断したときの処理としてSLOT関数を設定する。
- connect(socket, SIGNAL(disconnected()), this, SLOT(deleteLater()));
- // クライアントのIPアドレスを取得
- QString ippaddr = socket->localAddress().toString();
- // 送信データをstreamとして定義
- QDataStream out(socket);
- out.setVersion(QDataStream::Qt_5_0);
- out << "Hello client\r\n";
- }
- void MyTcpServer::readData()
- {
- QString s;
- // 受信データを」streamとして定義
- //QDataStream in(socket);
- //in.setVersion(QDataStream::Qt_5_11);
- //in >> s;
- s = socket->readAll();
- qDebug() << "receive : " << s << "( " << s.size() << " ) ";
- if ("bye\r\n" == s)
- {
- qDebug() << "close process";
- //deleteLater();
- emit socket->disconnected();
- }
- }
- void MyTcpServer::socketError(QAbstractSocket::SocketError )
- {
- qDebug() << " socket error!";
- socket->close();
- }
- void MyTcpServer::deleteLater()
- {
- qDebug() << " socket disconnected";
- socket->close();
- }
さて、ここでこのサンプルを作った本題に入ります。前からLinuxでSocket(TCP)のプログラムで悩んでいたのが、通信しているプログラムの片方が、socketをつないだまま、異常終了すると、しばらくその時のportが使えなくなる現象に困っていました。一方、昔winsockを見ていた時、windowsのAPIには、portを強制的にconnectするというフラグがあり、どう違うのか疑問に思っていました。
このサンプルプログラムで、windowsとLinux(or Mac)での挙動の違いを調べてみました。
(1)通信中にServerを強制終了すると、Linux(Mac)ではやはり当該portは使えなくなります。(already useのエラーメッセージがでます)しかし、windowsは何事もなかったようにconnectできます。
(2)Linux(Mac)でも、client側が強制終了しても、サンプルのServerでは相手との通信が切れたことを検知して、portをcloseするようにしてあるので、再度clientは接続できます。(これまでLinuxでうまくいかなかったのは、この処理を入れてなかったことに気づきました)
Windowsは「楽観的」というか「危険」というか、考え方がかなり違います。