QT学习之旅 - Winsock入门

文章目录

      • 服务端
        • 初始化
        • 为服务器创建套接字
        • sockaddr和sockaddr_in
        • inet_addr
        • htonl()、htons()、ntohl()、ntohs()四个函数
          • htonl()函数
          • htons()函数
          • ntohs()函数
          • ntohl()函数
          • 这些函数存在的意义(就是为了字节存放)
      • 绑定
        • getsockopt
        • udp主函数处理
      • 客户端

客户端和服务器应用程序的前几个步骤相同。

  • 关于服务器和客户端
  • 创建一个基本的Winsock应用程序
  • 正在初始化Winsock

服务端

  • 初始化Winsock。
  • 创建一个套接字。
  • 绑定套接字。
  • 听取客户端的套接字。
  • 接受来自客户端的连接。
  • 接收和发送数据。
  • 断开链接。

初始化

  1. 添加头文件
#include 
#include 
#include 
  1. 初始化Winsock
WSADATA wsaData;
  1. 调用WSAStartup并将其值作为整数返回并检查错误。
/*
0: 失败
非0:成功
*/
//WSAStartup(MAKEWORD(2,2), &wsaData)
if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
	//成功
    qDebug("winsock initialization FAILED.");
}

调用WSAStartup函数以启动WS2_32.dll的使用。

#pro文件
#...
LIBS += -lWs2_32
#...

完整初始化

UdpThread::UdpThread()
{//注意可以写在别的程序中,所以可以不用管线程
    //sockVersion = MAKEWORD(2,2);//声明调用不同的Winsock版本
    if (WSAStartup(MAKEWORD(2,2), &wsaData) != 0) {
        qDebug("winsock initialization FAILED.");
    }
}

为服务器创建套接字

初始化之后,必须实例化SOCKET对象以供服务器使用。

getaddrinfo函数用于确定sockaddr结构中的值:

  • AF_INET用于指定IPv4地址族。
  • SOCK_STREAM用于指定流套接字。
  • IPPROTO_TCP用于指定TCP协议。
  • AI_PASSIVE标志表示调用者打算在调用bind函数时使用返回的套接字地址结构。 当设置AI_PASSIVE标志并且getaddrinfo函数的nodename参数是NULL指针时,套接字地址结构的IP地址部分对于IPv4地址设置为INADDR_ANY,对于IPv6地址设置为IN6ADDR_ANY_INIT。

创建套接字

SOCKET sockWIN;
//udpThread->sockWIN: SOCKET sockWIN;
if(udpThread->sockWIN != INVALID_SOCKET){//INVALID_SOCKET:检查错误以确保套接字是有效的套接字
	closesocket(udpThread->sockWIN);//关闭套接字
}
/*
AF_INET:IPv4
SOCK_DGRAM:  是无保障的面向消息的socket,主要用于在网络上发广播信息(UDP)
SOCK_STREAM: 是有保障的(即能保证数据正确传送到对方)面向连接的SOCKET,多用于资料(如文件)传送。(TCP)
IPPROTO_UDP: 用于指定UDP协议
*/
udpThread->sockWIN = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(udpThread->sockWIN == INVALID_SOCKET){//INVALID_SOCKET:检查错误以确保套接字是有效的套接字
	QMessageBox::critical(this, tr("错误"), tr("无法创建 Socket!"));//显示提示
    return;
}

sockaddr和sockaddr_in

sockaddr在头文件#include 中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,如下:

struct sockaddr {  
    sa_family_t sin_family;//地址族
	char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               
}; 

sockaddr_in在头文件#include或#include 中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:

struct sockaddr_in {
	short	sin_family;				//地址组(Address Family)
	u_short	sin_port;				//16位TCP/UDP端口号
	struct in_addr	sin_addr;		//32位IP地址
	char	sin_zero[8];			//不使用
};

sin_portsin_addr都必须是网络字节序(NBO),一般可视化的数字都是主机字节序(HBO)。

inet_addr

ip地址转换函数

htonl()、htons()、ntohl()、ntohs()四个函数

htonl()函数

函数原型是:uint32_t htonl(uint32_t hostlong)
其中,hostlong是主机字节顺序表达的32位数,htonl中的h–host主机地址,to–to,n–net网络,l–unsigned long无符号的长整型(32位的系统是4字节)
函数返回值是一个32位的网络字节顺序;
函数的作用是将一个32位数从主机字节顺序转换成网络字节顺序。

htons()函数

函数原型是:uint16_t htons(uint16_t hostlong)
其中,hostlong是主机字节顺序表达的16位数,htons中的h–host主机地址,to–to,n–net网络,s–signed long无符号的短整型(32位的系统是2字节)
函数返回值是一个16位的网络字节顺序;
函数的作用是将一个16位数从主机字节顺序转换成网络字节顺序,简单的说就是把一个16位数高低位呼唤。

ntohs()函数

函数原型是:uint16_t ntohs(uint16_t hostlong)
其中,hostlong是网络字节顺序表达的16位数,ntohs中的,n–net网络,to–toh–host主机地址,s–signed long有符号的短整型(32位的系统是2字节)
函数返回值是一个16位的主机字节顺序;
函数的作用是将一个16位数由网络字节顺序转换为主机字节顺序,简单的说就是把一个16位数高低位互换。

ntohl()函数

函数原型是:uint32_t ntohs(uint32_t hostlong)
其中,hostlong是网络字节顺序表达的32位数,ntohs中的,n–net网络,to–toh–host主机地址,s–unsigned long无符号的短整型(32位的系统是4字节)
函数返回值是一个32位的主机字节顺序;
函数的作用是将一个32位数由网络字节顺序转换为主机字节顺序。

这些函数存在的意义(就是为了字节存放)

说到这部分需要引入字节存放的两个概念一个是“大端顺序”,一个是“小端顺序”。俗称“小尾顺序”、“大尾顺序”。
简单的说就是对应数据的高字节存放在低地址,低字节存放在高地址上就是大端顺序,对应数据的高字节存放在高地址,低字节存放在低地址上就是小端顺序。

绑定

if (bind(udpThread->sockWIN, (sockaddr *)&udpThread->locAddr,
             sizeof(udpThread->locAddr)) == SOCKET_ERROR)
    {//绑定错误
        closesocket(udpThread->sockWIN);
        udpThread->sockWIN = INVALID_SOCKET;
        QMessageBox::critical(this, tr("错误"), tr("端口无法打开或被占用!"));
        return;
    }

getsockopt

//获取一个套接字
int getsockopt(int socket, int level, int option_name,
            void *restrict option_value, socklen_t *restrict option_len);

socket:文件描述符

  • level:协议层次
    • SOL_SOCKET 套接字层次
    • IPPROTO_IP ip层次
    • IPPROTO_TCP TCP层次
  • option_name:选项的名称(套接字层次)
    • SO_BROADCAST 是否允许发送广播信息
    • SO_REUSEADDR 是否允许重复使用本地地址
    • SO_SNDBUF 获取发送缓冲区长度
    • SO_RCVBUF 获取接收缓冲区长度
    • SO_RCVTIMEO 获取接收超时时间
    • SO_SNDTIMEO 获取发送超时时间
  • option_value:获取到的选项的值
  • option_len:value的长度

返回值:
成功:0
失败:-1

getsockopt(udpThread->sockWIN, SOL_SOCKET, SO_RCVBUF, (char*)&optVal, &optLen);

udp主函数处理

//1. 来约束是否开启关闭线程
static bool isNetOpen = false;
NET_OPEN = false;
if(isNetOpen == false){
//ui界面样式,用来提醒可以不用管
        if(ui->pushButton_openNet->text() == "打开网络"){
            ui->pushButton_openNet->setText(tr("关闭网络"));
            ui->pushButton_openNet->setStyleSheet("QPushButton{background:yellow}");
        }else if(ui->pushButton_openNet->text() == "关闭网络"){
            ui->pushButton_openNet->setText(tr("打开网络"));
            ui->pushButton_openNet->setStyleSheet("QPushButton{background:none}");
        }
        //2.套接字赋值
        udpThread->sockWIN = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
        //udpThread->sockWIN: SOCKET sockWIN;
        //3.检查套接字是否正确
        if(udpThread->sockWIN != INVALID_SOCKET){//INVALID_SOCKET:检查错误以确保套接字是有效的套接字
            closesocket(udpThread->sockWIN);//关闭套接字
        }
        //4.创建socket是否成功
        if(udpThread->sockWIN == INVALID_SOCKET){//INVALID_SOCKET:检查错误以确保套接字是有效的套接字
            QMessageBox::critical(this, tr("错误"), tr("无法创建 Socket!"));//显示提示
            return;
        }

        ConfigDialog::Settings p = setting->settings(); //里面就只是本地和对方ip端口组播的值,可以修改成自己看的懂的

		//5.设置本地ip地址端口
        udpThread->locAddr.sin_family = AF_INET;
        udpThread->locAddr.sin_addr.S_un.S_addr = inet_addr(p.locIP.toStdString().data());
        udpThread->locAddr.sin_port = htons(p.locPort.toUInt());
		//6.设置对方ip地址端口
        udpThread->rmtAddr.sin_family = AF_INET;
        udpThread->rmtAddr.sin_addr.S_un.S_addr = inet_addr(p.DevIP.toStdString().data());
        udpThread->rmtAddr.sin_port = htons(p.DevPort.toUInt());
		//7. 绑定套接字,地址
        if (bind(udpThread->sockWIN, (sockaddr *)&udpThread->locAddr,
                 sizeof(udpThread->locAddr)) == SOCKET_ERROR)
        {//判断绑定是否出错
            closesocket(udpThread->sockWIN);
            udpThread->sockWIN = INVALID_SOCKET;
            QMessageBox::critical(this, tr("错误"), tr("端口无法打开或被占用!"));
            return;
        }
        //8.获取套接字,把套接字中的数据清0
        /* set socket buffer size */
        int optVal = 0;
        int optLen = sizeof(optVal);
        getsockopt(udpThread->sockWIN, SOL_SOCKET, SO_RCVBUF, (char*)&optVal, &optLen); //获取套接字,并获取接收缓冲区长度
        //9.获取套接字,把套接字中的数据设置为8M大小
        optVal = 8*1024*1024;//8M大小
    #if USE_TEST//只是为了测试,可以定义#define USE_TEST 1打开
        int testRes = setsockopt(udpThread->sockWIN, SOL_SOCKET, SO_RCVBUF, (char*)&optVal, optLen);//获取套接字,并获取接收缓冲区长度
    #else
        setsockopt(udpThread->sockWIN, SOL_SOCKET, SO_RCVBUF, (char*)&optVal, optLen);//获取套接字,并获取接收缓冲区长度
    #endif
        connect(udpThread, SIGNAL(recvPingTop(quint32,quint32,quint32)), this, SLOT(plotPingTop(quint32,quint32,quint32)),
            Qt::QueuedConnection);
        connect(udpThread, SIGNAL(recvRmsTop()), this, SLOT(plotRmsTop()),
            Qt::QueuedConnection);
    #if USE_TEST	//只是为了测试,可以定义#define USE_TEST 1打开
        qDebug() << "testRes:" << testRes; //0:连接成功
    #endif
        //显示状态
        setWindowTitle(QString("XHCJTest 杭州矢志信息科技有限公司 -")+p.locIP);
        showStatus(tr("连接已建立,本地:%1:%2,组播:%3,存储路径:%4;")
                          .arg(p.locIP).arg(p.locPort)
                          );//显示本地组播存储地址,这里只有本地

        /*初始化*/
		//自己自行添加可以是memset(x,0,sizeof(x));
		//10.开启线程
        udpThread->start();
    }else {
    	//10.或者关闭线程
        udpThread->stop();
    }

客户端

  • 初始化Winsock。
  • 创建一个套接字。
  • 连接到服务器。
  • 发送和接收数据。
  • 断开链接。

你可能感兴趣的:(#,Qt,qt,学习,开发语言)