基于TCP/IP协议的网络摄像头的QT项目

目录

项目简述:

1.服务器

 步骤一:首先搭建一个基本的服务器框架。

 1.初始化服务器的函数主体

 2.等待连接

步骤二:数据库的使用,本次项目使用的Sqlite3数据库

1.数据库初始化

2.登录时使用数据库 

3.注册时使用数据库

步骤三:摄像头的调用与数据传输

1.V4L2框架的使用

2.实现图像的发送

步骤四:实现服务器的并发

2.客户端

步骤一:实现登录

步骤二:实现服务器采集的图像的显示

1.准备

2.实现对应的槽函数

3.运行效果



项目简述:

        本项目是基于TCP/IP协议进行开发,使用QT写了一个客户端,在Ubuntu上使用C语言写了一个TCP服务器,同时依仗V4L2框架来调用本机的摄像头,将摄像头采集到的数据传输至QT客户端,在客户端的界面上显示。同时出于项目完整性和实用性考虑,笔者还在客户端添加了登录界面,服务器也写了对应的匹配机制,用户可以注册,登录本系统,此外还添加了一个天气查询系统,方便用户能实时获取天气情况。接下来笔者就分版块讲解一下本项目的构成。

        首先我们先从服务器讲起,首先本项需要使用TCP/IP协议,所以Ubuntu需要保持网络通畅,至于网络的配置,笔者就不再赘述,可以自行度娘。 

1.服务器

 步骤一:首先搭建一个基本的服务器框架。

 1.初始化服务器的函数主体

//服务器初始化*************************************************************************
int tcpinit(int port)
{
    int sockfd  = socket(AF_INET, SOCK_STREAM, 0);        //创建套接字
    if(sockfd < 0)
    {
        perror("socket");
        return -1;
    }
       
    printf("套接字创建成功----------\n"); 
    
    //设置套接字允许端口重用 
    int on = 1; //定义int整形变量,保存端口重用的开关,1:开 0:关
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)
    {
        perror("setsockopt");
        return -1;
    }
      
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(port);
    seraddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    if(bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr)) < 0)    //绑定本机ip和端口号
    {
        perror("bind");
        return -1;
    }

    printf("绑定成功----------------\n");

    if(listen(sockfd, 1024) < 0)    //监听
    {
        perror("listen");
        return -1;
    }
    printf("监听成功----------------\n");
    return sockfd;
}

 2.等待连接

//tcp等待连接**************************************************************************
int tcpwait(int conn_fd)
{	
		struct sockaddr_in cliaddr;
    	socklen_t len = sizeof(cliaddr);
        connfd = accept(conn_fd, (struct sockaddr *)&cliaddr, &len);    //等待连接,每当有一个客户端连接时,都会生成一个新的连接套接字
        if(connfd < 0)
        {
            perror("accept");
            return -1;
        }

        printf("ip:%s--port:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
        return connfd;
}

        按正确的创建顺序依次调用这些函数就能实现一个基本的服务器搭建了。

步骤二:数据库的使用,本次项目使用的Sqlite3数据库

1.数据库初始化

//数据库****************************************************************************
int sqliteInit()
{
	//1、打开或者创建数据库文件,得到数据库的句柄指针
	
	// SQLITE_OK == 0
	if( sqlite3_open("./yy.db", &db) != SQLITE_OK)
	{
		printf("open: %s\n", sqlite3_errmsg(db));
		return -1;
	}
	//创建数据库表
	char sql[128] = "create table if not exists user(账号 varchar , 密码 varchar);";
	char *errmsg = NULL;
	//执行SQL 语句
	if( sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){
		printf("create: %s\n", errmsg);
		return -1;
	}
}

        首先要打开或者创建数据库文件,要得到数据库的句柄指针,在示例中笔者并未写出,其实是写在了头文件中,因为在客户端与服务器登录过程的数据交互中,要频繁的调用数据库,而数据库的唯一识别特征就是句柄指针,将句柄指针放于头文件中可使得所有需要使用数据库的文件访问句柄指针。执行sqlite3_open函数后会在服务器本地创建一个数据库文件,笔者取名为yy.db。然后紧接着执行sql语句,在数据库中创建一个名为user的表用于存放客户端注册的用户数据。数据库初始化完成后就可以正常使用数据库了。

2.登录时使用数据库 

int login(int connfd)
{
	char *errmsg = NULL;
    char **result;
    int R = 0;
    int C = 0;
    char lgbuf[32] = {0};
	struct Info info;
	
	read(connfd, lgbuf, sizeof(lgbuf));        	//接受消息,应该接收到账号密码
	
	sscanf(lgbuf, "%s %s", info.id, info.pw);
	printf("id= %s\n",info.id);
	printf("pw= %s\n",info.pw);
	printf("log\n");
	
	char sql[128] = "select *from user;";
	if(sqlite3_get_table(db,sql,&result,&R,&C,&errmsg) != SQLITE_OK)
    {
        printf("gettable:%s\n",errmsg);
        return -1;
    }
    int k = C;
    int m;
    int flag = 1;
    
    for(int i = 0;i < R;i++)
    {
        if(strcmp(result[k],info.id) == 0)
        {
            printf("user:%s\n",result[k]);
            printf("user right\n");

            m = k + 1;    
            if(strcmp(result[m],info.pw) == 0)
            {
                printf("pwd:%s\n",result[m]);
                printf("pswd right\n");
                
                data[3] = 0x00;
                write(connfd, data, 32);   //发送给客户端判断用户名密码是否正确
                
                flag = 0;
                return 2;
            }
            else
            {
            	data[3] = 0x02;
               	if(write(connfd, data, 32) < 0)
				{
					printf("login fail\n");
					continue;
				} 
            }
        }
        k += 2;    //向后偏移一行数据
    }
    printf("flag = %d\n",flag);

    if(flag == 1)
    {
    	data[3] = 0x01;
        if(write(connfd, data, 32) < 0)
		{
			printf("进入注册\n");		//发送给客户端判断是否需要注册或者用户名错误
		}	 
		return 1;
    }

}

        首先,当客户端与服务器成功连接时,客户端就发送一个名为登录的信号做判断,进而使得服务器这端成功进入登录函数中,登录函数中最重要的部分就是在数据库中查询用户数据,要查询用户数据就要执行查询的sql语句,将说有结果查出,使用sqlite3_get_table()函数可以将查询带的之返回出来,并放入了result中进行保存,再将其中的值与接收到的账号进行比对,如果账号正确才能进行下一步,也就是密码的比对工作。值得注意的是,sqlite3数据库存储数据,第一行是字段名,所以遍历时应从第二行开始,并且是现将每行的数据存满后才存储下一行,笔者只设置了两个字段,所以在循环遍历时,需要将位置向后偏移两个位置才能访问到下一个对应字段的数据。如果在遍历账户时就未找到数据,那就返回一个值,用于服务器做决策判断,并向客户端发送一个信息,告诉客户端没有该用户。

3.注册时使用数据库

//注册过程**********************************************************************
int regist(int connfd)
{
	struct Info info;
	char buf[32] = {0};
	char sql1[128] = {0};
    char *errmsg1 = NULL;
    char **result;
    char *errmsg2 = NULL;
    int R = 0;
    int C = 0;
    int i;
    char sql2[128] = "select * from user;";
    while(1)
    {
		read(connfd, buf, sizeof(buf));        	//接受消息,应该接收到需要注册的账号密码
		
		sscanf(buf, "%s %s", info.id, info.pw);
		printf("id= %s\n",info.id);
		printf("pw= %s\n",info.pw);
		
		printf("nuser: %s\n",info.id);
		printf("npwd : %s\n",info.pw);
		if(sqlite3_get_table(db,sql2,&result,&R,&C,&errmsg1) != SQLITE_OK)
		{
		    printf("gettable:%s\n",errmsg1);
		    return -1;
		}
		int k = C;
		printf("R*C = %d\n",R*C);
		int flag = 1;

		for(i = 0;i < R;i++)
		{
		    if(strcmp(result[k],info.id) == 0)
		    {   
		    	data[3] = 0x05;
		        write(connfd,data,32);  //发送给客户端判断用户是否存在
		        flag = 0;
		    }
		    k += 2;
		}
		if(flag == 0)
		{
			return 3;
			break;
		}
		
		if(flag == 1)
		{
		    sprintf(sql1,"insert into user(账号, 密码)values('%s','%s');",info.id, info.pw);
		    if(sqlite3_exec(db,sql1,NULL,NULL,&errmsg2) != SQLITE_OK)
		    {
		        printf("insert:%s\n",sqlite3_errmsg(db));
		        return -1;
		    }
			data[3] = 0x06;
		    write(connfd,data,32);  //发送给客户端判断是否注册成功
		    return 3;
		}
    }
	printf("regist\n");
}

        在登陆过程中数据库未检测到用户账号信息后,客户端就该进行注册操作了,此时服务器任将首先接收到客户端的账户信息,步骤和登陆过程是基本一样,但是不同的是,在遍历完账号信息以后,如果发现待注册账号与数据库本身已有账号相同时,发送一个信息给客户端,告诉客户端此账户已存在,如果确定是未注册过的账户就可以进行注册,也就是执行插入信息的sql语句,插入完成后发送一个消息告诉客户端注册成功。

步骤三:摄像头的调用与数据传输

1.V4L2框架的使用

int camera_init(char *devpath, unsigned int *width, unsigned int *height, unsigned int *size)
{
	int i;
	int fd = -1;;
	int ret;
	struct v4l2_buffer vbuf;
	struct v4l2_format format;
	struct v4l2_capability capability;
	/*open 打开设备文件*/
	if((fd = open(devpath, O_RDWR)) == -1){
        perror("camera_init open");
		return -1;		
	}
	/*ioctl 查看支持的驱动*/
	ret = ioctl(fd, VIDIOC_QUERYCAP, &capability);
	if (ret == -1) {
		perror("camera->init");
		return -1;
	}
	/*判断设备是否支持视频采集*/
	if(!(capability.capabilities & V4L2_CAP_VIDEO_CAPTURE)) {
		fprintf(stderr, "camera->init: device can not support V4L2_CAP_VIDEO_CAPTURE\n");
		close(fd);
		return -1;
	}
	/*判断设备是否支持视频流采集*/
	if(!(capability.capabilities & V4L2_CAP_STREAMING)) {
		fprintf(stderr, "camera->init: device can not support V4L2_CAP_STREAMING\n");
		close(fd);
		return -1;
	}
	/*设置捕获的视频格式 MYJPEG*/
	memset(&format, 0, sizeof(format));
	format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;			    //永远都是这个类型
	format.fmt.pix.pixelformat = V4L2_PIX_FMT_MJPEG;		//设置采集图片的格式
	format.fmt.pix.width = *width;
	format.fmt.pix.height = *height;
	format.fmt.pix.field = V4L2_FIELD_ANY;				//设置图片一行一行的采集
	ret = ioctl(fd, VIDIOC_S_FMT, &format);				//ioctl	是设置生效
	if(ret == -1)
		perror("camera init");
	else {
		fprintf(stdout, "camera->init: picture format is mjpeg\n");
	}

	ret = ioctl(fd, VIDIOC_G_FMT, &format);
	if (ret == -1) {
		perror("camera init");
		return -1;
	}

	/*向驱动申请缓存*/
	memset(&reqbufs, 0, sizeof(struct v4l2_requestbuffers));
	reqbufs.count	= REQBUFS_COUNT;					//缓存区个数
	reqbufs.type	= V4L2_BUF_TYPE_VIDEO_CAPTURE;
	reqbufs.memory	= V4L2_MEMORY_MMAP;					//设置操作申请缓存的方式:映射 MMAP
	ret = ioctl(fd, VIDIOC_REQBUFS, &reqbufs);			
	if (ret == -1) {	
		perror("camera init");
		close(fd);
		return -1;
	}
	/*循环映射并入队*/
	for (i = 0; i < reqbufs.count; i++){
		/*真正获取缓存的地址大小*/
		memset(&vbuf, 0, sizeof(struct v4l2_buffer));
		vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vbuf.memory = V4L2_MEMORY_MMAP;
		vbuf.index = i;
		ret = ioctl(fd, VIDIOC_QUERYBUF, &vbuf);
		if (ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		}
		/*映射缓存到用户空间,通过mmap讲内核的缓存地址映射到用户空间,并切和文件描述符fd相关联*/
		bufs[i].length = vbuf.length;
		bufs[i].start = mmap(NULL, vbuf.length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, vbuf.m.offset);
		if (bufs[i].start == MAP_FAILED) {
			perror("camera init");
			close(fd);
			return -1;
		}
		/*每次映射都会入队,放入缓冲队列*/
		vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
		vbuf.memory = V4L2_MEMORY_MMAP;
		ret = ioctl(fd, VIDIOC_QBUF, &vbuf);
		if (ret == -1) {
			perror("camera init");
			close(fd);
			return -1;
		}
	}
	/*返回真正设置成功的宽.高.大小*/
	*width = format.fmt.pix.width;
	*height = format.fmt.pix.height;
	*size = bufs[0].length;

	return fd;
}


int camera_start(int fd)
{
	int ret;

	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	/*ioctl控制摄像头开始采集*/
	ret = ioctl(fd, VIDIOC_STREAMON, &type);
	if (ret == -1) {
		perror("camera->start");
		return -1;
	}
	fprintf(stdout, "camera->start: start capture\n");

	return 0;
}

int camera_dqbuf(int fd, void **buf, unsigned int *size, unsigned int *index)
{
	int ret;
	fd_set fds;
	struct timeval timeout;
	struct v4l2_buffer vbuf;

	while (1) {
		FD_ZERO(&fds);
		FD_SET(fd, &fds);
		timeout.tv_sec = 2;
		timeout.tv_usec = 0;
		ret = select(fd + 1, &fds, NULL, NULL, &timeout);	//使用select机制,保证fd有图片的时候才能出对
		if (ret == -1) {
			perror("camera->dbytesusedqbuf");
			if (errno == EINTR)
				continue;
			else
				return -1;
		} else if (ret == 0) {
			fprintf(stderr, "camera->dqbuf: dequeue buffer timeout\n");
			return -1;
		} else {
			vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
			vbuf.memory = V4L2_MEMORY_MMAP;
			ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);	//出队,也就是从用户空间取出图片
			if (ret == -1) {
				perror("camera->dqbuf");
				return -1;
			}
			*buf = bufs[vbuf.index].start;
			*size = vbuf.bytesused;
			*index = vbuf.index;

			return 0;
		}
	}
}

int camera_eqbuf(int fd, unsigned int index)
{
	int ret;
	struct v4l2_buffer vbuf;

	vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vbuf.memory = V4L2_MEMORY_MMAP;
	vbuf.index = index;
	ret = ioctl(fd, VIDIOC_QBUF, &vbuf);		//入队
	if (ret == -1) {
		perror("camera->eqbuf");
		return -1;
	}

	return 0;
}

int camera_stop(int fd)
{
	int ret;
	enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

	ret = ioctl(fd, VIDIOC_STREAMOFF, &type);
	if (ret == -1) {
		perror("camera->stop");
		return -1;
	}
	fprintf(stdout, "camera->stop: stop capture\n");

	return 0;
}

int camera_exit(int fd)
{
	int i;
	int ret;
	struct v4l2_buffer vbuf;
	vbuf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
	vbuf.memory = V4L2_MEMORY_MMAP;
	for (i = 0; i < reqbufs.count; i++) {
		ret = ioctl(fd, VIDIOC_DQBUF, &vbuf);
		if (ret == -1)
			break;
	}
	for (i = 0; i < reqbufs.count; i++)
		munmap(bufs[i].start, bufs[i].length);
	fprintf(stdout, "camera->exit: camera exit\n");
	return close(fd);
}

       V4L2 是专门为 linux 设备设计的一套视频框架,其主体框架在 linux 内核,可以理解为是整个 linux 系统上面的视频源捕获驱动框架。其广泛应用在嵌入式设备以及移动端、个人电脑设备上面,市面上的编码产品类如:SDV、手机、IPC、行车记录仪都会用到这个框架来进行视频采集。此次项目借用了这样一个完整的代码文件,主要是将采集到的图像进行了入队出队的操作,笔者能力有限,如果想深入了解V4L2框架请自行度娘。

//摄像头************************************************************
int cam_init()
{
	int cam_fd = camera_init("/dev/video0", &width, &height, &s);
	if(0 > cam_fd)
	{
		perror("cam_fd");
		return -1;
	}
	printf("w:%d\nh:%d\nsize:%d\n",width,height,s);
	
	if(0 > camera_start(cam_fd))
	{
		perror("cam_start");
		return -1;
	}
	return cam_fd;
}

int pic_send(int camfd ,int fd)
{		
	char *buf;
	int len;
	char lenstr[20]={0};

	camera_dqbuf(camfd,(void **)&buf,&len, &index1);
	
	sprintf(lenstr,"%d",len);
	
	write(fd,lenstr,sizeof(lenstr));

	int ret = write(fd,buf,len);
	if(ret < ret)
	{
		perror("camwrite");
	}
	//printf("len = %d ret = %d\n", len, ret);
	camera_eqbuf(camfd, index1);
}

        为了方便理解切需要与客户端通信,笔者在这里定义两个函数,通过调用框架中的初始化设置函数来初始化摄像头,最后返回一个关于摄像头的文件描述符。其次还定义了一个函数用于图像的发送,依旧是带用了框架中图片的入队出队函数实现发送图像至客户端。

2.实现图像的发送

int run_pic_send(int arg)
{
	int connfd = arg;
	char buf[4];
	int camfd = cam_init();
	while(1)
	{
		if(camflag == 0)
		{
			break;
		}
		else
		{
			pic_send(camfd,connfd);
		}
	}
	camera_stop(camfd);
	camera_exit(camfd);
}
   

      其实就一个函数,在函数被调用时初始化摄像头,循环的发送队列中的图片,如果客户端发出关闭摄像头的信息,服务器将关闭摄像头。

步骤四:实现服务器的并发

void *getData(void *arg)
{
    char getbuf[32] = {0};
    connfd = *(int *)arg;
    while(1)
    {
        int ret = read(connfd, getbuf, sizeof(getbuf));		//接收消息,应该接收到“Login”
        printf("getbuf: %s\n", getbuf);
        if(ret < 0)
        {   
            perror("read");
            break;
        }
        
       	if(ret == 0)
        {
            printf("client quit------------\n");
            break;
        }
        
        if(NULL != strstr(getbuf, "Register"))
        {
        	printf("注册\n");
        	int ret_reg = regist(connfd);
        	printf("ret_reg= %d\n",ret_reg);		//调用注册函数 
        	if(ret_reg == 3){
        		continue;
        	}
        }
        
        if(NULL != strstr(getbuf, "Login"))
        {
        	int ret_login = login(connfd);	// 调用登陆函数
        	
        	printf("ret_login=%d\n", ret_login);
        	
        	if(ret_login == 1)
        	{	
        		memset(getbuf, 0, sizeof(getbuf));
        		continue;	
        	}
        }
		
		if(NULL != strstr(getbuf, "OPEN_CAM"))
		{
			camflag = 1;
			int ret_cam_exit = run_pic_send(connfd);
			if(ret_cam_exit == 4)
			{
				continue;
			}
			memset(getbuf, 0, sizeof(getbuf));
		}
		
		if(NULL != strstr(getbuf, "CLOSE_CAM"))
		{
			camflag = 0;
			memset(getbuf, 0, sizeof(getbuf));
			continue;
		}
    	printf("@@@@@@@@@\n");
    }   
    close(connfd);
    //退出子线程 
    pthread_exit(NULL);
}

        本次项目笔者使用的是创建线程来实现服务器的并发,所以我们需要创建一个函数来调用其他的所有功能函数,将这个函数作为主体函数传入线程创建函数中,这样就可以实现服务器的并发。

2.客户端

步骤一:实现登录

         首先我们可以在ui拖拽界面上先快速搭建一个登录界面的主要框架,两个行编辑框用于显示账号密码,两个普通按钮用于触发相应功能的槽函数。

    w2 = NULL;
    this->setFixedSize(this->width(), this->height());
    this->setWindowTitle("登录:)");
    sockfd = new QTcpSocket(this);
    sockfd->connectToHost("192.168.219.148", 8081);
    sockfd->waitForConnected(2000);
    if(sockfd->state() == QAbstractSocket::UnconnectedState)
    {
        qDebug() << "连接服务器失败!请查看网络";
        return;
    }
    else
    {
        qDebug() << "连接成功!!!!!";
        connect(ui->LoginButton, SIGNAL(clicked()), this, SLOT(LoginHandle()));
        connect(sockfd, SIGNAL(readyRead()), this, SLOT(RecvHandle()));
    }

        首先创建套接字,连接服务器ip和端口,再触发对应的条件时,打印对应的语句,在登陆成功后调用槽函数。

void Widget::LoginHandle()
{
    QString user = ui->UserEdit->text();
    QString pw = ui->PwEdit->text();
    QString sign = "Login";
    sockfd->write(sign.toStdString().c_str(), 32);
    QString send_info = user+" "+pw;
    sockfd->write(send_info.toStdString().c_str(), 32);
    send_info.clear();
    flag = 0;
}

        然后就是实现登录时的数据交互,这里我们写一个槽函数,我们主要做的就是将行编辑框中的信息发送给服务器,用sockfd这个类中的write成员函数实现发送。


void Widget::RecvHandle()
{
        //点击登录时flag= 0
        if(!flag)
        {
            QByteArray data =  sockfd->readAll();
            qDebug()<<data;
            {
                if((uchar)data[3] == 0x00)
                {
                    this->hide();
                    w2 = new Form();
                    w2->show();

                    flag=1;
                    return;
                }
                else if((uchar)data[3] == 0x01)
                {
                    QMessageBox::warning(this, "提示", "没有该用户,请注册!");
                }
                else if((uchar)data[3] == 0x02)
                {
                    QMessageBox::warning(this, "提示", "账号或密码错误!");
                }
                data.clear();
            }
        }
        else
        {
            //点击注册时,flag = 1
            QByteArray data = sockfd->readAll();
            if((uchar)data[0] == 0xff)
            {
                if((uchar)data[3] == 0x05)
                {
                    QMessageBox::warning(this, "提示", "用户已存在,请重新输入!");
                }
                else if((uchar)data[3] == 0x06)
                {
                    QMessageBox::warning(this, "提示", "注册成功!");
                    flag1 = 1;
                }
            }
            data.clear();
        }
}

        当服务器接收到账号密码后回发信号用作客户端的判断,当登陆成功以后将登录界面隐藏,然后将第二个界面显示出来,这样就实现了简单的登录功能。

步骤二:实现服务器采集的图像的显示

1.准备

        首先依旧是通过拖拽搭建一个界面,使用lable标签显示摄像头画面。

    manager = new QNetworkAccessManager(this);  //新建QNetworkAccessManager对象

    camfd = new QTcpSocket(this);
    camfd->connectToHost("192.168.1.21", 8081);
    camfd->waitForConnected(2000);

    if(camfd->state() == QAbstractSocket::UnconnectedState)
    {
        qDebug() << "连接服务器失败!请查看网络";
        return;
    }else{
        qDebug() << "连接成功!!!!!";
    }
        connect(camfd, SIGNAL(readyRead()), this, SLOT(myrecv()));
        connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));//关联信号和槽
        connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(checkW()));//关联信号和槽
        //一旦服务器有消息发送过来,就会触发readyRead()信号 --> myrecv();

         先把需要的对象new出来,因为我们需要查询天气,所以首先new一个manager,用于查询天气,在new一个连接套接字,当连接套接字读取到信息就发出信号,调用槽函数myrecv(),当manager连接完成后发送一个信号,调用replyFinished()槽函数,当按钮点击触发chekw函数。

2.实现对应的槽函数

void Form::myrecv()
{
    if(flag)
    {
        if(camfd->bytesAvailable() < 20){
            return;
        }
        camfd->read(arr,20);
        len = atoi(arr);
        flag = 0;
       // qDebug() << "len" << len;
    }
    else
    {
        if(camfd->bytesAvailable() < len){
            return ;
        }
        int ret = camfd->read(buf,len);
        if(ret <= 0)
            return ;
       // qDebug()<<"ret = "<<ret;
        QPixmap pix;
        pix.loadFromData((uchar *)buf,len,"jpg");
        ui->label_image->setPixmap(pix);
        flag = 1;
    }
}

        接收到摄像头的图片数据,再将其放进一个lable进行显示,在此之前会首先接收到服务发送的标志位,当标志位大于20时,客户端才能正常的显示画面。

void Form::checkW() //点击查询请求天气数据
{
    QString local_city = ui->city->text().trimmed(); //获得需要查询天气的城市名称
    char quest_array[256] = "http://wthrcdn.etouch.cn/weather_mini?city=";
    QNetworkRequest quest;
    sprintf(quest_array, "%s%s", quest_array, local_city.toUtf8().data());
    quest.setUrl(QUrl(quest_array));
    quest.setHeader(QNetworkRequest::UserAgentHeader, "RT-Thread ART");
    /*发送get网络请求*/
    manager->get(quest);
}

void Form::replyFinished(QNetworkReply *reply)  //天气数据处理槽函数
{
    qDebug() << "recv weather data!!";
    QString all = reply->readAll();

    //ui->textBrowser->setText(all); //将接收到的数据显示出来

    QJsonParseError err;
    QJsonDocument json_recv = QJsonDocument::fromJson(all.toUtf8(), &err);//解析json对象
    qDebug() << err.error;
    if (!json_recv.isNull())
    {
        QJsonObject object = json_recv.object();

        if (object.contains("data"))
        {
            QJsonValue value = object.value("data");  // 获取指定 key 对应的 value
            if (value.isObject())
            {
                QJsonObject object_data = value.toObject();
                if (object_data.contains("forecast"))
                {
                    QJsonValue value = object_data.value("forecast");
                    if (value.isArray())
                    {
                        QJsonObject today_weather = value.toArray().at(0).toObject();
                        weather_type = today_weather.value("type").toString();

                        QString recommend = object.value("data").toObject().value("ganmao").toString();
                        QString low = today_weather.value("low").toString();
                        QString high = today_weather.value("high").toString();
                        tmp = low.mid(low.length() - 3, 4) + "~" + high.mid(high.length() - 3, 4);
                        QString strength = today_weather.value("fengli").toString();
                        strength.remove(0, 8);
                        strength.remove(strength.length() - 2, 2);
                        wind = today_weather.value("fengxiang").toString() + strength;
                        ui->weather->setText(weather_type); //显示天气类型
                        ui->tmp->setText(tmp);   //显示温度
                        ui->wind->setText(wind); //显示风力
                        ui->rec->setText(recommend);
                    }
                }
            }
        }

    }
    else
    {
        qDebug() << "json_recv is NULL or is not a object !!";
    }
    reply->deleteLater(); //销毁请求对象
}

        紧接着是天气查询的槽函数的实现,首先是向对应的查询网址发送一个查询请求,当接受到查询请求之后会回传一个数据包,着这个数据包是json格式的,按照对应的格式进行数据的提取,再将其显示在对应的行编辑框中,这样就可以实现天气查询的功能了。

3.运行效果

 这是登录界面效果,很有感觉。

这是主界面效果 ,就很有趣。

  • 5
    点赞
  • 48
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值