tomcat请求处理(续)

现在我们从connector.getContainer().getPipeline().getFirst().invoke(request, response)开始进入容器...

前面说到容器的时候,anne一直都只有说三个容器,engine, host, context。其实在context之下,还有一个容器,叫做wrapper,每个wapper包含了一个servlet,因此前文没有接触到servlet的时候,就暂时省略了。好了,现在我们知道了有这四个级别的容器。这四个容器素由上至下逐渐细分,形成树状结构,构成了tomcat容器结构的主体,它们都位于org.apache.catalina包内。

之前在第二部分tomcat的启动的时候,我们就看到了pipeline,pipeline是一种上级容器和下级容器进行沟通的管道,当上级容器种的request和response要被传递到下一个容器中去时,就必须通过这个管道,而value就像管道中的一个个阀门一样,给传递的request和response把把关,只有过了所有的阀门,才能被正确的传递到下一级容器中去。Tomcat中的pipeline/valve是标准的责任链模式,每个级别的容器中pipeline下都有配置value,每种类型的value专门负责做一项工作,比如验证Request的有效性、写日志等等。请求先到达第一个value,value会对其做一些工作,当工作做完后,将请求传递给下一个value。每个pipeline的最后都会有一个BasicValue(比如Engine的StandardEngineValue、Host的StanadrdHostValue),它负责寻找下一级容器的pipeline,并且将请求传递给下一级容器的pipeline中的value,这样一直传递下去直到真正的servlet。

在tomcat中,这个责任链真是最最标准,最最基础的责任链了,我们来看一下StandardPipeline中是怎样为pipeline添加value的:

Java代码 复制代码 收藏代码
  1. public void addValve(Valve valve) {       
  2.     if (valve instanceof Contained)   
  3.         ((Contained) valve).setContainer(this.container);   
  4.   
  5.     ...// Start the new component if necessary  
  6.   
  7.     // Add this Valve to the set associated with this Pipeline  
  8.    if (first == null) {   
  9.         first = valve;   
  10.         valve.setNext(basic);   
  11.     } else {   
  12.         Valve current = first;   
  13.         while (current != null) {   
  14. if (current.getNext() == basic) {   
  15.     current.setNext(valve);   
  16.     valve.setNext(basic);   
  17.     break;   
  18. }   
  19. current = current.getNext();   
  20.   
  21.     }   
    public void addValve(Valve valve) {    
        if (valve instanceof Contained)
            ((Contained) valve).setContainer(this.container);

        ...// Start the new component if necessary

        // Add this Valve to the set associated with this Pipeline
①      if (first == null) {
        	first = valve;
        	valve.setNext(basic);
        } else {
            Valve current = first;
            while (current != null) {
				if (current.getNext() == basic) {
					current.setNext(valve);
					valve.setNext(basic);
					break;
				}
				current = current.getNext();
			}
        }
}


First(代码①处)记录了这个pipeline关联着的第一个value,basic则是pipeline关联的最后一个value,执行完basic,就进入到下一级的容器中去了。
从上面的代码,我们可以看到,pipeline用了一种最基本的方法来维持这个value的链条,每个value都保持了一个下个value的引用。于是,我们就看到connector.getContainer().getPipeline().getFirst()就得到了engine中的第一个value。为什么呢?因为connector.getContainer()得到的是connector父节点(也就是service)中的engine容器,进而getPipeline()得到engine的pipeline,getFirst()得到engine的pipeline中的第一个value。

好了,下面我们来看看具体进入到了value,都做了些什么,我们来看一个AccessLogValue,很明显,是一个用来写日志的value。

Java代码 复制代码 收藏代码
  1. public void invoke(Request request, Response response) throws IOException, ServletException {   
  2.     if (started && getEnabled()) {                   
  3.         long t1 = System.currentTimeMillis();   
  4.   
  5.        getNext().invoke(request, response);   
  6.   
  7.         long t2 = System.currentTimeMillis();   
  8.         long time = t2 - t1;       
  9.         StringBuffer result = new StringBuffer();   
  10.     ...    //add log info into result  
  11.         log(result.toString());   
  12.     } else  
  13.         getNext().invoke(request, response);          
  14. }  
    public void invoke(Request request, Response response) throws IOException, ServletException {
        if (started && getEnabled()) {                
            long t1 = System.currentTimeMillis();
    
  ②        getNext().invoke(request, response);
    
            long t2 = System.currentTimeMillis();
            long time = t2 - t1;    
            StringBuffer result = new StringBuffer();
  			...    //add log info into result
            log(result.toString());
        } else
            getNext().invoke(request, response);       
    }


OK,很明显,value是通过getNext()方法来得到下一个责任链上的value的(代码②处)。那么当这个责任链到头了,进入到了最后一个value的话是怎么处理的呢?前面说了,每个容器的pipeline的责任链的末端都会有一个特殊的value,Engine的StandardEngineValue、Host的StanadrdHostValue,Context的StanadrdContextValue,Wrapper的StanadrdWrapperValue,这些叫做basicValue,对于容器来说,这些basicValue是一定会有的。我们就看一个StandardEngineValue:

Java代码 复制代码 收藏代码
  1.     public final void invoke(Request request, Response response)   
  2.         throws IOException, ServletException {   
  3.         // Select the Host to be used for this Request  
  4.         Host host = request.getHost();   
  5.         if (host == null) {   
  6.             response.sendError   
  7.                 (HttpServletResponse.SC_BAD_REQUEST,   
  8.                  sm.getString("standardEngine.noHost",    
  9.                               request.getServerName()));   
  10.             return;   
  11.         }   
  12.   
  13.         // Ask this Host to process this request  
  14. ③      host.getPipeline().getFirst().invoke(request, response);   
  15.     }  
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
        // Select the Host to be used for this Request
        Host host = request.getHost();
        if (host == null) {
            response.sendError
                (HttpServletResponse.SC_BAD_REQUEST,
                 sm.getString("standardEngine.noHost", 
                              request.getServerName()));
            return;
        }

        // Ask this Host to process this request
③      host.getPipeline().getFirst().invoke(request, response);
    }


我们可以看到在basicValue中得到了下一级容器,并且调用了下级容器的pipeline中的first value(代码③处)。对于StanadrdHostValue,StanadrdContextValue和StanadrdWrapperValue来说,也都是类似的。BasicValue放在org.apache.catalina.core下,而其他的value都放在org.apache.catalina.value下面。



Ok,大家继续脑补一下,现在已经进入到StanadrdWrapperValue了。那根据我们对tomcat的了解下面应该做什么了呢?对了,接下来我们就要穿越过层层filter,进入servlet了。
我们看一下StanadrdWrapperValuede的invoke方法,为了看起来方便一点,anne就大刀阔斧的只截取了一点点我们需要关注的内容。

Java代码 复制代码 收藏代码
  1. public final void invoke(Request request, Response response)   
  2.     throws IOException, ServletException {   
  3.      ...   
  4.     servlet = wrapper.allocate();   
  5.   
  6.     // 下面开始创建filter链啦   
  7.    ApplicationFilterFactory factory =   
  8.         ApplicationFilterFactory.getInstance();   
  9.     ApplicationFilterChain filterChain =   
  10.         factory.createFilterChain(request, wrapper, servlet);   
  11.     ...   
  12.   
  13.     // 调用filter链   
  14.     filterChain.doFilter(request.getRequest(),response.getResponse());   
  15.   
  16.     if (servlet != null) {   
  17.        wrapper.deallocate(servlet);   
  18.     }   
  19.  }  
    public final void invoke(Request request, Response response)
        throws IOException, ServletException {
         ...
   ④	servlet = wrapper.allocate();

        // 下面开始创建filter链啦
   ⑤   ApplicationFilterFactory factory =
            ApplicationFilterFactory.getInstance();
        ApplicationFilterChain filterChain =
            factory.createFilterChain(request, wrapper, servlet);
        ...

        // 调用filter链
        filterChain.doFilter(request.getRequest(),response.getResponse());

        if (servlet != null) {
           wrapper.deallocate(servlet);
        }
     }


因为一个wrapper是对应与一个servlet的,所以wrapper.allocate()就是得到它负责封装的那个servlet(代码④处)。
下面在代码⑤处,我们就要来创建filter chain了,anne照样把createFilterChain中的方法稍微提取了一下:

Java代码 复制代码 收藏代码
  1.   public ApplicationFilterChain createFilterChain   
  2.         (ServletRequest request, Wrapper wrapper, Servlet servlet) {   
  3.     ApplicationFilterChain filterChain = new ApplicationFilterChain();   
  4.     filterChain.setServlet(servlet);    
  5.   
  6.     // 得到context的filter mapping  
  7.     StandardContext context = (StandardContext) wrapper.getParent();   
  8.     FilterMap filterMaps[] = context.findFilterMaps();   
  9.   
  10.     // 遍历filterMaps,如果有符合这个servlet的filter就把它加到filter chain中  
  11.     for (int i = 0; i < filterMaps.length; i++) {   
  12.         if (!matchDispatcher(filterMaps[i] ,dispatcher)) {   
  13.           continue;   
  14.         }   
  15.         if (!matchFiltersURL(filterMaps[i], requestPath))   
  16.             continue;   
  17.   
  18.         ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)   
  19.                 context.findFilterConfig(filterMaps[i].getFilterName());   
  20.         if (filterConfig == null) {   
  21.             continue;   
  22.         }   
  23.         filterChain.addFilter(filterConfig);   
  24.      }   
  25.     return (filterChain);   
  26. }  
  public ApplicationFilterChain createFilterChain
        (ServletRequest request, Wrapper wrapper, Servlet servlet) {
    ApplicationFilterChain filterChain = new ApplicationFilterChain();
    filterChain.setServlet(servlet); 

    // 得到context的filter mapping
    StandardContext context = (StandardContext) wrapper.getParent();
    FilterMap filterMaps[] = context.findFilterMaps();

    // 遍历filterMaps,如果有符合这个servlet的filter就把它加到filter chain中
    for (int i = 0; i < filterMaps.length; i++) {
        if (!matchDispatcher(filterMaps[i] ,dispatcher)) {
          continue;
        }
        if (!matchFiltersURL(filterMaps[i], requestPath))
            continue;

        ApplicationFilterConfig filterConfig = (ApplicationFilterConfig)
                context.findFilterConfig(filterMaps[i].getFilterName());
        if (filterConfig == null) {
            continue;
        }
        filterChain.addFilter(filterConfig);
     }
    return (filterChain);
}


我们都知道filter chain也是采用的责任链模式,前面我们说到pipeline中的value也是采用的责任链模式,每个value都持有了下一个value的引用。我们可以看看ahuaxuan的《请问责任链真的是一种设计模式吗》这篇文章(http://ahuaxuan.iteye.com/blog/105825),这里面谈到了三种责任链的实现方式,filter chain就是这第三种潇洒版责任链。
前面pipeline的value链是通过引用的方式来形成一条隐形的链条,而这里,filterChain是真是存在的。我们只需要把这个链条上面的一个一个关节通过filterChain.addFilter()装上即可。这个filterChain中的每个关节都是一个FilterConfig对象,这个对象中包含了filter,context,initParameter等等。

链条组装完毕!
启动filterChain.doFilter()!
doFilter方法主要调用了internalDoFilter()。

Java代码 复制代码 收藏代码
  1.     public void doFilter(ServletRequest request, ServletResponse response)   
  2.         throws IOException, ServletException {   
  3.         ...   
  4.         internalDoFilter(request,response);   
  5.     }   
  6.   
  7.     private void internalDoFilter(ServletRequest request,    
  8.                                   ServletResponse response)   
  9.         throws IOException, ServletException {    
  10.   ⑥    if (pos < n) {   
  11.             ApplicationFilterConfig filterConfig = filters[pos++];   
  12.             Filter filter = null;    
  13.             filter = filterConfig.getFilter();   
  14. ...   
  15.             filter.doFilter(request, response, this);   
  16.         }   
  17.   
  18.    ⑦       if ((request instanceof HttpServletRequest) &&   
  19.                 (response instanceof HttpServletResponse)) {   
  20.                     servlet.service((HttpServletRequest) request,   
  21.                                     (HttpServletResponse) response);   
  22.                 }   
  23.             } else {   
  24.                 servlet.service(request, response);   
  25.             }   
  26.     }  
    public void doFilter(ServletRequest request, ServletResponse response)
        throws IOException, ServletException {
        ...
        internalDoFilter(request,response);
    }

    private void internalDoFilter(ServletRequest request, 
                                  ServletResponse response)
        throws IOException, ServletException { 
  ⑥    if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = null; 
            filter = filterConfig.getFilter();
...
            filter.doFilter(request, response, this);
		}

   ⑦       if ((request instanceof HttpServletRequest) &&
                (response instanceof HttpServletResponse)) {
                    servlet.service((HttpServletRequest) request,
                                    (HttpServletResponse) response);
                }
            } else {
                servlet.service(request, response);
            }
    }


代码⑥处的pos是filter chain上的一个标识位,表示现在执行的是哪一个filter。然后调用了filter的doFilter方法。我们来看一个例子,一个用来记录执行时间的filter。

Java代码 复制代码 收藏代码
  1.  public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)   
  2. rows IOException, ServletException {   
  3. if (attribute != null)   
  4.     request.setAttribute(attribute, this);   
  5.   
  6. long startTime = System.currentTimeMillis();   
  7. ⑧     chain.doFilter(request, response);   
  8. long stopTime = System.currentTimeMillis();   
  9. filterConfig.getServletContext().log   
  10.     (this.toString() + ": " + (stopTime - startTime) +   
  11.      " milliseconds");   
  12.  }  
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
	throws IOException, ServletException {
	  if (attribute != null)
	      request.setAttribute(attribute, this);

	  long startTime = System.currentTimeMillis();
   ⑧     chain.doFilter(request, response);
	  long stopTime = System.currentTimeMillis();
	  filterConfig.getServletContext().log
	      (this.toString() + ": " + (stopTime - startTime) +
	       " milliseconds");
    }


首先把当前正在执行的这个filter作为一个attribute放到request中去,接下来我们可以看到在两个time之间,调用了chain.doFilter()(代码⑧处),Chain就是filter chain。这下又要回到internalDoFilter,pos又加了1,就变成执行filterChain的下一个filter了。
如果这个filter chain已经到头了(pos=n),那就进入代码⑦处,就表示request和response已经突破filter的重重阻拦,可以进入servlet了。因此,我们就可以调用wrapper内的servlet的service()方法了。自此进入servlet。这下我们知道了filter原来是这样执行的,它是一层包着一层,一直不断的向内层进发,当进入到最内层,就是servlet了。



好了,我们现在终于进入servlet的service方法了。

Java代码 复制代码 收藏代码
  1.     protected void service(HttpServletRequest req, HttpServletResponse resp)   
  2.         throws ServletException, IOException {   
  3.   
  4.         String method = req.getMethod();   
  5.   
  6.         if (method.equals(METHOD_GET)) {   
  7. ...   
  8.             doGet(req, resp);   
  9.         } else if (method.equals(METHOD_HEAD)) {   
  10.             doHead(req, resp);   
  11.         } else if (method.equals(METHOD_POST)) {   
  12.             doPost(req, resp);               
  13.         } else if (method.equals(METHOD_PUT)) {   
  14.             doPut(req, resp);                       
  15.         } else if (method.equals(METHOD_DELETE)) {   
  16.             doDelete(req, resp);               
  17.         } else if (method.equals(METHOD_OPTIONS)) {   
  18.             doOptions(req,resp);               
  19.         } else if (method.equals(METHOD_TRACE)) {   
  20.             doTrace(req,resp);               
  21.         } else {   
  22.             ...// NO servlet supports  
  23.         }   
  24.     }  
    protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
...
            doGet(req, resp);
        } else if (method.equals(METHOD_HEAD)) {
            doHead(req, resp);
        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);                    
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);            
        } else {
            ...// NO servlet supports
        }
    }


Service方法里面其实很简单,就是根据不同的请求,调用不同的do***()方法。Http的请求类型一共有如上七种。
一个HttpServlet的子类必须至少覆写以下方法中的一个。
1) doGet()方法,适用于HTTP GET请求。自动支持一个HTTP HEAD请求。当覆写doGet()时,首先读取请求数据,写入响应的head,然后获得响应的writer或输出流对象,最后写入响应数据。
2) doPost()方法,适用于HTTP POST请求。覆写该方法与doGet()类似。
3) doPut()方法,适用于HTTP PUT请求。PUT操作允许客户好像使用FTP一样把文件放置到服务器。
4) doDelete()方法,适用于HTTP DELETE请求。DELETE操作允许客户从服务器中删除一个文档或网页。
5) init()和destroy()方法,管理Servlet生命周期中的资源。
6) getServletInfo()方法,提供Servlet本身的信息。

另外还有不需要覆写的方法:
7) doHead()方法,适用于HTTP HEAD请求。当客户端只需要知道响应头,比如Content-Type或者Content-Length,客户端只需要发送一个HEAD请求。HTTP HEAD会准确地计算输出的字节数来设定Content-Length。如果覆写该方法,可以避免计算响应的BODY,而只需设置响应头以改善性能。
8) doOptions()方法,适用于OPTIONS请求。OPTIONS操作决定服务器支持哪种HTTP方法,并返回一个适当的头信息。例如,如果一个servlet覆写了doGet()方法,doOptions()方法将会返回如下头信息:Allow: GET, HEAD, TRACE, OPTIONS。
9) doTrace()方法,适用于TRACE请求。该方法用于程序调试,无需被覆写。

尽管说有这么多do***()方法,可是我们常用的就只有doGet()和doPost()。 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。
Go语言(也称为Golang)是由Google开发的一种静态强类型、编译型的编程语言。它旨在成为一门简单、高效、安全和并发的编程语言,特别适用于构建高性能的服务器和分布式系统。以下是Go语言的一些主要特点和优势: 简洁性:Go语言的语法简单直观,易于学习和使用。它避免了复杂的语法特性,如继承、重载等,转而采用组合和接口来实现代码的复用和扩展。 高性能:Go语言具有出色的性能,可以媲美C和C++。它使用静态类型系统和编译型语言的优势,能够生成高效的机器码。 并发性:Go语言内置了对并发的支持,通过轻量级的goroutine和channel机制,可以轻松实现并发编程。这使得Go语言在构建高性能的服务器和分布式系统时具有天然的优势。 安全性:Go语言具有强大的类型系统和内存管理机制,能够减少运行时错误和内存泄漏等问题。它还支持编译时检查,可以在编译阶段就发现潜在的问题。 标准库:Go语言的标准库非常丰富,包含了大量的实用功能和工具,如网络编程、文件操作、加密解密等。这使得开发者可以更加专注于业务逻辑的实现,而无需花费太多时间在底层功能的实现上。 跨平台:Go语言支持多种操作系统和平台,包括Windows、Linux、macOS等。它使用统一的构建系统(如Go Modules),可以轻松地跨平台编译和运行代码。 开源和社区支持:Go语言是开源的,具有庞大的社区支持和丰富的资源。开发者可以通过社区获取帮助、分享经验和学习资料。 总之,Go语言是一种简单、高效、安全、并发的编程语言,特别适用于构建高性能的服务器和分布式系统。如果你正在寻找一种易于学习和使用的编程语言,并且需要处理大量的并发请求和数据,那么Go语言可能是一个不错的选择。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值