这边博客应该是 Qt实现Winsock网络编程—TCP服务端和客户端通信(多线程) 的姐妹篇,上篇博客中的socket通信中所用的Windows api函数 都是阻塞函数,而一般图形界面编程中的UI进程一般不能阻塞,所以上篇博客 采用的是多线程,将接受连接请求 和 通信的socket处理都放在单独的后台线程中,这样就不会造成界面无响应的情况。当然Windows api和Linux一样也都提供了非阻塞模式的套接字开发,这样我们也就不需要多线程了,用单线程一样的可以很好的处理。下面我们就来看看吧。
主要是客户端向服务端 发送一个命令,然后服务端进行相应的回应。当然要实现 多个客户端 和 一个服务端通信的效果塞,因为为 非阻塞模式,就可以不用多线程了。当然swap和restore命令可以控制服务器做出改变的。代码 没有什么太难的,需要有qt基础和查看MSDN的能力 即可实现。动手吧,编程 唯有实践。
这里我们使用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最新代码。