Qt:Qt实现Winsock网络编程—非阻塞模式下的简单远程控制的开发(WSAAsyncSelect)

Qt实现Winsock网络编程—非阻塞模式下的简单远程控制的开发(WSAAsyncSelect)

前言

这边博客应该是 Qt实现Winsock网络编程—TCP服务端和客户端通信(多线程) 的姐妹篇,上篇博客中的socket通信中所用的Windows api函数 都是阻塞函数,而一般图形界面编程中的UI进程一般不能阻塞,所以上篇博客 采用的是多线程,将接受连接请求 和 通信的socket处理都放在单独的后台线程中,这样就不会造成界面无响应的情况。当然Windows api和Linux一样也都提供了非阻塞模式的套接字开发,这样我们也就不需要多线程了,用单线程一样的可以很好的处理。下面我们就来看看吧。

界面效果

主要是客户端向服务端 发送一个命令,然后服务端进行相应的回应。当然要实现 多个客户端 和 一个服务端通信的效果塞,因为为 非阻塞模式,就可以不用多线程了。当然swap和restore命令可以控制服务器做出改变的。代码 没有什么太难的,需要有qt基础和查看MSDN的能力 即可实现。动手吧,编程 唯有实践。
Qt:Qt实现Winsock网络编程—非阻塞模式下的简单远程控制的开发(WSAAsyncSelect)_第1张图片

Windows 非阻塞模式设置

这里我们使用WSAAsyncSelect设置非阻塞模式的套接字,当然还有其他方式。WSAAsyncSelect是基于消息驱动机制的,在qt中通过重新nativeEvent来获取消息。

int WSAAPI WSAAsyncSelect(
  SOCKET s,	//指定要改变工作模式为非阻塞模式的套接字
  HWND   hWnd,	//发生网络事件时接受消息的窗口
  u_int  wMsg,	//发生网络事件时向窗口发送的消息,该消息为自定义消息
  long   lEvent	//指定应用程序感兴趣的通知码,可以是组合(FD_READ,FD_ACCEPT,FD_CONNECT,FD_CLOSE)
);

客户端代码

主要是nativeEvent函数的重写和非阻塞模式的设置,最后面有 完整代码下载路径。

bool Widget::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
    MSG* msg = (MSG*)message;
    switch(msg->message)
    {
    case WM_SOCK:
        //lParam的低位表示通知码
        //这段代码不明白,可以参考MSDN https://docs.microsoft.com/en-us/windows/desktop/api/winsock2/nf-winsock2-connect
        switch (WSAGETSELECTEVENT(msg->lParam)) {
        case FD_CONNECT:
        {
            //错误码 返回在lParam的高位
            int error = WSAGETSELECTERROR(msg->lParam);
            if(  WSAECONNREFUSED == error || WSAENETUNREACH == error ||WSAETIMEDOUT == error){//连接失败
                ui->lineEdit->setEnabled(true);
                ui->pushButton->setEnabled(true);
                qDebug() << "连接服务器失败";
                ui->listWidget->insertItem(0,QString("%1 连接服务器失败").arg(getTimeString()));
                //放开编辑框和按钮
                ui->lineEdit->setEnabled(true);
                ui->pushButton->setEnabled(true);
            }else{
                qDebug() << "连接服务器成功";
                ui->pushButton->setText("断开连接");
                ui->pushButton->setEnabled(true);

                ui->lineEdit_2->setEnabled(true);
                ui->pushButton_2->setEnabled(true);
                ui->listWidget->insertItem(0,QString("%1 连接服务器成功").arg(getTimeString()));
            }
        }
            break;
        case FD_READ:
            qDebug() << "收到服务端回的消息";
            memset(mBuf,0,4096);
            //收到服务器发送的消息了
            recv(mSocket,mBuf,sizeof(mBuf),0);
            ui->listWidget->insertItem(0,QString("%1 recv:[%2]").arg(getTimeString()).arg(mBuf));
            break;
        case FD_CLOSE:
            //服务端 套接字关闭了
            qDebug() << "服务端关闭了";
            ui->listWidget->insertItem(0,QString("%1 服务端断开连接了").arg(getTimeString()));
            break;
        }
        break;
    }
    //其他交给qt处理
    return QWidget::nativeEvent(eventType, message, result);
}

连接服务器

void Widget::on_pushButton_clicked()
{
    QString but = ui->pushButton->text();
    if(but.compare("断开连接") == 0){
        //断开连接
        qDebug() << "客户端 主动断开连接";
        closesocket(mSocket);//关闭套接字
        ui->listWidget->insertItem(0,QString("%1 客户端主动断开连接").arg(getTimeString()));
        ui->lineEdit->setEnabled(true);
        ui->lineEdit_2->setEnabled(false);
        ui->pushButton_2->setEnabled(false);
        ui->pushButton->setText("连接");
        return;
    }

    QString ip = ui->lineEdit->text();
    if(ip.isEmpty())
        return;
    ui->pushButton->setEnabled(false);
    ui->lineEdit->setEnabled(false);

    //创建连接套接字
    mSocket = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    //设置非阻塞模式,对 客户端连接到服务器 和 收到消息 通知码 感兴趣
    WSAAsyncSelect(mSocket,(HWND)winId(),WM_SOCK,FD_CONNECT|FD_READ|FD_CLOSE);

    mAddr.sin_addr.S_un.S_addr = inet_addr(ip.toUtf8().data());
    mAddr.sin_family = AF_INET;
    mAddr.sin_port = htons(8888);
    ::connect(mSocket,(sockaddr*)&mAddr,sizeof(mAddr));
}

服务端代码

nativeEvent函数

bool Widget::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
    MSG* msg = (MSG*)message;
    switch(msg->message)
    {
    case WM_SOCK:
        //发生错误统一处理
        if(WSAGETSELECTERROR(msg->lParam)){
            qDebug() << "发生错误";
            break;
        }
        switch (WSAGETSELECTEVENT(msg->lParam)) {
        case FD_ACCEPT:
        {
            qDebug() << "有新的客户端进行连接";

            sockaddr_in  *addr = new sockaddr_in;//传出参数
            int len;//传出参数
            SOCKET client =  accept(mListen,(sockaddr*)addr,&len);
            //通信套接字 对read和close感兴趣
            WSAAsyncSelect(client,(HWND)winId(),WM_SOCK,FD_READ | FD_CLOSE);

            //给客户端发送菜单信息
            char* ch = mHelp.toUtf8().data();
            send(client,ch,strlen(ch)+1,0);

            char* ipStr = inet_ntoa(addr->sin_addr);
            int port = ntohs(addr->sin_port);
            ui->listWidget->insertItem(0,QString("%1 [%2:%3] 连接成功").arg(this->getTimeString()).arg(QString(ipStr)).arg(port));
            //保存到全局对象中
            mClientMap.insert(client,addr);
        }
            break;
        case FD_READ:
        {
            memset(mBuf,0,4096);
            SOCKET client = (SOCKET)msg->wParam;
            sockaddr_in *addr = mClientMap.find(client).value();//msg->wParam保存的是发生网络事件的套接字
            char* ipStr = inet_ntoa(addr->sin_addr);
            int port = ntohs(addr->sin_port);
            recv(client,mBuf,4096,0);

            dealCommand(client);
            ui->listWidget->insertItem(0,QString("%1 [%2:%3]:%4").arg(this->getTimeString()).arg(ipStr).arg(port).arg(mBuf));
        }
            break;
        case FD_CLOSE:
            qDebug() << "客户端断开连接";
            SOCKET client = (SOCKET)msg->wParam;
            sockaddr_in *addr = mClientMap.find(client).value();//msg->wParam保存的是发生网络事件的套接字
            closesocket(client);
            mClientMap.remove(client);

            char* ipStr = inet_ntoa(addr->sin_addr);
            int port = ntohs(addr->sin_port);
            ui->listWidget->insertItem(0,QString("%1 [%2:%3] 断开连接").arg(this->getTimeString()).arg(ipStr).arg(port));
            break;
        }
    }
    //其他交给qt处理
    return QWidget::nativeEvent(eventType, message, result);
}

处理客服端发送过来的命令

//处理 客户端发送过来的命令
void Widget::dealCommand(SOCKET client){
    if(QString(mBuf).compare("help") == 0){
        char* ch = mHelp.toUtf8().data();
        send(client,ch,strlen(ch)+1,0);
    }else if(QString(mBuf).compare("swap") == 0){
        SwapMouseButton(true);
        char* ch = "swap命令执行成功";
        send(client,ch,strlen(ch)+1,0);
    }else if(QString(mBuf).compare("restore") == 0 ){
        SwapMouseButton(false);
        char* ch = "restore命令执行成功";
        send(client,ch,strlen(ch)+1,0);
    }else if(QString(mBuf).compare("getsysinfo") == 0){
        char buf[1024]{0};
        DWORD nsize;
        GetComputerNameA(buf,&nsize);
        QString info("\ncomputer name:%1\nuser name:%2\n");
        info  = info.arg(QString(buf));
        memset(buf,0,1024);
        GetUserNameA(buf,&nsize);
        info = info.arg(QString(buf));
        char* ch = info.toUtf8().data();
        send(client,ch,strlen(ch)+1,0);
    }else{
        //未知命令 回射回去
        send(client,mBuf,strlen(mBuf)+1,0);
    }
    return;
}

完整代码

完整代码下载路径或者GitHub最新代码。

你可能感兴趣的:(【Language_C++】,【Language_Qt,】,【Windows编程】)