惊呆了!手写4个mini版的tomcat!

发表于 3年以前  | 总阅读数:370 次

写在前面

Apache Tomcat 是Java Servlet, JavaServer Pages (JSP),Java表达式语言和Java的WebSocket技术的一个开源实现 ,通常我们将Tomcat称为Web容器或者Servlet容器 。

今天,我们就来手写tomcat,但是说明一下:咱们不是为了装逼才来写tomcat,而是希望大家能更多的理解和掌握tomcat。

废话不多说了,直接开干。

基本结构

tomcat架构图

我们可以把上面这张架构图做简化,简化后为:

什么是http协议

Http是一种网络应用层协议,规定了浏览器与web服务器之间如何通信以及数据包的结构。

通信大致可以分为四步:

  1. 先建立连接。
  2. 发送请求数据包。
  3. 发送响应数据包。
  4. 关闭连接。

优点

web服务器可以利用有限的连接为尽可能多的客户请求服务。

tomcat中Servlet的运作方式
  1. 在浏览器地址栏输入http://ip:port/servlet-day01/hello
  2. 浏览器依据IP、port建立连接(即与web服务器之间建立网络连接)。
  3. 浏览器需要将相关数据打包(即按照http协议要求,制作一个 请求数据包,包含了一些数据,比如请求资源路径),并且将请求 数据包发送出去。
  4. web服务器会将请求数据包中数据解析出来,并且将这些数据添加 到request对象,同时,还会创建一个response对象。
  5. web服务器创建Servlet对象,然后调用该对象的service方法(会将request和response作为参数)。注:在service方法里面,通过使用request获得请求相关的数据, 比如请求参数值,然后将处理结果写到response。
  6. web服务器将response中的数据取出来,制作响应数据包,然后发送给浏览器。
  7. 浏览器解析响应数据包,然后展现。

可以总结唯一张图:

什么是Servlet呢?

Servlet是JavaEE规范的一种,主要是为了扩展Java作为Web服务的功能,统一接口。由其他内部厂商如tomcat,jetty内部实现web的功能。如一个http请求到来:容器将请求封装为servlet中的HttpServletRequest对象,调用init(),service()等方法输出response,由容器包装为httpresponse返回给客户端的过程。

什么是Servlet规范?

  • 从 Jar 包上来说,Servlet 规范就是两个 Jar 文件。servlet-api.jar 和 jsp-api.jar,Jsp 也是一种 Servlet。
  • 从package上来说,就是 javax.servlet 和 javax.servlet.http 两个包。
  • 从接口来说,就是规范了 Servlet 接口、Filter 接口、Listener 接口、ServletRequest 接口、ServletResponse 接口等。类图如下:

第一版:Socket版

使用Socket编程,实现简单的客户端和服务端的聊天。

服务端代码如下:

package com.tian.v1;

import java.io.*;
import java.net.*;


public class Server {

    public static String readline = null;
    public static String inTemp = null;
    public static String turnLine = "\n";
    public static final String client = "客户端:";
    public static final String server = "服务端:";
    public static final int PORT = 8090;

    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(PORT);
        System.out.println("服务端已经准备好了");
        Socket socket = serverSocket.accept();

        BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
        BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
        while (true) {
            inTemp = socketIn.readLine();
            if (inTemp != null &&inTemp.contains("over")) {
                systemIn.close();
                socketIn.close();
                socketOut.close();
                socket.close();
                serverSocket.close();
            }
            System.out.println(client + inTemp);
            System.out.print(server);

            readline = systemIn.readLine();

            socketOut.println(readline);
            socketOut.flush();
        }
    }
}

客户端代码如下:

package com.tian.v1;

import java.io.*;
import java.net.*;

public class Client {

    public static void main(String[] args) throws Exception {
        String readline;
        String inTemp;
        final String client = "客户端说:";
        final String server = "服务端回复:";

        int port = 8090;
        byte[] ipAddressTemp = {127, 0, 0, 1};
        InetAddress ipAddress = InetAddress.getByAddress(ipAddressTemp);

        //首先直接创建socket,端口号1~1023为系统保存,一般设在1023之外
        Socket socket = new Socket(ipAddress, port);

        BufferedReader systemIn = new BufferedReader(new InputStreamReader(System.in));
        BufferedReader socketIn = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        PrintWriter socketOut = new PrintWriter(socket.getOutputStream());
        while (true) {
            System.out.print(client);
            readline = systemIn.readLine();

            socketOut.println(readline);
            socketOut.flush();
            //处理
            inTemp = socketIn.readLine();
            if (inTemp != null && inTemp.contains("over")) {
                systemIn.close();
                socketIn.close();
                socketOut.close();
                socket.close();
            }
            System.out.println(server + inTemp);
        }
    }
}

过程如下:

第二版:我们直接请求http://localhost:8090

实现代码如下:

package com.tian.v2;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {
    /**
     * 设定启动和监听端口
     */
    private int port = 8090;

    /**
     * 启动函数
     *
     * @throws IOException
     */
    public void start() throws IOException {
        System.out.println("my tomcat starting...");
        String responseData = "6666666";
        ServerSocket socket = new ServerSocket(port);
        while (true) {
            Socket accept = socket.accept();
            OutputStream outputStream = accept.getOutputStream();
            String responseText = HttpProtocolUtil.getHttpHeader200(responseData.length()) + responseData;
            outputStream.write(responseText.getBytes());
            accept.close();
        }
    }

    /**
     * 启动入口
     */
    public static void main(String[] args) throws IOException {
        MyTomcat tomcat = new MyTomcat();
        tomcat.start();
    }
}

再写一个工具类,内容如下;

ackage com.tian.v2;

public class HttpProtocolUtil {

    /**
     * 200 状态码,头信息
     *
     * @param contentLength 响应信息长度
     * @return 200 header info
     */
    public static String getHttpHeader200(long contentLength) {
        return "HTTP/1.1 200 OK \n" + "Content-Type: text/html \n"
                + "Content-Length: " + contentLength + " \n" + "\r\n";
    }

    /**
     * 为响应码 404 提供请求头信息(此处也包含了数据内容)
     *
     * @return 404 header info
     */
    public static String getHttpHeader404() {
        String str404 = "<h1>404 not found</h1>";
        return "HTTP/1.1 404 NOT Found \n" + "Content-Type: text/html \n"
                + "Content-Length: " + str404.getBytes().length + " \n" + "\r\n" + str404;
    }
}

启动main方法:

使用IDEA访问:

在浏览器访问:

自此,我们的第二版本搞定。下面继续第三个版本;

第三版:封装请求信息和响应信息

一个http协议的请求包含三部分:

  • 方法 URI 协议/版本
  • 请求的头部
  • 主体内容

比如

POST /index.html HTTP/1.1
Accept: text/plain; text/html
Accept-Language: en-gb
Connection: Keep-Alive
Host: localhost
User-Agent: Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)
Content-Length: 33
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate

lastName=tian&firstName=JohnTian

简单的解释

  • 数据的第一行包括:方法、URI、协议和版本。在这个例子里,方法为POST,URI为/index.html,协议为HTTP/1.1,协议版本号为1.1。他们之间通过空格来分离。
  • 请求头部从第二行开始,使用英文冒号(:)来分离键和值。
  • 请求头部和主体内容之间通过空行来分离,例子中的请求体为表单数据。

类似于http协议的请求,响应也包含三个部分。

  • 协议 状态 状态描述
  • 响应的头部
  • 主体内容

比如:

HTTP/1.1 200 OK
Server: Microsoft-IIS/4.0
Date: Mon, 5 Jan 2004 13:13:33 GMT
Content-Type: text/html
Last-Modified: Mon, 5 Jan 2004 13:13:12 GMT
Content-Length: 112

<html>
<head>
<title>HTTP Response Example</title> </head>
<body>
Welcome to Brainy Software
</body>
</html>

简单解释

  • 第一行,HTTP/1.1 200 OK表示协议、状态和状态描述。
  • 之后表示响应头部。
  • 响应头部和主体内容之间使用空行来分离。

代码实现

创建一个工具类,用来获取静态资源信息。

package com.tian.v3;

import com.tian.v2.HttpProtocolUtil;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

/**
 * 提取了一些共用类和函数
 */
public class ResourceUtil {

    /**
     * 根据请求 url 获取完整绝对路径
     */
    public static String getPath(String url) {
        String path = ResourceUtil.class.getResource("/").getPath();
        return path.replaceAll("\\\\", "/") + url;
    }

    /**
     * 输出静态资源信息
     */
    public static void outputResource(InputStream input, OutputStream output) throws IOException {
        int count = 0;
        while (count == 0) {
            count = input.available();
        }
        int resourceSize = count;
        output.write(HttpProtocolUtil.getHttpHeader200(resourceSize).getBytes());
        long written = 0;
        int byteSize = 1024;
        byte[] bytes = new byte[byteSize];
        while (written < resourceSize) {
            if (written + byteSize > resourceSize) {
                byteSize = (int) (resourceSize - written);
                bytes = new byte[byteSize];
            }
            input.read(bytes);
            output.write(bytes);
            output.flush();
            written += byteSize;
        }
    }
}

另外HttpProtocolUtil照样用第二版本中。

再创建Request类,用来解析并存放请求相关参数。

package com.tian.v3;

import java.io.IOException;
import java.io.InputStream;

public class Request {
    /**
     * 请求方式, eg: GET、POST
     */
    private String method;

    /**
     * 请求路径,eg: /index.html
     */
    private String url;

    /**
     * 请求信息输入流 <br>
     * 示例
     * <pre>
     *  GET / HTTP/1.1
     *  Host: localhost
     *  Connection: keep-alive
     *  Pragma: no-cache
     *  Cache-Control: no-cache
     *  Upgrade-Insecure-Requests: 1
     *  User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
     * </pre>
     */
    private InputStream inputStream;

    public Request() {
    }

    public Request(InputStream inputStream) throws IOException {
        this.inputStream = inputStream;
        int count = 0;
        while (count == 0) {
            count = inputStream.available();
        }
        byte[] bytes = new byte[count];
        inputStream.read(bytes);
        // requestString 参考:this.inputStream 示例
        String requestString = new String(bytes);
        // 按换行分隔
        String[] requestStringArray = requestString.split("\\n");
        // 读取第一行数据,即:GET / HTTP/1.1
        String firstLine = requestStringArray[0];
        // 遍历第一行数据按空格分隔
        String[] firstLineArray = firstLine.split(" ");
        this.method = firstLineArray[0];
        this.url = firstLineArray[1];
    }

    public String getMethod() {
        return method;
    }

    public void setMethod(String method) {
        this.method = method;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public InputStream getInputStream() {
        return inputStream;
    }

    public void setInputStream(InputStream inputStream) {
        this.inputStream = inputStream;
    }
}

把第二版的MyTomcat进行小小调整:

package com.tian.v3;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class MyTomcat {

    private static final int PORT = 8090;
    public void start() throws IOException {
        System.out.println("my tomcat starting...");
        ServerSocket socket = new ServerSocket(PORT);
        while (true) {
            Socket accept = socket.accept();
            OutputStream outputStream = accept.getOutputStream();
            // 分别封装 Request 和 Response
            Request request = new Request(accept.getInputStream());
            Response response = new Response(outputStream);
            // 根据 request 中的 url,输出
            response.outputHtml(request.getUrl());
            accept.close();
        }
    }

    public static void main(String[] args) throws IOException {
        MyTomcat tomcat = new MyTomcat();
        tomcat.start();
    }
}

然后再创建一个index.html,内容很简单:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>hello world</title>
</head>
<body>
<h2> you already succeed!</h2>
</body>
</html>

这一需要注意,index.html文件的存放路径不放错了,视本地路径来定哈,放在classes文件夹下的。你可以debug试试,看看你应该放在那个目录下。

启动MyTomcat。

访问http://localhost:8090/index.html

自此,我们针对于Http请求参数和相应参数做了一个简单的解析以及封装。

尽管其中还有很多问题,但是字少看起来有那点像样了。我们继续第四版,

第四版:实现动态请求资源

用过servlet的同学都知道,Servlet中有三个很重要的方法init、destroy 、service 。其中还记得我们自己写LoginServlet的时候,还会重写HttpServlet中的doGet()和doPost()方法。下面们就自己来搞一个:

Servlet类代码如下:

public interface Servlet {
    void init() throws Exception;
    void destroy() throws Exception;
    void service(Request request, Response response) throws Exception;
}

然后再写一个HttpServlet来实现Servlet。

代码实现如下:

package com.tian.v4;

public abstract class HttpServlet implements Servlet {
    @Override
    public void init() throws Exception {

    }

    @Override
    public void destroy() throws Exception {

    }

    @Override
    public void service(Request request, Response response) throws Exception {
        String method = request.getMethod();
        if ("GET".equalsIgnoreCase(method)) {
            doGet(request, response);
        } else {
            doPost(request, response);
        }
    }
    public abstract void doGet(Request request, Response response) throws Exception;

    public abstract void doPost(Request request, Response response) throws Exception;
}

下面我们就来写一个自己的Servlet,比如LoginServlet。

package com.tian.v4;

public class LoginServlet  extends HttpServlet {

    @Override
    public void doGet(Request request, Response response) throws Exception {
        String repText = "<h1> LoginServlet by GET method</h1>";
        response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
    }

    @Override
    public void doPost(Request request, Response response) throws Exception {
        String repText = "<h1>LoginServlet by POST method</h1>";
        response.output(HttpProtocolUtil.getHttpHeader200(repText.length()) + repText);
    }

    @Override
    public void init() throws Exception {
    }

    @Override
    public void destroy() throws Exception {
    }
}

大家是否还记得,我们在学习Servlet的时候,在resources目录下面有个web.xml。我们这个版本也把这个xml文件给引入。

<?xml version="1.0" encoding="utf-8"?>
<web-app>
    <servlet>
        <servlet-name>login</servlet-name>
        <servlet-class>com.tian.v4.LoginServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>login</servlet-name>
        <url-pattern>/login</url-pattern>
    </servlet-mapping>
</web-app>

既然引入了xml文件,那我们就需要去读取这个xml文件,并解析器内容。所以这里我们需要引入两个jar包。

<dependencies>
    <dependency>
        <groupId>dom4j</groupId>
        <artifactId>dom4j</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>jaxen</groupId>
        <artifactId>jaxen</artifactId>
        <version>1.1.6</version>
    </dependency>
</dependencies>

万事俱备,只欠东风了。这时候我们来吧MyTomcat这个类做一些调整即可。

下面有个很重要的initServlet()方法,刚刚是对应下面这张图中的List servlets,但是我们代码里使用的是Map来存储Servlet的,意思就那么个意思,把Servlet放在集合里。

这也就是为什么大家都把Tomcat叫做Servlet容器的原因,其实真正的容器还是java集合。

package com.tian.v4;

import com.tian.v3.RequestV3;
import com.tian.v3.ResponseV3;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MyTomcat {
    /**
     * 设定启动和监听端口
     */
    private static final int PORT = 8090;
    /**
     * 存放 Servlet信息,url: Servlet 实例
     */
    private Map<String, HttpServlet> servletMap = new HashMap<>();

    public void start() throws Exception {

        System.out.println("my tomcat starting...");
        initServlet();
        ServerSocket socket = new ServerSocket(PORT);
        while (true) {
            Socket accept = socket.accept();
            OutputStream outputStream = accept.getOutputStream();
            // 分别封装 RequestV3 和 ResponseV3
            RequestV4 requestV3 = new RequestV4(accept.getInputStream());
            ResponseV4 responseV3 = new ResponseV4(outputStream);
            // 根据 url 来获取 Servlet
            HttpServlet httpServlet = servletMap.get(requestV3.getUrl());
            // 如果 Servlet 为空,说明是静态资源,不为空即为动态资源,需要执行 Servlet 里的方法
            if (httpServlet == null) {
                responseV3.outputHtml(requestV3.getUrl());
            } else {
                httpServlet.service(requestV3, responseV3);
            }
            accept.close();
        }
    } 

    public static void main(String[] args) throws Exception {
        MyTomcat tomcat = new MyTomcat();
        tomcat.start();
    }


    /**
     * 解析web.xml文件,把url和servlet解析出来,
     * 并保存到一个java集合里(Map)
     */
    public void initServlet() throws Exception {
        InputStream resourceAsStream = this.getClass().getClassLoader().getResourceAsStream("web.xml");
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(resourceAsStream);
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//servlet");
        for (Element element : list) {
            // <servlet-name>show</servlet-name>
            Element servletnameElement = (Element) element.selectSingleNode("servlet-name");
            String servletName = servletnameElement.getStringValue();
            // <servlet-class>server.ShowServlet</servlet-class>
            Element servletclassElement = (Element) element.selectSingleNode("servlet-class");
            String servletClass = servletclassElement.getStringValue();

            // 根据 servlet-name 的值找到 url-pattern
            Element servletMapping = (Element) rootElement.selectSingleNode("/web-app/servlet-mapping[servlet-name='" + servletName + "']");
            // /show
            String urlPattern = servletMapping.selectSingleNode("url-pattern").getStringValue();
            servletMap.put(urlPattern, (HttpServlet) Class.forName(servletClass).getDeclaredConstructor().newInstance());
        }
    }
}

启动,再次访问http://localhost:8090/index.html

同时,我们可以访问http://localhost:8090/login

到此,第四个版本也搞定了。

但是前面四个版本都有一个共同的问题,全部使用的是BIO。

BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

所以,大家在网上看到的手写tomcat的,也有使用线程池来做的,这里希望大家能get到为什么使用线程池来实现。另外,其实在tomcat高版本中已经没有使用BIO了。

而 HTTP/1.1默认使用的就是NIO了。

但这个只是通信方式,重点是我们要理解和掌握tomcat的整体实现。

总结

另外,发现上面都是讲配置文件解析,并将对应数据保存起来。熟悉这个套路后,大家是不是想到,我们很多配置项都是在server.xml中,还记得否?也是可以通过解析某个目录下的server.xml文件,并把内容赋给java中相应的变量罢了。

比如:

1.server.xml中的端口配置,我们是在代码里写死的而已,改成MyTomcat启动的时候去解析并获取不久得了吗?

2.我们通常是将我们项目的打成war,然后解压到某个目录下,最后还不是可以通过读取这个解压后的某个目录中找到web.xml,然后用回到上面的web.xml解析了。

本文主要是分享如何从一个塑料版到黄金版、然后铂金版,最后到砖石版。可以把加入线程池的版本称之为星耀版,最后把相关server.xml解析,以及读取我们放入到tomcat中项目解析可以称之为王者版。

技术点:Socket编程、InputStream、OutputStream、线程池、xml文件解析、反射。更高级版本中NIO,AIO等。

不是为了装逼而来搞这个tomcat,而是为了我们更深刻的理解tomcat的原理。

参考:http://ccx4.cn/Sr5yL

本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/gu49D6O4LhcoZlHoOg5A8A

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237326次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8173次阅读
 目录