Вопрос:
Я пытаюсь внедрить систему аутентификации с использованием С++/QtTcpSocket для личного проекта (многопользовательская шахматная игра).
Мой друг предложил метод проверки пользователя, но я хотел спросить, был ли более простой или лучший способ. Исходя из фона Python и в основном выполняющего этот проект для более глубокого понимания С++.
Я отправлю метод, предложенный моим другом, и попрошу, может быть, лучшее решение.
Он построил его в виде псевдокодов. Сервер в основном построен, теперь я надеюсь реализовать аутентификацию
* веселит
void process_packet(PACKET *pkt) { switch(pkt->PacketID) { case 0: // let say packet id 0 is the logon packet; packet contents are username and password { //let say packet size is 101 bytes; packet id was already received, so get the other 100 bytes unsigned char BUFFER[101] = {0}; // i always add an extra byte to the end of the buffer to allow for off-by-one errors ^_^ int result = recv_packet(pkt->cSocket, 100, BUFFER); if(result <= 0) return; // connection error; no packet data was received unsigned char *UserName = BUFFER+0; //+0 is not neccessary, but the username starts at the beginning. just getting the point across. unsigned char *PassWord = BUFFER+50; //side note: if we did «unsigned long *blah = BUFFER+4» or something, we would have to make sure the byte order is right. network byte order is BIG ENDIAN // WINDOWS byte order is LITTLE ENDIAN result = QueryDatabase(«SELECT username, password FROM chess_players WHERE username = ‘%s'», FILTER_INVALID_CHARS(UserName)); // check result unsigned char ServerResponse[2] = {0}; if(result[‘password’] == PassWord) { ServerResponse[0] = 1; // packet id will be 1. the next byte can be 1 or 0 to indicate logon success or failure. ServerResponse[1] = true; // so packet 0x0101 mean logon success, packet 0x0100 means logon failure send_packet(pkt->cSocket, ServerResponse, 2); } else { ServerResponse[0] = 1; ServerResponse[1] = false; send_packet(pkt->cSocket, ServerResponse, 2); } } break; default: { // received an unknown packet id; send a packet to the client that indicates an error_status_t unsigned char *ServerResponse[2] = {0}; ServerResponse[0] = 2; // packet id 2 means server error ServerResponse[1] = 0; // error code 0 means ‘unknown packet id’ send_packet(pkt_cSocket, ServerResponse, 2); } break; } delete pkt; // must delete pkt, was created with ‘new’ in get_client_packets() } Лучший ответ:
Если вы следовали за примерами успешного клиента/сервера, у вас должен быть QTcpServer (Rfserver) с подклассом QThread (Rfdevice, его экземплярная переменная называется thread в следующем коде), который содержит QTcpSocket (listenSocket).
Сказав, что в вашем классе сервера слушайте входящие подключения, моя настройка выглядит так:
void Rfserver::incomingConnection(int socketDescriptor){ if(thread){ //if thread exists, there is probably still an open connection if(thread->listenSocket){//if thread exists and the listenSocket is filled, there is definately an open connection if(thread->listenSocket->state() == QAbstractSocket::UnconnectedState){ //but alas, it could just be in the unconnected state, if so kill it. this->disconnect(); thread->terminate(); thread=0; connected=false; }//otherwise, do nothing, because the software is happily connected to a device } } if(!thread){ //if no thread exists, we are by no means connected thread = new rfdevice(socketDescriptor, this); //set up a new thread //this first connection communicates the string from your socket to the server parent…use it if you want. connect( thread, SIGNAL(RemoteButton(QString)),this,SLOT(remoteButton(QString)),Qt::BlockingQueuedConnection); connect( thread, SIGNAL(error(QTcpSocket::SocketError)),this,SLOT(tcpError(QTcpSocket::SocketError)),Qt::AutoConnection); connect( thread, SIGNAL(finished()), this, SLOT(threadZero())); //I have a threadZero function that deletes all the data then schedules the socket for deletion. thread->start(); connected=true; QString *welcome = new QString(«Enter your password:rn»); echoCommand(welcome); //this is a function you will implement that sends the welcome message to the pending device. } }
Итак, теперь, когда устройство пытается подключиться к серверу, устройство представлено с помощью «Enter your password:rn». Возможно, ваше устройство ответит на это с помощью пароля и имени пользователя. Но сторона Qt вещей будет выглядеть так:
/* FUNCTION:read this is a polling runloop that listens for data as long as the socket is connected or connecting. If a write is ever scheduled, it will be called from this runloop.. */ void Rfdevice::read(void){ while((listenSocket->state() == QAbstractSocket::ConnectedState) || (listenSocket->state() == QAbstractSocket::ConnectingState)){ //if there is data available to send write it to the socket if(dataToSend) this->write(); if(listenSocket->waitForReadyRead(50)) readBytes(); //wait for 50ms for data from the device //if there is ever data available to be read, read it. } }
Ваше устройство отвечает именем пользователя/паролем в формате username—passwordrn. Затем сокет делает это:
/* FUNCTION:readBytes this is like a callback function because it only gets called when there is data available for read. It basically converts the data to a string. */ void Rfdevice::readBytes(void){ QByteArray newData; newData = listenSocket->readAll(); QString *recieved = new QString(newData); QStringList userAndPass = recieved.split(«—«);//this is your delimiter QString username = userAndPass.at(0); QString password = userAndPass.at(1); //NOW, check the username and password vs your SQL or wherever it saved. }
Псевдокод довольно подробен в деталях. Надеюсь, вы можете собрать все это вместе! Дайте мне знать, если вам нужно больше кода.
Ответ №1
Это кажется скорее C-стильным, а не Qt-способом делать что-то.
На ваш вопрос нет общего ответа, но мои предложения заключаются в следующем:
Прослушать newConnection() сигнал QTcpServer. Ваш обработчик должен вызвать nextPendingConnection(), чтобы следующий клиент ожидал очереди. Первое, что вы сделаете, это, вероятно, ваша аутентификация пользователей.
После аутентификации вы сохраняете QTcpSocket в списке активных подключений.
Посмотрите, например. примеры удачных клиентов/серверов, как на самом деле писать/читать пакеты.
Вы также можете просмотреть операторы потока << для сериализации ваших объектов. Это намного проще и меньше подверженности ошибкам, чем метод низкого уровня, который вы опубликовали. Кроме того, QDataStream будет автоматически выполнять байтовые и сетевые байты.