UVC 设备框架在 Linux 4.15 内核的演变

news2024/11/25 10:45:57

1. 概述

发现之前的uvc框架和现在的还是有一些差别的(比如从videobuf 过渡到videobuf2),写个blog记录一下,方便以后查询,我的内核版本:Linux 4.15

  • UVC(USB Video Class)设备框架是建立在V4L2(Video4Linux version 2)子系统之上的。UVC框架主要负责管理通过USB接口连接的视频捕获设备,如网络摄像头。

2. 流程分析

  • 打开设备文件 : 应用程序通过文件I/O打开设备时,内核创建一个uvc_fh 文件句柄实例,将其关联到特定的视频流
  • 缓冲区队列操作:uvc_fh通过它的视频流uvc_streaming 结构体间接操作vb2_queue ,以执行如缓冲区排队(qbuf),和缓冲区准备(reqbufs)等操作
  • 数据流传输: 视频数据通过 vb2_buffer 结构体在用户空间和UVC硬件之间传输

3. 主要的内核结构体

图1
可以看出当前版本下的UVC驱动和V4L2之间的交互非常密切。V4L2是Linux内核中用于处理视频捕获和输出设备的一个标准API框架,而UVC驱动则是V4L2框架下用于支持符合USB视频类规范的摄像头和其他视频设备的一个具体实现

3.1 UVC驱动和V4L2之间主要的交互方式:

  1. 设备注册和初始化
    UVC驱动在系统启动或USB视频设备插入时,会被初始化并注册为V4L2设备。这一过程包括设置设备的V4L2能力、支持的格式、控制操作等,确保UVC设备能够通过V4L2接口与用户空间应用程序交互。
  2. 控制查询和设置
    UVC驱动实现了一系列V4L2控制类(control class)接口,允许用户空间应用程序查询和设置视频设备的参数,如亮度、对比度、饱和度等。这些控制操作通过UVC驱动转换为USB传输,与硬件设备交互。
  3. 缓冲区管理
    UVC驱动使用V4L2提供的videobuf2 API来管理视频帧的缓冲区。这包括缓冲区的分配、队列管理、数据传输等。Videobuf2作为V4L2的一部分,提供了一个高效的机制来处理视频数据的缓冲和流转。
  4. 数据流控制
    用户空间应用程序可以通过V4L2接口来启动和停止视频流。UVC驱动响应这些请求,通过USB接口与硬件设备进行交互,控制视频数据的捕获和传输。
  5. 事件处理
    UVC驱动能够处理来自硬件的事件,比如状态变化、错误报告等,并通过V4L2框架将这些事件上报给用户空间应用程序,使得应用程序能够对特定的硬件事件做出响应。
  6. 格式协商
    在视频捕获或输出过程中,UVC驱动和用户空间应用程序会通过V4L2接口进行格式协商,确定视频数据的格式、分辨率、帧率等参数。UVC驱动根据这些协商结果配置USB视频设备,以满足应用程序的需求。

4. UVC_Driver 驱动入口出口函数

static int __init uvc_init(void)
{
...
	ret = usb_register(&uvc_driver.driver);
	return 0;
...
}

static void __exit uvc_cleanup(void)
{
	usb_deregister(&uvc_driver.driver);
}
module_init(uvc_init);
module_exit(uvc_cleanup);

接下里就是关于uvc_driver 结构体

struct uvc_driver uvc_driver = {
	.driver = {
		.name		= "uvcvideo",
		.probe		= uvc_probe,
		.disconnect	= uvc_disconnect,
		.suspend	= uvc_suspend,
		.resume		= uvc_resume,
		.reset_resume	= uvc_reset_resume,
		.id_table	= uvc_ids,
		.supports_autosuspend = 1,
	},
};

4.1 uvc_probe

这里主要是分配 设置 设置结构体,以及一些其他的操作

4.1.1 分配 uvc_device

if ((dev = kzalloc(sizeof *dev, GFP_KERNEL)) == NULL)

	INIT_LIST_HEAD(&dev->entities); 
	INIT_LIST_HEAD(&dev->chains);  
	INIT_LIST_HEAD(&dev->streams); 
  • entities: 指的也就是摄像头、输入终端(ITT)、输出终端(OTT)、处理单元(PU)和扩展单元(XU)等。每个实体代表设备的一个功能部分,可能是视频捕获的源头(例如摄像头),或是对视频数据进行处理的模块(例如编码器)。
  • chains: 视频链是由一系列实体(entities)连接而成的路径,从视频捕获的源头开始,经过一系列处理,最终到达输出。每个视频链代表了一种特定的视频流处理流程
  • streams :视频流可以理解为通过一个特定视频链(chains)实现的数据流,包含实际的视频帧数据
    在这里插入图片描述

解析设备接口描述符 并将信息储存到 uvc_device *dev; 以便后面使用

uvc_parse_control(dev)

4.1.2 分配 uvc_device注册 video 设备节点

  1. 注册uvc设备到V4L2框架下, 将V4L2设备结构体 v4l2_device 与内核结构体device 关联起来使其成为一个可以由用户空间访问和控制的设备
if (v4l2_device_register(&intf->dev, &dev->vdev) < 0)
		goto error;
  1. 初始化uvc设备的控制项
if (uvc_ctrl_init_device(dev) < 0)
		goto error;
/* 
uvc_ctrl_init_device 主要就是
1.. 遍历所有与设备管理的实体(比如处理单元PU,控制单元CU等),并且为每
个实体分配了一个 struct uvc_control *ctrl 的结构体
3. 对于每个支持的控制项,函数设置控制的索引,并调用
 uvc_ctrl_init_ctrl 来进一步初始化控制项。
*/

  1. 扫描设备实体(entities) 并注册成 视频链 (chains)
if (uvc_scan_device(dev) < 0)
		goto error;
/* 1.遍历所有实体 从输出端OT 反向扫描
   2.为没用分配到的实体分配视频链结构体
   3.初始化视频链:
	4.设置默认标志:
*/
  1. 为识别到的视频链注册到video_device 设备节点
uvc_register_chains(dev)	
	uvc_register_terms(dev, chain);
		uvc_register_video(dev, stream);//根据传输终端的 ID 查找视频流并将其注册为设备节点 dev/video x 

接下来分析一下uvc_register_video 这里面的函数

a. 首先对uvc_queue 进行初始化, 设置它的队列的类型,以及是否丢帧行为 (uvc_no_drop_param),根据内核结构体那张图,我们还需要将其初始化到vb2_queue 里面去

	ret = uvc_queue_init(&stream->queue, stream->type, !uvc_no_drop_param);
	if (ret)
		return ret;
/*
ret = vb2_queue_init(&queue->queue);
	if (ret)
		return ret;
*/

可以看出在UVC驱动中,主要是使用videobuf2来管理视频流的数据传输,主要包括以下几个步骤:

  • 初始化队列:UVC驱动在初始化视频流时,会创建并初始化一个vb2_queue结构体实例。这个过程包括指定队列操作的回调函数和设置缓冲区的类型(捕获或输出)。

  • 缓冲区操作:UVC驱动会使用videobuf2提供的API来执行缓冲区的排队(queue)和出队(dequeue)操作

  • 数据处理:当缓冲区准备好数据后(例如,捕获了一帧视频数据),videobuf2框架会通知UVC驱动,驱动随后可以处理这些数据,比如将其传输给用户空间。

  • 与用户空间的交互:videobuf2还处理与用户空间应用程序的交互,包括应用程序对缓冲区的请求、查询和映射操作。

b .初始化视频流 :确保视频流在开始传输数据之前已经正确配置,包括分辨率、帧率等参数

ret = uvc_video_init(stream);
	if (ret < 0) {
		uvc_printk(KERN_ERR, "Failed to initialize the device "
			"(%d).\n", ret);
		return ret;
	}

c. 注册视频设备

	/*设置相关的参数*/
	vdev->v4l2_dev = &dev->vdev;
	vdev->fops = &uvc_fops;
	vdev->ioctl_ops = &uvc_ioctl_ops;
	vdev->release = uvc_release;
	vdev->prio = &stream->chain->prio;
	if (stream->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
		vdev->vfl_dir = VFL_DIR_TX;
	strlcpy(vdev->name, dev->name, sizeof vdev->name);

//设置视频设备的私有数据,确保在设备操作中可以访问到关联的 uvc_streaming 结构
	video_set_drvdata(vdev, stream);
	ret = video_register_device(vdev, VFL_TYPE_GRABBER, -1);
	if (ret < 0) {
		return ret;
	}

c. 更新视频流和链的能力

	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
		stream->chain->caps |= V4L2_CAP_VIDEO_CAPTURE;
	else
		stream->chain->caps |= V4L2_CAP_VIDEO_OUTPUT;

	atomic_inc(&dev->nstreams); /*更新活跃视频流的计数*/
  1. 将数据指针保存在数据接口中
usb_set_intfdata(intf, dev);
  1. 初始化中断 URB。
if ((ret = uvc_status_init(dev)) < 0) {
		uvc_printk(KERN_INFO, "Unable to initialize the status "
			"endpoint (%d), status interrupt will not be "
			"supported.\n", ret);
	}

5. UVC驱动的调用过程详解

UVC驱动通过一系列文件操作和IO控制操作(ioctl)来管理视频捕获和控制流程

5.1 核心操作集合

uvc驱动的调用过程主要是涉及之前注册时候设置的

    vdev->fops = &uvc_fops; //文件操作
	vdev->ioctl_ops = &uvc_ioctl_ops; //ioctl操作

文件操作(uvc_fops)

const struct v4l2_file_operations uvc_fops = {
	.owner		= THIS_MODULE,
	.open		= uvc_v4l2_open,
	.release	= uvc_v4l2_release,
	.unlocked_ioctl	= video_ioctl2,
#ifdef CONFIG_COMPAT
	.compat_ioctl32	= uvc_v4l2_compat_ioctl32,
#endif
	.read		= uvc_v4l2_read,
	.mmap		= uvc_v4l2_mmap,
	.poll		= uvc_v4l2_poll,
#ifndef CONFIG_MMU
	.get_unmapped_area = uvc_v4l2_get_unmapped_area,
#endif
};

IOCTL操作(uvc_ioctl_ops)

const struct v4l2_ioctl_ops uvc_ioctl_ops = {
	/*最基本的几个参数*/
	.vidioc_querycap = uvc_ioctl_querycap,
	.vidioc_enum_fmt_vid_cap = uvc_ioctl_enum_fmt_vid_cap,
	.vidioc_g_fmt_vid_cap = uvc_ioctl_g_fmt_vid_cap,
	.vidioc_s_fmt_vid_cap = uvc_ioctl_s_fmt_vid_cap,
	.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,

	.vidioc_reqbufs = uvc_ioctl_reqbufs,
	.vidioc_querybuf = uvc_ioctl_querybuf,
	.vidioc_qbuf = uvc_ioctl_qbuf,
	.vidioc_dqbuf = uvc_ioctl_dqbuf,

	
	

	.vidioc_streamon = uvc_ioctl_streamon,
	/* uvc_ioctl_streamon
	 *1.向USB摄像头设置参数 比如使用那个format 使用这个format下的那个frame(分辨率)
	 *1.1 根据一个uvc_streaming_control 设置数据包,可以手工设置也可以读出
	 *1.2 调用usb_control_msg 发出数据包
	 
	 *2.分配设置URB
	 *3.提交URB以接受数据
	*/
	.vidioc_streamoff = uvc_ioctl_streamoff,

	.vidioc_enum_fmt_vid_out = uvc_ioctl_enum_fmt_vid_out,
	.vidioc_g_fmt_vid_out = uvc_ioctl_g_fmt_vid_out,
	.vidioc_s_fmt_vid_out = uvc_ioctl_s_fmt_vid_out,
	.vidioc_try_fmt_vid_cap = uvc_ioctl_try_fmt_vid_cap,
	.vidioc_try_fmt_vid_out = uvc_ioctl_try_fmt_vid_out,
	.vidioc_create_bufs = uvc_ioctl_create_bufs,

	.vidioc_enum_input = uvc_ioctl_enum_input,
	.vidioc_g_input = uvc_ioctl_g_input,
	.vidioc_s_input = uvc_ioctl_s_input,

	/*查询 查询额外属性 获得 设置属性*/
	.vidioc_queryctrl = uvc_ioctl_queryctrl,
	.vidioc_query_ext_ctrl = uvc_ioctl_query_ext_ctrl,
	.vidioc_g_ctrl = uvc_ioctl_g_ctrl,
	.vidioc_s_ctrl = uvc_ioctl_s_ctrl,
	
	.vidioc_g_ext_ctrls = uvc_ioctl_g_ext_ctrls,
	.vidioc_s_ext_ctrls = uvc_ioctl_s_ext_ctrls,
	.vidioc_try_ext_ctrls = uvc_ioctl_try_ext_ctrls,
	.vidioc_querymenu = uvc_ioctl_querymenu,
	.vidioc_g_selection = uvc_ioctl_g_selection,

	/*设置帧间隔*/
	.vidioc_g_parm = uvc_ioctl _g_parm,
	.vidioc_s_parm = uvc_ioctl_s_parm,
	/*枚举支持的分辨率 帧间隔参数*/
	.vidioc_enum_framesizes = uvc_ioctl_enum_framesizes,
	.vidioc_enum_frameintervals = uvc_ioctl_enum_frameintervals,

	.vidioc_subscribe_event = uvc_ioctl_subscribe_event,
	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
	.vidioc_default = uvc_ioctl_default,
};

5.2 主要的调用过程

5.2.1 open

内核创建一个uvc_fh 文件句柄实例,将其关联到特定的视频流,以及初始化V4L2文件句柄

static int uvc_v4l2_open(struct file *file)

5.2.2 VIDIOC_QUERYCAP 查询设备的功能

static int uvc_ioctl_querycap(struct file *file, void *fh,
			      struct v4l2_capability *cap)
{
...
	usb_make_path(stream->dev->udev, cap->bus_info, sizeof(cap->bus_info));
	cap->capabilities = V4L2_CAP_DEVICE_CAPS | V4L2_CAP_STREAMING
			  | chain->caps;
	if (stream->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
		cap->device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
	else
		cap->device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_STREAMING;

	return 0;
}

5.2.3 VIDIOC_ENUM_FMT 枚举设备支持的格式

枚举设备支持的格式 ,比如MJPEG或H264等 填充到v4l2_fmtdesc *fmt

static int uvc_ioctl_enum_fmt(struct uvc_streaming *stream,
			      struct v4l2_fmtdesc *fmt)
{
	struct uvc_format *format;
	enum v4l2_buf_type type = fmt->type;
	__u32 index = fmt->index;

	if (fmt->type != stream->type || fmt->index >= stream->nformats)
		return -EINVAL;

	memset(fmt, 0, sizeof(*fmt)); //将fmt结构内存清0 
	fmt->index = index;
	fmt->type = type;

	format = &stream->format[fmt->index];
	fmt->flags = 0;
	if (format->flags & UVC_FMT_FLAG_COMPRESSED)
		fmt->flags |= V4L2_FMT_FLAG_COMPRESSED;
	strlcpy(fmt->description, format->name, sizeof(fmt->description));
	fmt->description[sizeof(fmt->description) - 1] = 0;
	fmt->pixelformat = format->fcc;
	return 0;
}

5.2.4 VIDIOC_G_FMT 得到当前的支持的格式 比如分辨率等

int uvc_ioctl_g_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
--------->	uvc_v4l2_get_format(stream, fmt); /*跳转到v4l2框架下的函数来进行*/
static int uvc_v4l2_get_format(struct uvc_streaming *stream,
	struct v4l2_format *fmt)
{
...
	fmt->fmt.pix.pixelformat = format->fcc;
	fmt->fmt.pix.width = frame->wWidth;
	fmt->fmt.pix.height = frame->wHeight;
	fmt->fmt.pix.field = V4L2_FIELD_NONE; //逐行扫描
	fmt->fmt.pix.bytesperline = format->bpp * frame->wWidth / 8;
	fmt->fmt.pix.sizeimage = stream->ctrl.dwMaxVideoFrameSize;
	fmt->fmt.pix.colorspace = format->colorspace;//色彩空间
	fmt->fmt.pix.priv = 0;
	return ret;
...
}

5.2.5 VIDIOC_S_FMT 更新UVC视频流的当前格式和帧大小 以匹配应用程序的请求配置

static int uvc_ioctl_s_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
-------> uvc_v4l2_set_format(stream, fmt);
/*
struct uvc_streaming *stream: 视频流对象的指针。
struct v4l2_format *fmt: 应用程序请求的视频格式信息。
*/
static int uvc_v4l2_set_format(struct uvc_streaming *stream,struct v4l2_format *fmt)
{
	/*尝试匹配和调整应用请求的格式*/
	ret = uvc_v4l2_try_format(stream, fmt, &probe, &format, &frame);

	
	mutex_lock(&stream->mutex);
	
	if (uvc_queue_allocated(&stream->queue)) {
		/*如果视频流帧缓冲区已经分配,此时不能修改格式*/
		ret = -EBUSY;
	}
}

5.2.6 VIDIOC_TRY_FMT 检查格式是否匹配

static int uvc_ioctl_try_fmt_vid_cap(struct file *file, void *fh, struct v4l2_format *fmt)
-------> uvc_v4l2_try_format(stream, fmt, &probe, NULL, NULL);

5.2.3 VIDIOC_REQBUF

static int uvc_ioctl_reqbufs(struct file *file, void *fh, struct v4l2_requestbuffers *rb)
-------> uvc_request_buffers(&stream->queue, rb);
--------->vb2_reqbufs(&queue->queue, rb); //跳转到video_buf2
-----------> __reqbufs(q, req)
--------------> __vb2_queue_alloc(q, req->memory, num_buffers, num_planes)
/*
struct vb2_queue *q: 指向视频缓冲区vb2队列对象的指针。
struct v4l2_requestbuffers *req: 包含了请求分配缓冲区的信息。
*/

int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
	/*验证请求的内存类型和视频流的类型是否匹配*/
	int ret = __verify_memory_type(q, req->memory, req->type);
	return ret ? ret : __reqbufs(q, req);
}
__reqbufs() 启动流式传输

应从驱动程序的 vidioc_reqbufs ioctl 处理程序中调用。
该函数

  1. 验证从用户空间传递的流参数、
  2. 设置队列、
  3. 与驱动程序协商缓冲区数量和每个缓冲区的平面数
  4. 根据商定的参数分配内部缓冲区结构(struct vb2_buffer)。
  5. 对于 MMAP 内存类型,使用队列初始化过程中提供的内存处理/分配例程分配实际视频内存

如果 req->count 为 0,则释放所有内存。
如果队列之前已被分配(通过之前的 vb2_reqbufs)调用
且队列不忙,内存将被重新分配。

该函数的返回值可直接从 从驱动程序中的 vidioc_reqbufs 处理程序直接返回。

static int __reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req)
{
	unsigned int num_buffers, allocated_buffers, num_planes = 0;
	int ret;

	if (q->streaming) {
		dprintk(1, "streaming active\n");
		return -EBUSY;
	}

	if (req->count == 0 || q->num_buffers != 0 || q->memory != req->memory) {
		/*
		 * We already have buffers allocated, so first check if they
		 * are not in use and can be freed.
		 */
		mutex_lock(&q->mmap_lock);
		if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) {
			mutex_unlock(&q->mmap_lock);
			dprintk(1, "memory in use, cannot free\n");
			return -EBUSY;
		}

		/*
		 * Call queue_cancel to clean up any buffers in the PREPARED or
		 * QUEUED state which is possible if buffers were prepared or
		 * queued without ever calling STREAMON.
		 */
		__vb2_queue_cancel(q);
		ret = __vb2_queue_free(q, q->num_buffers);
		mutex_unlock(&q->mmap_lock);
		if (ret)
			return ret;

		/*
		 * In case of REQBUFS(0) return immediately without calling
		 * driver's queue_setup() callback and allocating resources.
		 */
		if (req->count == 0)
			return 0;
	}

	/*
	 * Make sure the requested values and current defaults are sane.
	 */
	num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME);
	num_buffers = max_t(unsigned int, num_buffers, q->min_buffers_needed);
	memset(q->plane_sizes, 0, sizeof(q->plane_sizes));
	memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx));
	q->memory = req->memory;

	/*
	 * Ask the driver how many buffers and planes per buffer it requires.
	 * Driver also sets the size and allocator context for each plane.
	 */
	ret = call_qop(q, queue_setup, q, NULL, &num_buffers, &num_planes,
		       q->plane_sizes, q->alloc_ctx);
	if (ret)
		return ret;

	/* Finally, allocate buffers and video memory */
	allocated_buffers = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes);
	if (allocated_buffers == 0) {
		dprintk(1, "memory allocation failed\n");
		return -ENOMEM;
	}

	/*
	 * There is no point in continuing if we can't allocate the minimum
	 * number of buffers needed by this vb2_queue.
	 */
	if (allocated_buffers < q->min_buffers_needed)
		ret = -ENOMEM;

	/*
	 * Check if driver can handle the allocated number of buffers.
	 */
	if (!ret && allocated_buffers < num_buffers) {
		num_buffers = allocated_buffers;

		ret = call_qop(q, queue_setup, q, NULL, &num_buffers,
			       &num_planes, q->plane_sizes, q->alloc_ctx);

		if (!ret && allocated_buffers < num_buffers)
			ret = -ENOMEM;

		/*
		 * Either the driver has accepted a smaller number of buffers,
		 * or .queue_setup() returned an error
		 */
	}

	mutex_lock(&q->mmap_lock);
	q->num_buffers = allocated_buffers;

	if (ret < 0) {
		/*
		 * Note: __vb2_queue_free() will subtract 'allocated_buffers'
		 * from q->num_buffers.
		 */
		__vb2_queue_free(q, allocated_buffers);
		mutex_unlock(&q->mmap_lock);
		return ret;
	}
	mutex_unlock(&q->mmap_lock);

	/*
	 * Return the number of successfully allocated buffers
	 * to the userspace.
	 */
	req->count = allocated_buffers;
	q->waiting_for_buffers = !V4L2_TYPE_IS_OUTPUT(q->type);

	return 0;
}
__vb2_queue_alloc -----分配videobuf 结构体

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1512081.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Shadertoy内置函数系列 - mod 取模运算

mod函数返回x % 3的结果 先看一个挑战问题题目&#xff1a; Create a pattern of alternating black and red columns, with 9 columns of each color. Then, hide every third column that is colored red.The shader should avoid using branching or conditional statemen…

jeecg 项目 springcloud 项目有一个模块 没加载进来 只需要 把这个模块放到 可以加载到模块的位置 刷新依赖

springcloud 项目有一个模块 没加载进来 只需要 把这个模块放到 可以加载到模块的位置 刷新依赖

docker+elasticsearch

一&#xff0c;环境准备&#xff1a;安装docker&#xff08;往期文章&#xff09; 二&#xff0c;elasticsearch简介&#xff1a; 用于储存数据 三&#xff0c;部署&#xff1a; 1&#xff09;&#xff0c;拉取镜像 使用本作者提供的java17镜像 2&#xff09;&#xff0c;…

刷题日记——01字符串、非素数个数(厦门大学机试)

题目1——01字符串 分析 经过拆解找规律&#xff0c;发现是两个斐波那契 那么代码就好写了呀 #include <stdio.h>unsigned long long f(int n){if(n0||n1){return n;}return (f(n-1)f(n-2))%2333333; }int main(){int n;scanf("%d",&n);printf("%…

服务器段的连接端口和监听端口编程实现

new ServerSocket(int)是开启监听端口&#xff0c;并不是连接端口。真正的连接端口是随机开辟的空闲端口&#xff0c;当连接创建完成后&#xff0c;监听关口可以继续等待下一次连接请求&#xff0c;处于空闲等待状态。 编程实现方式 1 、主线程一直处于阻塞等待状态&#xff0c…

【功能大全】手机短信验证码一键注册登录流程

目录 发送验证码 注册登录 用户表设计 ​编辑申请腾讯云短信与密钥 找到云短信服务 开通腾讯云短信服务 ​编辑​​​​​创建短信签名 ​编辑​编辑创建短信正文模版​编辑​编辑 等待审核 测试短信​编辑 SDK密钥创建 SpringBoot集成腾讯云短信 pom中导入腾讯云短…

Unity 显示MeshRenderer的渲染层级

Unity 显示MeshRenderer的渲染层级 前言源码MeshRendererInspectorSkinnedMeshRendererInspector 参考 前言 Mesh Renderer和Skinned Mesh Renderer组件默认不显示Order&#xff0c;找了个工具显示一下。 源码 下面两个代码放入Editor文件夹中 MeshRendererInspector Me…

互联网高频面:输入URL按下回车后,中间发生了什么

题目 输入URL按下回车后&#xff0c;中间发生了什么 这个问题其实是计算机网络里面很经典的一个问题&#xff0c;不能去死机硬背&#xff0c;很考察对网络架构和通信原理的理解&#xff0c;也是各个互联网大厂喜欢考察的面试题。 一些图片参考了小林的计算机网络面经 从输入…

Elasticsearch基础操作

一、Restful接口 Elasticsearch通过RESTful接口提供与其进行交互的方式。在ES中&#xff0c;提供了功能丰富的RESTful API的操作&#xff0c;包括CRUD、创建索引、删除索引等操作。你可以用你最喜爱的 web 客户端访问 Elasticsearch 。事实上&#xff0c;你甚至可以使用 curl …

C#,文字排版的折行问题(Word-wrap problem)的算法与源代码

1、英文的折行问题 给定一个单词序列&#xff0c;以及一行中可以输入的字符数限制&#xff08;线宽&#xff09;。 在给定的顺序中放置换行符&#xff0c;以便打印整齐。 假设每个单词的长度小于线宽。 像MS word这样的文字处理程序负责放置换行符。 这个想法是要有平衡的线条。…

Android基础开发-通讯录的添加和查询

案例&#xff1a;往手机通讯录添加信息&#xff0c;输入姓名和手机号。 保存的手机的表&#xff1a;一共有两个&#xff0c;一个是主表&#xff0c;提供一个联系人id&#xff0c;另外是辅表&#xff0c;提供id对应的手机号和姓名。 普通操作&#xff1a;一个表一个表的添加 …

ThingsBoard开源物联网平台介绍

1. Thingsboard 简介 ThingsBoard是一个基于Java的开源物联网平台&#xff0c;旨在实现物联网项目的快速开发、管理和扩展。它使用行业标准的物联网协议&#xff08;MQTT、CoAP和HTTP&#xff09;实现设备连接&#xff0c;并支持云和本地部署。ThingsBoard结合了可扩展性、容错…

synchronized关键字(锁升级)

概述 synchronized是Java的一个关键字&#xff0c;用来保证多线程下临界区资源的共享安全性 synchronized可以加在方法上&#xff08;静态方法和普通方法&#xff09;、代码块上 使用语法&#xff1a; synchronized (对象) {// 操作临界资源 }public synchronized void te…

云起云落:揭秘云计算基础内功心法

文章目录 云计算基本概念云计算发展历程云计算计算模式云计算商业模式云计算部署模式私有云部署公有云部署混合云部署 云计算虚拟化技术虚拟机模式虚拟化容器模式虚拟化容器VS虚拟机 常见虚拟化工具和技术虚拟化工具“轻量级”虚拟化工具 云计算供应商云计算三要点&#xff08;…

用miniconda建立PyTorch、Keras、TensorFlow三个环境

一、配置清华镜像conda源 由于网络问题&#xff0c;直接使用conda默认的源下载包可能会非常慢。为了解决这个问题&#xff0c;可以配置国内镜像源来加速包的下载。清华大学TUNA协会提供了一个常用的conda镜像源。下面是如何配置清华镜像源的步骤&#xff1a; 1. 配置清华conda…

案例分析篇14:信息系统安全设计考点(2024年软考高级系统架构设计师冲刺知识点总结系列文章)

专栏系列文章推荐: 2024高级系统架构设计师备考资料(高频考点&真题&经验)https://blog.csdn.net/seeker1994/category_12593400.html 【历年案例分析真题考点汇总】与【专栏文章案例分析高频考点目录】(2024年软考高级系统架构设计师冲刺知识点总结-案例分析篇-…

多维时序 | Matlab实现VMD-CNN-LSTM变分模态分解结合卷积神经网络结合长短期记忆神经网络多变量时间序列预测

多维时序 | Matlab实现VMD-CNN-LSTM变分模态分解结合卷积神经网络结合长短期记忆神经网络多变量时间序列预测 目录 多维时序 | Matlab实现VMD-CNN-LSTM变分模态分解结合卷积神经网络结合长短期记忆神经网络多变量时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介…

ARM64汇编06 - 基本整型运算指令

ADD (immediate) 将 Xn 与 imm 相加&#xff0c;结果赋值给 Xd&#xff0c;imm 是无符号数&#xff0c;范围为 0 - 4095。 shift 是对 imm 进行移位&#xff0c;shift 为 0 的时候&#xff0c;表示左移 0 位&#xff0c;即不变。shift 为 1 的时候&#xff0c;表示左移12 位&a…

Django 学习笔记(Day1)

「写在前面」 本文为千锋教育 Django 教程的学习笔记。本着自己学习、分享他人的态度&#xff0c;分享学习笔记&#xff0c;希望能对大家有所帮助。 目录 0 课程介绍 1 Django 快速入门 1.1 Django 介绍 1.2 Django 安装 1.3 创建 Django 项目 1.4 运行 Django 项目 1.5 数据迁…

使用 Ruby 或 Python 在文件中查找

对于经常使用爬虫的我来说&#xff0c;在大多数文本编辑器都会有“在文件中查找”功能&#xff0c;主要是方便快捷的查找自己说需要的内容&#xff0c;那我有咩有可能用Ruby 或 Python实现类似的查找功能&#xff1f;这些功能又能怎么实现&#xff1f; 问题背景 许多流行的文本…