首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >你会用Tomcat,但不一定懂

你会用Tomcat,但不一定懂

作者头像
用户3467126
发布于 2022-09-01 03:38:42
发布于 2022-09-01 03:38:42
40500
代码可运行
举报
文章被收录于专栏:爱编码爱编码
运行总次数:0
代码可运行

Tomcat是我们最常用的Web容器,但是很少有人知晓其工作原理。下面就总结一下极客时间的课程《深入拆解 Tomcat & Jetty》,本文仅总结部分知识,有兴趣可以找相应的课程学习一番。

手写一个Servlet

1、首先新建一个名为MyServlet.java的文件,敲入下面这些代码

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("MyServlet 在处理get()请求...");
        PrintWriter out = response.getWriter();
        response.setContentType("text/html;charset=utf-8");
        out.println("<strong>My Servlet!</strong><br>");
    }
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        System.out.println("MyServlet 在处理post()请求...");
        PrintWriter out = response.getWriter();
        response.setContentType("text/html;charset=utf-8");
        out.println("<strong>My Servlet!</strong><br>");
    }
}

这个 Servlet 完成的功能很简单,分别在 doGet 和 doPost 方法体里返回一段简单的 HTML。

2、 将 Java 文件编译成 Class 文件

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
javac -cp ./servlet-api.jar MyServlet.java

编译成功后,你会在当前目录下找到一个叫MyServlet.class的文件。

3、然后在web.xml中配置 Servlet,内容如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
  http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
  version="4.0"
  metadata-complete="true">
    <description> Servlet Example. </description>
    <display-name> MyServlet Example </display-name>
    <request-character-encoding>UTF-8</request-character-encoding>
    <servlet>
      <servlet-name>myServlet</servlet-name>
      <servlet-class>MyServlet</servlet-class>
    </servlet>
    <servlet-mapping>
      <servlet-name>myServlet</servlet-name>
      <url-pattern>/myservlet</url-pattern>
    </servlet-mapping>
</web-app>

4、启动 Tomcat

在浏览器里访问这个 URL:http://localhost:8080/MyWebApp/myservlet,你会看到。

Servlet规范和容器

当客户请求某个资源时,HTTP 服务器会用一个 ServletRequest 对象把客户的请求信息封装起来,然后调用 Servlet 容器的 service 方法,Servlet 容器拿到请求后,根据请求的 URL 和 Servlet 的映射关系,找到相应的 Servlet,如果 Servlet 还没有被加载,就用反射机制创建这个 Servlet,并调用 Servlet 的 init 方法来完成初始化,接着调用 Servlet 的 service 方法来处理请求,把 ServletResponse 对象返回给 HTTP 服务器,HTTP 服务器会把响应发送给客户端。

Servlet 规范里定义了 ServletContext 这个接口来对应一个 Web 应用。Web 应用部署好后,Servlet 容器在启动时会加载 Web 应用,并为每个 Web 应用创建唯一的 ServletContext 对象。你可以把 ServletContext 看成是一个全局对象,一个 Web 应用可能有多个 Servlet,这些 Servlet 可以通过全局的 ServletContext 来共享数据,这些数据包括 Web 应用的初始化参数、Web 应用目录下的文件资源等。由于 ServletContext 持有所有 Servlet 实例,你还可以通过它来实现 Servlet 请求的转发。

Servlet 规范提供了两种扩展机制:Filter 和 Listener。Filter 是干预过程的,它是过程的一部分,是基于过程行为的。Listener 是基于状态的,任何行为改变同一个状态,触发的事件是一致的

Tomcat系统架构

Tomcat 要实现 2 个核心功能:

  • 处理 Socket 连接,负责网络字节流与 Request 和 Response 对象的转化。
  • 加载和管理 Servlet,以及具体处理 Request 请求。

因此 Tomcat 设计了两个核心组件连接器(Connector)和容器(Container)来分别做这两件事情。连接器负责对外交流,容器负责内部处理。

连接器

Tomcat 的整体架构包含了两个核心组件连接器和容器。连接器负责对外交流,容器负责内部处理。连接器用 ProtocolHandler 接口来封装通信协议和 I/O 模型的差异,ProtocolHandler 内部又分为 Endpoint 和 Processor 模块,Endpoint 负责底层 Socket 通信,Processor 负责应用层协议解析。连接器通过适配器 Adapter 调用容器。

容器

Tomcat 设计了 4 种容器,分别是 Engine、Host、Context 和 Wrapper。这 4 种容器不是平行关系,而是父子关系。下面我画了一张图帮你理解它们的关系。

Context 表示一个 Web 应用程序;Wrapper 表示一个 Servlet,一个 Web 应用程序中可能会有多个 Servlet;Host 代表的是一个虚拟主机,或者说一个站点,可以给 Tomcat 配置多个虚拟主机地址,而一个虚拟主机下可以部署多个 Web 应用程序;Engine 表示引擎,用来管理多个虚拟站点,一个 Service 最多只能有一个 Engine。

通过 Tomcat 的server.xml配置文件来加深对 Tomcat 容器的理解

请求定位 Servlet 的过程

使用 Pipeline-Valve 管道(责任链模式),首先,根据协议和端口号选定 Service 和 Engine,然后,根据域名选定 Host,之后,根据 URL 路径找到 Context 组件,最后,根据 URL 路径找到 Wrapper(Servlet)。

通过对 Tomcat 整体架构的学习,我们可以得到一些设计复杂系统的基本思路。首先要分析需求,根据高内聚低耦合的原则确定子模块,然后找出子模块中的变化点和不变点,用接口和抽象基类去封装不变点,在抽象基类中定义模板方法,让子类自行实现抽象方法,也就是具体子类去实现变化点。

Catalina

  • Catalina 完成两个功能
  • 解析 server.xml, 创建定义的各组件, 调用 server init 和 start 方法
  • 处理异常情况, 例如 ctrl + c 关闭 Tomcat. 其会在 JVM 中注册"关闭钩子"
  • 关闭钩子, 在关闭 JVM 时做清理工作, 例如刷新缓存到磁盘
  • 关闭钩子是一个线程, JVM 停止前会执行器 run 方法, 该 run 方法调用 server stop 方法

Server组件

Server组件的具体实现类是StandardServer

  • 继承了 LifeCycleBase
  • 子组件是 Service, 需要管理其生命周期(调用其 LifeCycle 的方法), 用数组保存多个 Service 组件, 动态扩容数组来添加组件
  • 启动一个 socket Listen停止端口, Catalina 启动时, 调用 Server await 方法, 其创建 socket Listen 8005 端口, 并在死循环中等连接, 检查到 shutdown 命令, 调用 stop 方法
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Override
public void addService(Service service) {
    service.setServer(this);
    synchronized (servicesLock) {
        //创建一个长度+1的新数组
        Service results[] = new Service[services.length + 1];
        
        //将老的数据复制过去
        System.arraycopy(services, 0, results, 0, services.length);
        results[services.length] = service;
        services = results;
        //启动Service组件
        if (getState().isAvailable()) {
            try {
                service.start();
            } catch (LifecycleException e) {
                // Ignore
            }
        }
        //触发监听事件
        support.firePropertyChange("service", null, service);
    }
}

Service 组件

实现类 StandardService

  • 包含 Server, Connector, Engine 和 Mapper 组件的成员变量
  • 还包含 MapperListener 成员变量, 以支持热部署, 其Listen容器变化, 并更新 Mapper, 是观察者模式
  • 需注意各组件启动顺序, 根据其依赖关系确定
  • 先启动 Engine, 再启动 Mapper Listener, 最后启动连接器, 而停止顺序相反.
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class StandardService extends LifecycleBase implements Service {
    //名字
    private String name = null;
    
    //Server实例
    private Server server = null;
    //连接器数组
    protected Connector connectors[] = new Connector[0];
    private final Object connectorsLock = new Object();
    //对应的Engine容器
    private Engine engine = null;
    
    //映射器及其监听器
    protected final Mapper mapper = new Mapper();
    protected final MapperListener mapperListener = new MapperListener(this);

作为“管理”角色的组件,最重要的是维护其他组件的生命周期。此外在启动各种组件时,要注意它们的依赖关系,也就是说,要注意启动的顺序。我们来看看 Service 启动方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
protected void startInternal() throws LifecycleException {
    //1. 触发启动监听器
    setState(LifecycleState.STARTING);
    //2. 先启动Engine,Engine会启动它子容器
    if (engine != null) {
        synchronized (engine) {
            engine.start();
        }
    }
    
    //3. 再启动Mapper监听器
    mapperListener.start();
    //4.最后启动连接器,连接器会启动它子组件,比如Endpoint
    synchronized (connectorsLock) {
        for (Connector connector: connectors) {
            if (connector.getState() != LifecycleState.FAILED) {
                connector.start();
            }
        }
    }
}

Engine 组件

Engine 组件, 实现类 StandEngine 继承 ContainerBase

  • ContainerBase 实现了维护子组件的逻辑, 用 HaspMap 保存子组件, 因此各层容器可重用逻辑
  • ContainerBase 用专门线程池启动子容器, 并负责子组件启动/停止, "增删改查"
  • 请求到达 Engine 之前, Mapper 通过 URL 定位了容器, 并存入 Request 中. Engine 从 Request 取出 Host 子容器, 并调用其 pipeline 的第一个 valve
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

final class StandardEngineValve extends ValveBase {
    public final void invoke(Request request, Response response)
      throws IOException, ServletException {
  
      //拿到请求中的Host容器
      Host host = request.getHost();
      if (host == null) {
          return;
      }
  
      // 调用Host容器中的Pipeline中的第一个Valve
      host.getPipeline().getFirst().invoke(request, response);
  }
  
}

Host容器

Tomcat 的热加载和热部署:热加载的粒度比较小,主要是针对类文件的更新,通过创建新的类加载器来实现重新加载。而热部署是针对整个 Web 应用的,Tomcat 会将原来的 Context 对象整个销毁掉,再重新创建 Context 容器对象。

热加载和热部署的实现都离不开后台线程的周期性检查,Tomcat 在基类 ContainerBase 中统一实现了后台线程的处理逻辑,并在顶层容器 Engine 启动后台线程,这样子容器组件甚至各种通用组件都不需要自己去创建后台线程,这样的设计显得优雅整洁。

Context容器

Tomcat 正是通过 Context 组件来加载管理 Web 应用的。

Tomcat 的类加载器

双亲委托机制是为了保证一个 Java 类在 JVM 中是唯一的,假如你不小心写了一个与 JRE 核心类同名的类,比如 Object 类,双亲委托机制能保证加载的是 JRE 里的那个 Object 类,而不是你写的 Object 类。这是因为 AppClassLoader 在加载你的 Object 类时,会委托给 ExtClassLoader 去加载,而ExtClassLoader 又会委托给 BootstrapClassLoader,BootstrapClassLoader 发现自己已经加载过了 Object 类,会直接返回,不会去加载你写的 Object 类。

Tomcat 的自定义类加载器 WebAppClassLoader 打破了双亲委托机制,它首先自己尝试去加载某个类,如果找不到再代理给父类加载器,其目的是优先加载 Web 应用自己定义的类。 具体实现就是重写 ClassLoader 的两个方法:findClass 和 loadClass。

findClass 方法:

先在 Web 应用本地目录下查找要加载的类。如果没有找到,交给父加载器去查找,它的父加载器就是上面提到的系统类加载器 AppClassLoader。如何父加载器也没找到这个类,抛出 ClassNotFound 异常

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Class<?> findClass(String name) throws ClassNotFoundException {
    ...
    
    Class<?> clazz = null;
    try {
            //1. 先在Web应用目录下查找类 
            clazz = findClassInternal(name);
    }  catch (RuntimeException e) {
           throw e;
       }
    
    if (clazz == null) {
    try {
            //2. 如果在本地目录没有找到,交给父加载器去查找
            clazz = super.findClass(name);
    }  catch (RuntimeException e) {
           throw e;
       }
    
    //3. 如果父类也没找到,抛出ClassNotFoundException
    if (clazz == null) {
        throw new ClassNotFoundException(name);
     }
    return clazz;
}

loadClass 方法

Tomcat 的类加载器打破了双亲委托机制,没有一上来就直接委托给父加载器,而是先在本地目录下加载,为了避免本地目录下的类覆盖 JRE 的核心类,先尝试用 JVM 扩展类加载器 ExtClassLoader 去加载。那为什么不先用系统类加载器 AppClassLoader 去加载?很显然,如果是这样的话,那就变成双亲委托机制了,这就是 Tomcat 类加载器的巧妙之处。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
 
        Class<?> clazz = null;
        //1. 先在本地cache查找该类是否已经加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        //2. 从系统类加载器的cache中查找是否加载过
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }
        // 3. 尝试用ExtClassLoader类加载器类加载,为什么?
        ClassLoader javaseLoader = getJavaseClassLoader();
        try {
            clazz = javaseLoader.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 4. 尝试在本地目录搜索class并加载
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return clazz;
            }
        } catch (ClassNotFoundException e) {
            // Ignore
        }
        // 5. 尝试用系统类加载器(也就是AppClassLoader)来加载
            try {
                clazz = Class.forName(name, false, parent);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
                // Ignore
            }
       }
    
    //6. 上述过程都加载失败,抛出异常
    throw new ClassNotFoundException(name);
}

Tomcat类加载隔离

Tomcat 的 Context 组件为每个 Web 应用创建一个 WebAppClassLoader 类加载器,由于不同类加载器实例加载的类是互相隔离的,因此达到了隔离 Web 应用的目的,同时通过 CommonClassLoader 等父加载器来共享第三方 JAR 包。而共享的第三方 JAR 包怎么加载特定 Web 应用的类呢?可以通过设置线程上下文加载器来解决。而作为 Java 程序员,我们应该牢记的是:

  • 每个 Web 应用自己的 Java 类文件和依赖的 JAR 包,分别放在WEB-INF/classes和WEB-INF/lib目录下面。
  • 多个应用共享的 Java 类文件和 JAR 包,分别放在 Web 容器指定的共享目录下。
  • 当出现 ClassNotFound 错误时,应该检查你的类加载器是否正确。
Tomcat如何实现Servlet规范

Context 容器并不直接持有 Servlet 实例,而是通过子容器 Wrapper 来管理 Servlet类实例以及它相关的配置信息,比如它的 URL 映射、它的初始化参数等。

Wrapper 通过 loadServlet 方法来实例化 Servlet:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public synchronized Servlet loadServlet() throws ServletException {
    Servlet servlet;
  
    //1. 创建一个Servlet实例
    servlet = (Servlet) instanceManager.newInstance(servletClass);    
    
    //2.调用了Servlet的init方法,这是Servlet规范要求的
    initServlet(servlet);
    
    return servlet;
}

其实 loadServlet 主要做了两件事:创建 Servlet 的实例,并且调用 Servlet 的 init 方法,因为这是 Servlet 规范要求的。那接下来的问题是,什么时候会调到这个 loadServlet 方法呢?为了加快系统的启动速度,我们往往会采取资源延迟加载的策略,Tomcat 也不例外,默认情况下 Tomcat 在启动时不会加载你的 Servlet,除非你把 Servlet 的loadOnStartup参数设置为true。

当请求到来时,Context 容器的 BasicValve 会调用 Wrapper 容器中 Pipeline 中的第一个 Valve,然后会调用到 StandardWrapperValve的 invoke 方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制

public final void invoke(Request request, Response response) {
    //1.实例化Servlet
    servlet = wrapper.allocate();
   
    //2.给当前请求创建一个Filter链
    ApplicationFilterChain filterChain =
        ApplicationFilterFactory.createFilterChain(request, wrapper, servlet);
   //3. 调用这个Filter链,Filter链中的最后一个Filter会调用Servlet
   filterChain.doFilter(request.getRequest(), response.getResponse());
}

请求过来时会先执行所有的Filter,调用 Filter 链的 doFilter 方法,直到最后一个Filter调用完毕后才调用 Servlet 的 service 方法。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public final class ApplicationFilterChain implements FilterChain {
  
  //Filter链中有Filter数组,这个好理解
  private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];
    
  //Filter链中的当前的调用位置
  private int pos = 0;
    
  //总共有多少了Filter
  private int n = 0;
  //每个Filter链对应一个Servlet,也就是它要调用的Servlet
  private Servlet servlet = null;
  
  public void doFilter(ServletRequest req, ServletResponse res) {
        internalDoFilter(request,response);
  }
   
  private void internalDoFilter(ServletRequest req,
                                ServletResponse res){
    // 每个Filter链在内部维护了一个Filter数组
    if (pos < n) {
        ApplicationFilterConfig filterConfig = filters[pos++];
        Filter filter = filterConfig.getFilter();
        filter.doFilter(request, response, this);
        return;
    }
    servlet.service(request, response);
   
}

Servlet 规范中最重要的就是 Servlet、Filter 和 Listener“三兄弟”。Web 容器最重要的职能就是把它们创建出来,并在适当的时候调用它们的方法。Tomcat 通过 Wrapper 容器来管理 Servlet,Wrapper 包装了 Servlet 本身以及相应的参数,这体现了面向对象中“封装”的设计原则。Tomcat 会给每个请求生成一个 Filter 链,Filter 链中的最后一个 Filter 会负责调用 Servlet 的 service 方法。对于 Listener 来说,我们可以定制自己的监听器来监听 Tomcat 内部发生的各种事件:包括 Web 应用级别的、Session 级别的和请求级别的。Tomcat 中的 Context 容器统一维护了这些监听器,并负责触发。

Tomcat的线程池

Tomcat 扩展了 Java 线程池的核心类 ThreadPoolExecutor,并重写了它的 execute 方法,定制了自己的任务处理流程。同时 Tomcat 还实现了定制版的任务队列,重写了 offer 方法,使得在任务队列长度无限制的情况下,线程池仍然有机会创建新的线程。

Tomcat 在线程总数达到最大数时,不是立即执行拒绝策略,而是再尝试向任务队列添加任务,添加失败后再执行拒绝策略。那具体如何实现呢,其实很简单,我们来看一下 Tomcat 线程池的 execute 方法的核心代码。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ThreadPoolExecutor extends java.util.concurrent.ThreadPoolExecutor {
  
  ...
  
  public void execute(Runnable command, long timeout, TimeUnit unit) {
      submittedCount.incrementAndGet();
      try {
          //调用Java原生线程池的execute去执行任务
          super.execute(command);
      } catch (RejectedExecutionException rx) {
         //如果总线程数达到maximumPoolSize,Java原生线程池执行拒绝策略
          if (super.getQueue() instanceof TaskQueue) {
              final TaskQueue queue = (TaskQueue)super.getQueue();
              try {
                  //继续尝试把任务放到任务队列中去
                  if (!queue.force(command, timeout, unit)) {
                      submittedCount.decrementAndGet();
                      //如果缓冲队列也满了,插入失败,执行拒绝策略。
                      throw new RejectedExecutionException("...");
                  }
              } 
          }
      }
}

定制版的任务队列

TaskQueue 重写了 LinkedBlockingQueue 的 offer 方法,在合适的时机返回 false,返回 false 表示任务添加失败,这时线程池会创建新的线程。那什么是合适的时机呢?请看下面 offer 方法的核心源码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class TaskQueue extends LinkedBlockingQueue<Runnable> {
  ...
   @Override
  //线程池调用任务队列的方法时,当前线程数肯定已经大于核心线程数了
  public boolean offer(Runnable o) {
      //如果线程数已经到了最大值,不能创建新线程了,只能把任务添加到任务队列。
      if (parent.getPoolSize() == parent.getMaximumPoolSize()) 
          return super.offer(o);
          
      //执行到这里,表明当前线程数大于核心线程数,并且小于最大线程数。
      //表明是可以创建新线程的,那到底要不要创建呢?分两种情况:
      
      //1. 如果已提交的任务数小于当前线程数,表示还有空闲线程,无需创建新线程
      if (parent.getSubmittedCount()<=(parent.getPoolSize())) 
          return super.offer(o);
          
      //2. 如果已提交的任务数大于当前线程数,线程不够用了,返回false去创建新线程
      if (parent.getPoolSize()<parent.getMaximumPoolSize()) 
          return false;
          
      //默认情况下总是把任务添加到任务队列
      return super.offer(o);
  }
  
}

Tomcat的Session管理

Servlet 规范中定义了 HttpServletRequest 和 HttpSession 接口,Tomcat 实现了这些接口,但具体实现细节并没有暴露给开发者,因此定义了两个包装类,RequestFacade 和 StandardSessionFacade。Tomcat 是通过 Manager 来管理 Session 的,默认实现是 StandardManager。StandardContext 持有 StandardManager 的实例,并存放了 HttpSessionListener 集合,Session 在创建和销毁时,会通知监听器。

如果想了解更多文章,关注一下公众号,点赞和转发。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-05-08,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 爱编码 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
c# 利用AForge.NET组件操作摄像头
private FilterInfoCollection videoDevices;
用户5640963
2019/07/26
1.6K0
摄像头拍照功能是怎样实现的?自己动手做一个!
现如今,拍照已经融入我们的日常生活中了。我们在日常的工作生活中很多场景都会用到拍照功能。比如在登录网页或者设备时,密码错误进行拍照,防止被盗。日常进行图像识别或者图像处理前的图像获取。都需要用到我们的摄像头进行图像的获取。
跋扈洋
2021/07/19
9410
调用打印机拍照的工具类
using AForge.Controls; using AForge.Video; using AForge.Video.DirectShow; using Desktop.Protocol.Models.WhCommModels; using Destop.Infrastucture.Helper.Log; using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System
kiki.
2022/09/29
5250
使用 AForge.NET 做视频采集
AForge.NET 是基于C#设计的,在计算机视觉和人工智能方向拥有很强大功能的框架。btw... it's an open source framework. 附上官网地址: http://www.aforgenet.com/aforge/framework/ 。 今天要介绍的是AForge中的视频采集功能,这里的视频包括从摄像头等设备的输入和从视频文件的输入。 首先来认识一下 视频源播放器:VideoSourcePlayer,从摄像头和文件输入的视频,都会通过它来播放,并按帧(Frame)来输出Bit
Shao Meng
2018/04/28
2.2K0
Windows Forms:在C#中将图像转换成灰度图
本文翻译自Windows Forms: Convert an image into grayscale in C# 这篇文章向你展示在C# Windows窗体应用程序中如何将图像转换成灰度图。 创建一个新的Windows窗体应用程序项目,然后创建一个允许你可以打开图像,然后将图像转换成黑白图像的简单的UI,如下图所示:
ccf19881030
2021/03/02
1.8K0
Windows Forms:在C#中将图像转换成灰度图
【愚公系列】2023年12月 GDI+绘图专题 图形图像编程基础
本章主要介绍使用C#进行图形图像编程基础,其中包括GDI+绘图基础、C#图像处理基础以及简单的图像处理技术。
愚公搬代码
2023/12/22
1.6K0
太神奇了!使用C#实现自动核验健康码:(1)二维码识别
但是,单靠人工核验健康码容易造成人员拥堵,增加病毒交叉感染的风险,其实完全可以使用计算机来实现自动核验。
zls365
2021/09/02
2.6K0
太神奇了!使用C#实现自动核验健康码:(1)二维码识别
工业相机与镜头选型方法(含实例)
工业相机与我们手机上面的相机或者我们单反相机不同,工业相机它能够使用各种恶劣的工作环境,比如说高温,高压,高尘等。工业相机主要有面阵相机和线阵相机,线阵相机主要用于检测精度要求很高,运动速度很快的场景,而面阵相机应用更为广泛。
全栈程序员站长
2022/08/30
3.7K0
工业相机与镜头选型方法(含实例)
机器视觉系统之——镜头、相机介绍
1).分辨率(Resolution):相机每次采集图像的像素点数(Piels),对于数字工业相机机一般是直接与光电传感器的像元数对应的,对于模拟相机机则是取决于视频制式,PAL制为768*576,NTSC制为640*480。
AI机器视觉
2022/06/01
1.8K0
机器视觉系统之——镜头、相机介绍
[C#]调用本地摄像头录制视频并保存
AForge.NET是一个基于C#框架设计的开源计算机视觉和人工智能库,专为开发者和研究者设计。它提供了丰富的图像处理和视频处理算法、机器学习和神经网络模型,具有高效、易用、稳定等特点。AForge库由多个组件模块组成,包括AForge.Imaging(图像处理)、AForge.Video(视频处理)、AForge.MachineLearning(机器学习)、AForge.Neuro(神经网络)等,广泛应用于计算机视觉、图像处理、视频处理、机器学习、人脸识别、手写数字识别、物体识别等领域。
云未归来
2025/07/21
1150
[C#]调用本地摄像头录制视频并保存
一文详解工业相机和镜头选取
一问价格,至少都是大几千,贵的在十几万,心里就不禁有疑问,就这么一个破相机,为啥就卖这么贵?它跟我们常见的单反相机有什么区别?我用单反相机来拍,色彩又好,成像又清晰,它不香吗?为啥一定要用工业相机?
3D视觉工坊
2020/12/11
1.6K0
一文详解工业相机和镜头选取
【相机标定系列】相机sensor传感器尺寸,CMOS靶面尺寸,分辨​率​和​镜头​焦距,畸变处理效果,相机主点
https://blog.csdn.net/j_shui/article/details/77262947 https://www.ni.com/zh-cn/support/documentation/supplemental/18/calculating-camera-sensor-resolution-and-lens-focal-length.html#section-489585536
全栈程序员站长
2022/09/05
2.3K0
【相机标定系列】相机sensor传感器尺寸,CMOS靶面尺寸,分辨​率​和​镜头​焦距,畸变处理效果,相机主点
机器视觉工业缺陷检测(光源,相机,镜头,算法)
视觉工业检测大体分为工件尺寸测量与定位,和表面缺陷检测,及各种Logo标识的检测与识别等。
机器学习AI算法工程
2021/10/14
19.1K0
机器视觉工业缺陷检测(光源,相机,镜头,算法)
机器视觉(第4期)----工业相机综述及接口介绍
上期我们一起学习了镜头的相关知识,戳下链接: 机器视觉(第3期)----图像采集之镜头原理详述 这期我们一起学习相机和接口的相关知识,工业相机是机器视觉系统中的一个关键组件,其最本质的功能就是将光信号转变成有序的电信号。选择合适的相机也是机器视觉系统设计中的重要环节,相机的选择不仅直接决定所采集到的图像分辨率、图像质量等,同时也与整个系统的运行模式直接相关。 本期主要内容: 相机的芯片类型 黑白相机成像原理 彩色相机成像原理 相机接口类型 线阵和面阵相机 相机常见参数介绍 一. 相机的芯片类型 一般情况下,
智能算法
2018/04/03
1.6K0
机器视觉(第4期)----工业相机综述及接口介绍
(干货) |机器视觉不可不知的相机内部工作原理
工业相机是机器视觉系统中的一个关键组件,其最本质的功能就是将光信号转变成有序的电信号。选择合适的相机也是机器视觉系统设计中的重要环节,相机的选择不仅直接决定所采集到的图像分辨率、图像质量等,同时也与整个系统的运行模式直接相关。 一. 相机的芯片类型: 一般情况下,工业相机按照芯片类型可以分为CCD相机和CMOS相机,当然也有一些其他的芯片,比如富士公司生产的Super CCD芯片。这里我们只讨论市场主流的CCD相机和CMOS相机的工作原理。数码相机的CCD和CMOS都深藏于相机内部,就算您有机会看到它们的样
智能算法
2018/04/03
2.8K0
(干货) |机器视觉不可不知的相机内部工作原理
[C#]winform使用纯opencvsharp部署yolox-onnx模型
https://github.com/Megvii-BaseDetection/YOLOX 【算法介绍】
云未归来
2025/07/17
1050
[C#]winform使用纯opencvsharp部署yolox-onnx模型
WPF中UI元素跨线程访问
dotnet中线程资源独占UI元素,不能跨线程访问,可以通过Dispatcher.Invoke的方式调用,但实际处理还是UI线程中,任务量比较大的数据会增加线程的处理压力。 其实还有一种做法,可以通过设置UI元素为只读的方式,跨线程访问。 如BitmapSource跨线程访问,可以调用Freeze设置元素为只读模式。 Aforge.net跨线程传递图像资源,如下:
sofu456
2019/07/09
1.3K0
c#实战教程_ps初学者入门视频
大家好,又见面了,我是你们的朋友全栈君。 C#基础教程-c#实例教程,适合初学者。 第一章 C#语言基础 本章介绍C#语言的基础知识,希望具有C语言的读者能够基本掌握C#语言,并以此为基础,能够进一步学习用C#语言编写window应用程序和Web应用程序。当然仅靠一章的内容就完全掌握C#语言是不可能的,如需进一步学习C#语言,还需要认真阅读有关C#语言的专著。 1.1 C#语言特点 Microsoft.NET(以下简称.NET)框架是微软提出的新一代Web软件开发模型,C#语言是.NET框架中新一代的开发工具。C#语言是一种现代、面向对象的语言,它简化了C++语言在类、命名空间、方法重载和异常处理等方面的操作,它摒弃了C++的复杂性,更易使用,更少出错。它使用组件编程,和VB一样容易使用。C#语法和C++和JAVA语法非常相似,如果读者用过C++和JAVA,学习C#语言应是比较轻松的。 用C#语言编写的源程序,必须用C#语言编译器将C#源程序编译为中间语言(MicroSoft Intermediate Language,MSIL)代码,形成扩展名为exe或dll文件。中间语言代码不是CPU可执行的机器码,在程序运行时,必须由通用语言运行环境(Common Language Runtime,CLR)中的既时编译器(JUST IN Time,JIT)将中间语言代码翻译为CPU可执行的机器码,由CPU执行。CLR为C#语言中间语言代码运行提供了一种运行时环境,C#语言的CLR和JAVA语言的虚拟机类似。这种执行方法使运行速度变慢,但带来其它一些好处,主要有:  通用语言规范(Common Language Specification,CLS):.NET系统包括如下语言:C#、C++、VB、J#,他们都遵守通用语言规范。任何遵守通用语言规范的语言源程序,都可编译为相同的中间语言代码,由CLR负责执行。只要为其它操作系统编制相应的CLR,中间语言代码也可在其它系统中运行。  自动内存管理:CLR内建垃圾收集器,当变量实例的生命周期结束时,垃圾收集器负责收回不被使用的实例占用的内存空间。不必象C和C++语言,用语句在堆中建立的实例,必须用语句释放实例占用的内存空间。也就是说,CLR具有自动内存管理功能。  交叉语言处理:由于任何遵守通用语言规范的语言源程序,都可编译为相同的中间语言代码,不同语言设计的组件,可以互相通用,可以从其它语言定义的类派生出本语言的新类。由于中间语言代码由CLR负责执行,因此异常处理方法是一致的,这在调试一种语言调用另一种语言的子程序时,显得特别方便。  增加安全:C#语言不支持指针,一切对内存的访问都必须通过对象的引用变量来实现,只允许访问内存中允许访问的部分,这就防止病毒程序使用非法指针访问私有成员。也避免指针的误操作产生的错误。CLR执行中间语言代码前,要对中间语言代码的安全性,完整性进行验证,防止病毒对中间语言代码的修改。  版本支持:系统中的组件或动态联接库可能要升级,由于这些组件或动态联接库都要在注册表中注册,由此可能带来一系列问题,例如,安装新程序时自动安装新组件替换旧组件,有可能使某些必须使用旧组件才可以运行的程序,使用新组件运行不了。在.NET中这些组件或动态联接库不必在注册表中注册,每个程序都可以使用自带的组件或动态联接库,只要把这些组件或动态联接库放到运行程序所在文件夹的子文件夹bin中,运行程序就自动使用在bin文件夹中的组件或动态联接库。由于不需要在注册表中注册,软件的安装也变得容易了,一般将运行程序及库文件拷贝到指定文件夹中就可以了。  完全面向对象:不象C++语言,即支持面向过程程序设计,又支持面向对象程序设计,C#语言是完全面向对象的,在C#中不再存在全局函数、全局变量,所有的函数、变量和常量都必须定义在类中,避免了命名冲突。C#语言不支持多重继承。 1.2 编写控制台应用程序 使用SDK命令行工具编写控制台程序 第一个程序总是非常简单的,程序首先让用户通过键盘输入自己的名字,然后程序在屏幕上打印一条欢迎信息。程序的代码是这样的: using System;//导入命名空间。//为C#语言新增解释方法,解释到本行结束 class Welcome//类定义,类的概念见下一节 { /*解释开始,和C语言解释用法相同 解释结束*/ static void Main()//主程序,程序入口函数,必须在一个类中定义 { Console.WriteLine(“请键入你的姓名:”);//控制台输出字符串 Console.ReadLine();//从键盘读入数据,输入回车结束 Console.WriteLine(“欢迎!”); } } 可以用任意一种文本编辑软件完成上述代码的编写,然后把文件存盘,假设文件名叫做welcome.c
全栈程序员站长
2022/09/30
16.3K0
Emgucv视频操作--进阶1
实现功能: 播放视频 提取每一帧图片并保存 显示视频播放的时间 videowrite 视频保存的方法还未调试成功,等待后续再继续研究! //---------------------------------------------------------------------------- // Copyright (C) 2004-2019 by EMGU Corporation. All rights reserved. //----------------------------
zls365
2020/08/19
1.5K0
Emgucv视频操作--进阶1
PictureForm.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using System.Drawing.Imaging; using Packet; namespace TSProducer { pu
静默虚空
2022/05/07
2120
推荐阅读
相关推荐
c# 利用AForge.NET组件操作摄像头
更多 >
LV.0
这个人很懒,什么都没有留下~
目录
  • 手写一个Servlet
  • Servlet规范和容器
  • Tomcat系统架构
    • 连接器
    • 容器
    • 请求定位 Servlet 的过程
  • Catalina
  • Server组件
  • Service 组件
  • Engine 组件
  • Host容器
  • Context容器
    • Tomcat 的类加载器
    • Tomcat类加载隔离
    • Tomcat如何实现Servlet规范
  • Tomcat的线程池
  • Tomcat的Session管理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档