美國率先宣布開放中國產熟禽肉進口

摘錄自2019年11月09日中央通訊社美國報導

中美貿易談判雙方主談人10月25日通話時提到,將互相解禁禽肉進口。隨著美國8日率先宣布對中國解禁,分析認為,若中國也解禁,不僅美國農民將受益,也能緩解被非洲豬瘟抬高的肉價。

10月25日中美通話中協議,美方確認在進口中國熟製禽肉及鯰魚產品時,使用等效的監管體系;中國則就解除美國禽肉對中出口禁令,及應用肉類產品公共衛生訊息系統達成共識。美國政府於當地時間11月8日公布中國自產原料禽肉輸美的最終規則,宣布確認雙方相關監管體系等效,中國國產熟禽肉將可對美出口。

針對美方率先宣布的消息,中國海關總署今天以發言人名義表示,這讓中國繼加拿大、墨西哥、智利等國之後,有資格對美國出口自產原料熟製禽肉。

中國自2004年開始,就一直積極爭取這項目標,但美國先前僅允許中國使用美國或特定國家的禽肉原料加工後輸往美國。這次禽肉進口談判,正值非洲豬瘟疫情嚴重削減中國這一全球頭號豬肉消費國的生豬存欄數量。中國為填補蛋白質來源的市場空缺,正迅速增加肉類進口量,而雞肉正是替代豬肉最便宜的一種肉類。

此外,如果中國同樣解除對美國禽肉產品進口的限制,也將是美國農民和肉類加工企業的一大勝利。

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

不尾號限行!純電動車在北京受禮遇

4月2日,北京市政府發佈消息稱,自2015年4月11日至2016年4月10日,北京將繼續實施工作日機動車尾號限行措施,限行時間為7時至20時,範圍為五環路以內道路(不含五環路)。   而今年的限行措施特別提到,純電動小客車將不受尾號限行影響,具體執行時間由交管局公佈。其中,純電動小客車不受工作日高峰時段區域限行交通管理措施限制,具體執行時間由市公安交通管理部門另行公佈。   今年3月,國家交通運輸部發佈的《關於加快推進新能源汽車在交通運輸行業推廣應用的實施意見》便對推行電動車在內的新能源車有政策傾斜,提出要對新能源汽車不限行、不限購,對新能源計程車的運營權指標適當放寬。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

GitLab Runner部署(kubernetes環境)

歡迎訪問我的GitHub

https://github.com/zq2599/blog_demos
內容:所有原創文章分類匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

關於GitLab CI

如下圖所示,開發者將代碼提交到GitLab后,可以觸發CI腳本在GitLab Runner上執行,通過編寫CI腳本我們可以完成很多使用的功能:編譯、構建、生成docker鏡像、推送到私有倉庫等:

本次實戰內容

今天咱們會一起完成以下操作:

  1. 部署minio,pipeline腳本中的cache功能由minio來實現;
  2. 配置和部署GitLab Runner;
  3. 編寫和運行pipeline腳本;

環境和版本信息

本次實戰涉及到多個服務,下面給出它們的版本信息供您參考:

  1. GitLab:Community Edition 13.0.6
  2. GilLab Runner:13.1.0
  3. kubernetes:1.15.3
  4. Harbor:1.1.3
  5. Minio:2020-06-18T02:23:35Z
  6. Helm:2.16.1

需要提前準備好的服務

以下服務需要您在實戰前提前準備好:

  1. 部署好GitLab,參考《群暉DS218+部署GitLab》
  2. 部署好Harbor,參考《群暉DS218+部署Harbor(1.10.3)》
  3. 部署好Helm,參考《部署和體驗Helm(2.16.1版本)》

準備完畢后開始實戰;

部署minio

minio作為一個獨立的服務部署,我將用docker部署在服務器:192.168.50.43

  1. 在宿主機準備兩個目錄,分別存儲minio的配置和文件,執行以下命令:
mkdir -p /var/services/homes/zq2599/minio/gitlab_runner \
&& chmod -R 777 /var/services/homes/zq2599/minio/gitlab_runner \
&& mkdir -p /var/services/homes/zq2599/minio/config \
&& chmod -R 777 /var/services/homes/zq2599/minio/config
  1. 執行docker命令創建minio服務,指定服務端口是9000,並且指定了access key(最短三位)和secret key(最短八位):
sudo docker run -p 9000:9000 --name minio \
-d --restart=always \
-e "MINIO_ACCESS_KEY=access" \
-e "MINIO_SECRET_KEY=secret123456" \
-v /var/services/homes/zq2599/minio/gitlab_runner:/gitlab_runner \
-v /var/services/homes/zq2599/minio/config:/root/.minio \
minio/minio server /gitlab_runner
  1. 瀏覽器訪問,輸入access key和secret key后登錄成功:
  2. 如下圖,點擊紅框中的圖標,創建一個bucket,名為runner
  3. 至此,minio已備好,接下來在kubernetes環境部署GitLab Runner;

GitLab Runner的類型

從使用者的維度來看,GitLab Runner的類型分為sharedspecific兩種:

  1. 如果您想創建的GitLab Runner給所有GitLab倉庫使用,就要創建shared類型;
  2. 如果您的GitLab Runner只用於給某個固定的Gitlab倉庫,就要創建specific類型;

今天的實戰,我們創建的是specific類型,即先有GitLab代碼倉庫,然後創建該倉庫專用的runner,所以請您提前準備好GitLab倉庫;

準備GitLab配置信息(specific)

在部署GitLab Runner之前,要準備兩個關鍵的配置信息,以便GitLab Runner啟動后可以順利連接上GitLab:

  1. 瀏覽器訪問GitLab,打開用來做CI的代碼倉庫,點擊Settings -> CI/CD -> Runners -> Expand:
  2. 如下圖,紅框1中是gitlab url,紅框2中是registration token,記好這兩個參數,稍後會用到:

準備GitLab配置信息(shared)

本次實戰不會創建shared類型的runner,如果您要創建該類型runner,只需按照以下方法準備信息即可,創建出來的runner就是所有倉庫都能使用的了:

  1. 以管理員身份登錄GitLab;
  2. 按照下圖紅框的順序取得gitlab urlregistration token

部署RitLab Runner

  1. 請確保當前可以通過kubectl命令在kubernetes進行常規操作;
  2. 創建名為gitlab-runner的namespace:
kubectl create namespace gitlab-runner
  1. 創建一個secret,把minio的access key和secret key存進去,在後面配置cache的時候會用到:
kubectl create secret generic s3access \
--from-literal=accesskey="access" \
--from-literal=secretkey="secret123456" -n gitlab-runner
  1. 用helm部署GitLab Runner之前,先把chart的倉庫添加到helm的倉庫列表中:
helm repo add gitlab https://charts.gitlab.io
  1. 下載GitLab Runner的chart:
helm fetch gitlab/gitlab-runner
  1. 當前目錄會多出一個文件gitlab-runner-0.18.0.tgz,解壓:
tar -zxvf gitlab-runner-0.18.0.tgz
  1. 解壓后是名為gitlab-runner的文件夾,內容如下圖所示,接下來要修改裏面的三個文件:
  2. 打開values.yaml,裏面有四處需要修改:
  3. 第一處,找到已被註釋掉的gitlabUrl參數位置,添加gitlabUrl的配置,其值就是前面在GitLab網頁取得的gitlab url參數,如下圖紅框:
  4. 第二處,找到已被註釋掉的runnerRegistrationToken參數位置,添加runnerRegistrationToken的配置,其值就是前面在GitLab網頁取得的registration token參數,如下圖紅框:
  5. 找到rbac的配置,將createclusterWideAccess的值都改成true(創建RBAC、創建容器gitlab-bastion用於管理job的容器):
  6. 設置此GitLab Runner的tagk8s,在pipeline腳本中可以通過指定tag為k8s,這樣pipeline就會在這個Gitlab Runner上允許:
  7. 找到cache的配置,在修改之前,cache的配置如下圖,可見值為空內容的大括號,其餘信息全部被註釋了:
  8. 修改后的cache配置如下圖,紅框1中原先的大括號已去掉,紅框2中的是去掉了註釋符號,內容不變,紅框3中填寫的是minio的訪問地址,紅框4中的是去掉了註釋符號,內容不變:
  9. 上圖紅框4中的s3CacheInsecure參數等於false表示對minio的請求為http(如果是true就是https),但實際證明,當前版本的chart中該配置是無效的,等到運行時還是會以https協議訪問,解決此問題的方法是修改templates目錄下的_cache.tpl文件,打開此文件,找到下圖紅框中的內容:
  10. 將上圖紅框中的內容替換成下面紅框中的樣子,即刪除原先的if判斷和對應的end這兩行,直接給CACHE_S3_INSECURE賦值:
  11. 接下來要修改的是templates/configmap.yaml文件,在這裏面將宿主機的docker的sock映射給runner executor,這樣job中的docker命令就會發到宿主機的docker daemon上,由宿主機來執行,打開templates/configmap.yaml,找到下圖位置,我們要在紅框1和紅框2之間添加一段內容:
  12. 要在上圖紅框1和紅框2之間添加的內容如下:
cat >>/home/gitlab-runner/.gitlab-runner/config.toml <<EOF
            [[runners.kubernetes.volumes.host_path]]
              name = "docker"
              mount_path = "/var/run/docker.sock"
              read_only = true
              host_path = "/var/run/docker.sock"
    EOF
  1. 添加上述內容后,整體效果如下,紅框中就是新增內容:
  2. 修改完畢,回到values.yam所在目錄,執行以下命令即可創建GitLab Runner:
helm install \
--name-template gitlab-runner \
-f values.yaml . \
--namespace gitlab-runner
  1. 檢查pod是否正常:
  2. 看pod日誌也並未發現異常:
  3. 回到GitLab的runner頁面,可見新增一個runner:

    至此,整個GitLab CI環境已部署完畢,接下來簡單的驗證環境是否OK;

驗證

  1. 在GitLab倉庫中,增加名為.gitlab-ci.yml的文件,內容如下:
# 設置執行鏡像
image: busybox:latest

# 整個pipeline有兩個stage
stages:
- build
- test

# 定義全局緩存,緩存的key來自分支信息,緩存位置是vendor文件夾
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
  - vendor/

before_script:
  - echo "Before script section"

after_script:
  - echo "After script section"

build1:
  stage: build
    tags:
  - k8s
  script:
    - echo "將內容寫入緩存"
    - echo "build" > vendor/hello.txt

test1:
  stage: test
  script:
    - echo "從緩存讀取內容"
    - cat vendor/hello.txt
  1. 提交上述腳本到GitLab,如下圖,可見pipeline會被觸發,狀態為pending是因為正在等待runner創建executor pod:
  2. 稍後就會執行成功,點開看結果:
  3. 點開build1的圖標,可見此job的輸出信息:
  4. 點開test1的圖標,可見對應的控制台輸出,上一個job寫入的數據被成功讀取:

    至此,GitLab Runner已經成功在kubernetes環境部署和運行,接下來的文章,我們會一起實戰將SpringBoot應用構建成docker鏡像並推送到Harbor;

歡迎關注我的公眾號:程序員欣宸

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

學界首次發現空污與腦癌有關 慎防奈米級「超細懸浮微粒」

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

Dubbo想要個網關怎麼辦?試試整合Spring Cloud Gateway

一、背景

在微服務架構中 API網關 非常重要,網關作為全局流量入口並不單單是一個反向路由,更多的是把各個邊緣服務(Web層)的各種共性需求抽取出來放在一個公共的“服務”(網關)中實現,例如安全認證、權限控制、限流熔斷、監控、跨域處理、聚合API文檔等公共功能。

 

在以 Dubbo 框架體系來構建的微服務架構下想要增加API網關,如果不想自研開發的情況下在目前的開源社區中幾乎沒有找到支持dubbo協議的主流網關,但是 Spring Cloud 體系下卻有兩個非常熱門的開源API網關可以選擇;本文主要介紹如何通過 Nacos 整合 Spring Cloud GatewayDubbo 服務

 

二、傳統 dubbo 架構

dubbo屬於rpc調用,所以必須提供一個web層的服務作為http入口給客戶端調用,並在上面提供安全認證等基礎功能,而web層前面對接Nginx等反向代理用於統一入口和負載均衡。

web層一般是根據業務模塊來切分的,用於聚合某個業務模塊所依賴的各個service服務

PS:我們能否把上圖中的web層全部整合在一起成為一個API網關呢?(不建議這樣做)

因為這樣的web層並沒有實現 泛化調用 必須引入所有dubbo服務的api依賴,會使得網關變得非常不穩定,任何服務的接口變更都需要修改網關中的api依賴!

 

三、整合 Srping Cloud Gateway 網關

下面就開始聊聊直接拿熱門的 Srping Cloud Gateway 來作為dubbo架構體系的網關是否可行,首先該API網關是屬於 Spring Cloud 體系下的組件之一,要整合dubbo的話需要解決以下問題:

  1. 打通註冊中心:spring cloud gateway 需要通過註冊中心發現下游服務,而 dubbo 也需要通過註冊中心實現服務的註冊與發現,如果兩者的註冊中心不能打通的話就會變成雙註冊中心架構就非常複雜了!
  2. 協議轉換: gateway 使用http傳輸協議調用下游服務,而dubbo服務默認使用的是tcp傳輸協議

上面提到的第一個問題“打通註冊中心”其實已經不是問題了,目前dubbo支持 ZookeeperNacos 兩個註冊中心,而 Spring Cloud 自從把 @EnableEurekaClient 改為 @EnableDiscoveryClient 之後已經基本上支持所有主流的註冊中心了,本文將使用 Nacos 作為註冊中心打通兩者

 

3.1. 方式一

把傳統dubbo架構中的 Nginx 替換為 Spring Cloud Gateway ,並把 安全認證 等共性功能前移至網關處實現

由於web層服務本身提供的就是http接口,所以網關層無需作協議轉換,但是由於 安全認證 前移至網關了需要通過網絡隔離的手段防止被繞過網關直接請求後面的web層

 

3.2. 方式二

dubbo服務本身修改或添加 rest 傳輸協議的支持,這樣網關就可以通過http傳輸協議與dubbo服務通信了

rest傳輸協議:基於標準的Java REST API——JAX-RS 2.0(Java API for RESTful Web Services的簡寫)實現的REST調用支持

目前版本的dubbo已經支持dubbo、rest、rmi、hessian、http、webservice、thrift、redis等10種傳輸協議了,並且還支持同一個服務同時定義多種協議,例如配置 protocol = { “dubbo”, “rest” } 則該服務同時支持 dubborest 兩種傳輸協議

 

3.3. 總結

方式一 對比 方式二 多了一層web服務所以多了一次網絡調用開銷,但是優點是各自的職責明確單一,web層可以作為聚合層用於聚合多個service服務的結果經過融合加工一併返回給前端,所以這種架構下能大大減少服務的 循環依賴

 

四、代碼實踐

依賴環境

  • lombok
  • jdk 1.8
  • Nacos 1.3
  • Spring Boot 2.2.8.RELEASE
  • Spring Cloud Hoxton.SR5
  • Spring Cloud Alibaba 2.2.1.RELEASE

 

在根目錄的 pom.xml 中定義全局的依賴版本

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>8</java.version>

        <spring-boot-dependencies.version>2.2.8.RELEASE</spring-boot-dependencies.version>
        <spring-cloud-dependencies.version>Hoxton.SR5</spring-cloud-dependencies.version>
        <spring-cloud-alibaba-dependencies.version>2.2.1.RELEASE</spring-cloud-alibaba-dependencies.version>
        <jaxrs.version>3.12.1.Final</jaxrs.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba-dependencies.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

 

4.1. 創建dubbo-api工程

分別定義兩個api接口

DubboService 使用dubbo協議的服務

public interface DubboService {
    String test(String param);
}

RestService 使用rest協議的服務

public interface RestService {
    String test(String param);
}

 

4.2. 創建web-dubbo工程

使用 方式一 整合對接網關,這裏為了簡化在同一個服務下只使用邏輯分層定義controller層與service層,並沒有做服務拆分

4.2.1. 創建配置

定義 spring boot 配置

server:
  port: 8081

spring:
  application:
    name: zlt-web-dubbo
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      server-addr: 192.168.28.130:8848
      username: nacos
      password: nacos

server.port:配置應用服務器暴露的端口

spring.cloud.nacos:配置 spring cloud 的註冊中心相關參數,nacos 的配置需要改為自己環境所對應

定義 dubbo 配置

dubbo:
  scan:
    base-packages: org.zlt.service
  protocols:
    dubbo:
      name: dubbo
      port: -1
  registry:
    address: spring-cloud://localhost
  consumer:
    timeout: 5000
    check: false
    retries: 0
  cloud:
    subscribed-services:

dubbo.scan.base-packages:指定 Dubbo 服務實現類的掃描基準包

dubbo.protocols:服務暴露的協議配置,其中子屬性 name 為協議名稱,port 為協議端口( -1 表示自增端口,從 20880 開始)

dubbo.registry.address:Dubbo 服務註冊中心配置,其中子屬性 address 的值 “spring-cloud://localhost”,說明掛載到 Spring Cloud 註冊中心

 

4.2.2. 創建DubboService的實現類

通過 protocol = "dubbo" 指定使用 dubbo協議 定義服務

@Service(protocol = "dubbo")
public class DubboServiceImpl implements DubboService {
    @Override
    public String test(String param) {
        return "dubbo service: " + param;
    }
}

 

4.2.3. 創建Controller類

使用 Spring Boot@RestController 註解定義web服務

@RestController
public class WebController {
    @Autowired
    private DubboService dubboService;

    @GetMapping("/test/{p}")
    public String test(@PathVariable("p") String param) {
        return dubboService.test(param);
    }
}

 

4.3. 創建rest-dubbo工程

使用 方式二 整合對接網關,由於該服務是通過dubbo來創建rest服務,所以並不需要使用 Spring Boot 內置應用服務

4.3.1. 創建配置

定義 spring boot 配置

spring:
  application:
    name: zlt-rest-dubbo
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      server-addr: 192.168.28.130:8848
      username: nacos
      password: nacos

因為不使用 Spring Boot 內置的應用服務所以這裏並不需要指定 server.port

定義 dubbo 配置

dubbo:
  scan:
    base-packages: org.zlt.service
  protocols:
    dubbo:
      name: dubbo
      port: -1
    rest:
      name: rest
      port: 8080
      server: netty
  registry:
    address: spring-cloud://localhost
  consumer:
    timeout: 5000
    check: false
    retries: 0
  cloud:
    subscribed-services:

dubbo.protocols:配置兩種協議,其中rest協議定義 8080 端口並使用 netty 作為應用服務器

 

4.3.2. 創建RestService的實現類

通過 protocol = "rest" 指定使用 rest協議 定義服務

@Service(protocol = "rest")
@Path("/")
public class RestServiceImpl implements RestService {
    @Override
    @Path("test/{p}")
    @GET
    public String test(@PathParam("p") String param) {
        return "rest service: " + param;
    }
}

 

4.4. 創建Spring Cloud Gateway工程

定義 spring boot 配置

server:
  port: 9900

spring:
  application:
    name: sc-gateway
  main:
    allow-bean-definition-overriding: true
  cloud:
    nacos:
      server-addr: 192.168.28.130:8848
      username: nacos
      password: nacos

server.port:定義網關端口為 9090

定義網關配置

spring:
  cloud:
    gateway:
      discovery:
        locator:
          lowerCaseServiceId: true
          enabled: true
      routes:
        - id: web
          uri: lb://zlt-web-dubbo
          predicates:
            - Path=/api-web/**
          filters:
            - StripPrefix=1
        - id: rest
          uri: lb://zlt-rest-dubbo
          predicates:
            - Path=/api-rest/**
          filters:
            - StripPrefix=1

分別定義兩個路由策略:

  • 路徑 /api-web/ 為請求 web-dubbo 工程
  • 路徑 /api-rest/ 為請求 rest-dubbo 工程

 

4.5. 測試

分別啟動:Nacos、sc-gateway、web-dubbo、rest-dubbo 工程,通過網關的以下兩個接口分別測試兩種整合方式

  1. http://127.0.0.1:9900/api-web/test/abc :請求 web-dubbo 工程測試整合方式一
  2. http://127.0.0.1:9900/api-rest/test/abc :請求 rest-dubbo 工程測試整合方式二

 

五、demo下載

ide需要安裝 lombok 插件

https://github.com/zlt2000/dubboSpringCloud

 
掃碼關注有驚喜!

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

特斯拉推儲能電池系統 德國石墨陽極商 SGL 受惠

美國豪華電動車製造商特斯拉 (Tesla) 跨界家用電池,德國汽車碳纖維生產商西格里集團 (SGL Carbon SE) 在投資人期待該公司可望因此受惠的激勵下,股價創下近三個月以來最大單日漲幅。   SGL 主要是為日立、Panasonic 這些日本電子大廠供應石墨陽極材料,而日立、Panasonic 則會將電池元件賣給特斯拉。   Bankhaus Lampe 分析師 Marc Gabriel 說,SGL 是少數幾家能因電池需求增溫而受惠的德國業者。根據報導,SGL 發言人已確認,該公司的確是日立、Panasonic 的石墨陽極材料供應商。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

TCP協議粘包問題詳解

TCP協議粘包問題詳解

前言

  在本章節中,我們將探討TCP協議基於流式傳輸的最大一個問題,即粘包問題。本章主要介紹TCP粘包的原理與其三種解決粘包的方案。並且還會介紹為什麼UDP協議不會產生粘包。

 

基於TCP協議的socket實現遠程命令輸入

  我們準備做一個可以在Client端遠程執行Server端shell命令並拿到其執行結果的程序,而涉及到網絡通信就必然會出現socket模塊,關於如何抉擇傳輸層協議的選擇?我們選擇使用TCP協議,因為它是可靠傳輸協議且數據量支持比UDP協議要大。好了廢話不多說直接上代碼了。

 

  Server端代碼如下:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# ==== 基於TCP協議的socket實現遠程命令輸入之Server ====

import subprocess
from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(("0.0.0.0",6666))  # 放在遠程填入0.0.0.0,放在本地填入127.0.0.1
server.listen(5)

while 1:  # 鏈接循環
    conn,client_addr = server.accept()
    while 1:  # 通信循環
        try:  # 防止Windows平台下Client端異常關閉導致雙向鏈接崩塌Server端異常的情況發生
            cmd = conn.recv(1024)
            if not cmd:  # 防止類Unix平台下Client端異常關閉導致雙向鏈接崩塌Server端異常的情況發生
                break
            res = subprocess.Popen(cmd.decode("utf-8"),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE,)

            stdout_res = res.stdout.read()  # 正確結果
            stderr_res = res.stderr.read()  # 錯誤結果
            # subprocess模塊拿到的是bytes類型,所以直接發送即可

            cmd_res = stdout_res if stdout_res else stderr_res  # 因為兩個結果只有一個有信息,所以我們只拿到有結果的那個
            conn.send(cmd_res)

        except Exception:
            break

    conn.close()  # 由於client端鏈接異常,故關閉鏈接循環

 

  Client端代碼如下:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# ==== 基於TCP協議的socket實現遠程命令輸入之Client ====

from socket import *

client = socket(AF_INET,SOCK_STREAM)
client.connect(("xxx.xxx.xxx.xxx",6666))  # 填入Server端公網IP

while 1:
    cmd = input("請輸入命令>>>:").strip()
    if not cmd:
        continue
    if cmd == "quit":
        break
    client.send(cmd.encode("utf-8"))
    cmd_res = client.recv(1024)  # 本次接收1024字節數據
    print(cmd_res.decode("utf-8"))  # 如果Server端是Windows則用gbk解碼,類Unix用utf-8解碼

client.close()

 

  測試結果:

 

粘包問題及其原理

  上面的測試一切看起來都非常完美,但是是有一個BUG的。當我們如果讀取一條非常長的命令實際上是會出問題的,比如:

  這種現象被稱之為粘包,那麼為何會產生這樣的現象呢?

 

  這是由於recv()沒有一次性讀取完整個內核緩衝區的內容導致的。其實歸根結底還是怪TCP是字節流方式傳輸數據。

 

  我們來解析一下這種現象產生的原因:

 

  由於我們的recv()只是按照固定的1024去讀取數據,那麼一旦整體內核緩衝區中所存儲的整體數據大於1024,就會產生粘包現象。所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的。

 

  這裏我還畫了一幅圖,可以方便讀者理解:

 

  那麼我們可以通過不斷的增大recv()中的讀取範圍來解決這個問題嗎?就像對應上圖中的,一次性把快遞櫃包裹全取完,答案是不可以!你再大你也不可能大過內核緩衝區,這個東西都是有一個一定的閾值。一旦超出了這個閾值就會引發異常或者乾脆無效。那麼有什麼好的辦法呢?哈,下面會教給你一些解決辦法的。不過在此之前我們要先看一個TCP協議特有的Nagle算法。

 

Nagle算法與粘包

 

  基於TCP協議的socket通信有一個特點,即:一方的send()與另一方的recv()可以沒有任何關係,即:一方send()三次,另一方recv()一次就可以將數據全部取出來。

 

  TCP協議的發送方有一個特徵。他會進行組包,如果一次發送的數據量很小,比如第一次發送10個字節,第二次發生2個字節,第三次發生3個字節。他可能會將這15個字節湊到一塊發送出去,這是採用了Nagle算法來進行的,這麼做有一個弊端就是接收方想要將這個大的數據包按照發送方的發送次數精確無誤的接收拆分成10 2 3必須要有發送方提供的拆包機制才行。

 

  如下圖組所示

 

  發送方:

from socket import *
ip_port = ("127.0.0.1",12306)
buffer_size = 1024
back_log = 5

server = socket(AF_INET,SOCK_STREAM)
server.bind(ip_port)
server.listen(back_log)

conn,addr = server.accept()
conn.send("hello,".encode("utf-8"))  # 第一次發送是6Bytes的數據
conn.send("world,".encode("utf-8"))     # 第二次也是6Bytes的數據
conn.send("yunyaGG!!".encode("utf-8"))  # 第三次是9Bytes的數據

 

  接收方:

from socket import *
ip_port = ("127.0.0.1",12306)
buffer_size = 1024

client = socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)

data_1 = client.recv(buffer_size)  # 我們讀取數據時統一用設定的 buffer_size 來讀取
print("這是第一次的數據包:",data_1.decode("utf-8"))
data_2 = client.recv(buffer_size)
print("這是第二次的數據包:",data_2.decode("utf-8"))
data_3 = client.recv(buffer_size)
print("這是第三次的數據包:",data_3.decode("utf-8"))

 

  接收結果:

# ==== 執行結果 ====
"""
這是第一次的數據包: hello,
這是第二次的數據包: world,yunyaGG!!
這是第三次的數據包: 
"""

 

  和預想的有點不太一樣哈,居然把第二次和第三次組成了一個大的數據包發送過來了。這就是Nagle算法,這樣的組包策略很容易就會產生粘包。我不知道你是以什麼樣的方式發過來的,所以我recv()就只能按照自己設定的方式去接收。

 

  現在思考一下粘包的思路,我們的發送方需要將切分解包的規則告訴給接收方。

  我們嘗試改一下每一次的buffer_size接收大小:

 

  接收方:

from socket import *
ip_port = ("127.0.0.1",12306)
buffer_size = 1024

client = socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)

data_1 = client.recv(6)  # 我們手動的按照對方發送時的規則來進行拆包
print("這是第一次的數據包:",data_1.decode("utf-8"))
data_2 = client.recv(6)
print("這是第二次的數據包:",data_2.decode("utf-8"))
data_3 = client.recv(9)
print("這是第三次的數據包:",data_3.decode("utf-8"))

 

  接收結果:

# ==== 執行結果 ====
"""
這是第一次的數據包: hello,
這是第二次的數據包: world,
這是第三次的數據包: yunyaGG!!
"""

 

  粘包被我們手動的計算字節數來精確的分割數據接受量的大小給解決了,但是這樣做是不現實的..我們不可能知道對方發送的數據到底是怎麼樣的,更不用說手動計算。所以有沒有更好的解決方案呢?

 

解決方案1:預先發送消息長度

  好了,其實上面關於解決粘包的思路已經出來了。我們需要做的就是讓接收方知道本次發送內容的大小,接收方才能夠精確的將所有數據全部提取出來不產生遺漏。其實實現方式很簡單,可以嘗試以下思路:

 

  1.發送方發送一個此次數據固定的長度

  2.接收方接收到該數據長度並且回應

  3.發送方收到回應並且發送真正的數據

  4.接收方不斷的用默認的buffer_size值接收新的數據並存儲起來直到超出整個數據的長度,代表此處數據全部接收完畢

 

  Server端:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# ==== 基於TCP協議的socket實現遠程命令輸入之Server ====

import subprocess
from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(("0.0.0.0", 6666))  # 放在遠程填入0.0.0.0 放在本地測試填入127.0.0.1
server.listen(5)

while 1:  # 鏈接循環
    conn, client_addr = server.accept()
    while 1:  # 通信循環
        try:  # 防止Windows平台下Client端異常關閉導致雙向鏈接崩塌Server端異常的情況發生
            cmd = conn.recv(1024)
            if not cmd:  # 防止類Unix平台下Client端異常關閉導致雙向鏈接崩塌Server端異常的情況發生
                break
            res = subprocess.Popen(cmd.decode("utf-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE, )

            stdout_res = res.stdout.read()  # 正確結果
            stderr_res = res.stderr.read()  # 錯誤結果
            # subprocess模塊拿到的是bytes類型,所以直接發送即可

            cmd_res = stdout_res if stdout_res else stderr_res  # 因為兩個結果只有一個有信息,所以我們只拿到有結果的那個
            msg_length = len(cmd_res)  # 本次數據的長度
            conn.send(str(msg_length).encode("utf-8"))  # 先將要發的整體內容長度發送過去
            if conn.recv(1024) == b"ready":  # 如果接收方回應了ready則開始發送真正的數據體
                conn.send(cmd_res)

        except Exception:
            break

    conn.close()  # 由於client端鏈接異常,故關閉鏈接循環

 

  Client端:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# ==== 基於TCP協議的socket實現遠程命令輸入之Client ====

from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(("xxx.xxx.xxx.xxx", 6666))  # 填入Server端公網IP

while 1:
    cmd = input("請輸入命令>>>:").strip()
    if not cmd:
        continue
    if cmd == "quit":
        break
    client.send(cmd.encode("utf-8"))

    msg_length = int(client.recv(1024).decode("utf-8"))  # 接收到此次發送內容的整體長度
    recv_length = 0  # 代表已接收的內容長度
    cmd_res = b""

    client.send(b"ready")  # 發送給Server端,代表自己已經接收到此次內容長度,可以發送真正的數據啦

    while recv_length < msg_length:
        cmd_res += client.recv(1024)  # 本次接收1024字節數據,可能是一小節數據
        recv_length += len(cmd_res)  # 添加上本次讀取的長度,當全部讀取完后應該 recv_length == msg_length

    else:
        print(cmd_res.decode("utf-8"))  # 如果Server端是Windows則用gbk解碼,類Unix用utf-8解碼

client.close()

 

  結果如下:

 

解決方案2:json+struct方案

  其實上面的解決方案還是有一些弊端,因為Server端是發送了2次send(),第1次發送數據整體長度,第2次發送數據內容主體,這樣其實是不太好的(Server端可能同時處理多個鏈接,所以send()次數越少越好),而且如果Server端傳的是一個文件的話那麼局限性就太強了。因為我們只能將整體的消息長度發送過去而諸如文件名,文件大小之內的信息就發送不過去。

  所以我們需要一個更加完美的解決方案,即Server端發送一次send()就將本次的數據整體長度發送過去(還可以包括文件姓名,文件大小等信息。)

 

  struct模塊使用介紹

 

  struct模塊可以將其某一種數據格式序列化為固定長度的Bytes類型,其中最重要的兩個方法就是pack()unpack()

 

  pack(fmt,*args): 根據格式將其轉換為Bytes類型

  unpack(fmt,string):根據格式將Bytes類型數據反解為其原本的形式

 

格式 C語言類型 Python類型 字節數大小
x 填充字節 沒有值  
c char 字節長度為1 1
b signed char 整數 1
B unsigned char 整數 1
? _Bool bool 1
h short 整數 2
H unsigned short 整數 2
i int 整數 4
I unsigned int 整數 4
l long 整數 4
L unsigned long 整數 4
q long long 整數 8
Q unsigned long long 整數 8
n ssize_t 整數  
N size_t 整數  
f float 浮點數 4
d double 浮點數 8
s char[] 字節  
p char[] 字節  
P void * 整數  

 

  使用演示:

>>> import struct
>>> b1 = struct.pack("i",12)  # 嘗試將 int類型的12進行序列化,得到一個4字節的對象
>>> b1
b'\x0c\x00\x00\x00'
>>> struct.unpack("i",b1)  # 嘗試將12的序列化對象字節進行反解,得出元組,第1位就是需要的數據。
(12,)
>>>

 

  好了,了解到這裏我們就可以開始進行改寫了。

  Server端代碼如下:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# ==== 基於TCP協議的socket實現遠程命令輸入之Server ====

import json
import struct
import subprocess
from socket import *

server = socket(AF_INET, SOCK_STREAM)
server.bind(("0.0.0.0", 6666))  # 放在遠程填入0.0.0.0 放在本地測試填入127.0.0.1
server.listen(5)

while 1:  # 鏈接循環
    conn, client_addr = server.accept()
    while 1:  # 通信循環
        try:  # 防止Windows平台下Client端異常關閉導致雙向鏈接崩塌Server端異常的情況發生
            cmd = conn.recv(1024)
            if not cmd:  # 防止類Unix平台下Client端異常關閉導致雙向鏈接崩塌Server端異常的情況發生
                break
            res = subprocess.Popen(cmd.decode("utf-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE, )

            stdout_res = res.stdout.read()  # 正確結果
            stderr_res = res.stderr.read()  # 錯誤結果
            # subprocess模塊拿到的是bytes類型,所以直接發送即可

            cmd_res = stdout_res if stdout_res else stderr_res  # 因為兩個結果只有一個有信息,所以我們只拿到有結果的那個

            # 解決粘包:構建字典,包含數據主體長度,這個就相當於其頭部信息
            head_msg = {
                "msg_length": len(cmd_res), # 包含數據主體部分的長度
                # 如果是文件,還可以添加file_name,file_size等屬性。
            }

            # 序列化成json格式,並且統計其頭部的長度
            head_data = json.dumps(head_msg).encode("utf-8")
            head_length = struct.pack("i", len(head_data))  # 得到4字節的頭部信息,裡面包含頭部的長度

            # 發送頭部長度信息,頭部數據,與真實數據部分
            conn.send(head_length + head_data + cmd_res)

        except Exception:
            break

    conn.close()  # 由於client端鏈接異常,故關閉鏈接循環

 

  Client端代碼如下:

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

# ==== 基於TCP協議的socket實現遠程命令輸入之Client ====

import json
import struct
from socket import *

client = socket(AF_INET, SOCK_STREAM)
client.connect(("xxx.xxx.xxx.xxx", 6666))  # 填入Server端公網IP

while 1:
    cmd = input("請輸入命令>>>:").strip()
    if not cmd:
        continue
    if cmd == "quit":
        break
    client.send(cmd.encode("utf-8"))  # 發送終端命令

    # 解決粘包
    head_length = struct.unpack("i", client.recv(4))[0]  # 接收到頭部的長度信息
    head_data = json.loads(client.recv(head_length))  # 接收到真實的頭部信息

    msg_length = head_data["msg_length"]  # 獲取到數據主體的長度信息
    recv_length = 0  # 代表已接收的內容長度
    cmd_res = b""

    # 開始獲取真正的數據主體信息
    while recv_length < msg_length:
        cmd_res += client.recv(1024)  # 本次接收1024字節數據,可能是一小節數據
        recv_length += len(cmd_res)  # 添加上本次讀取的長度,當全部讀取完后應該 recv_length == msg_length

    else:
        print(cmd_res.decode("utf-8"))  # 如果Server端是Windows則用gbk解碼,類Unix用utf-8解碼


client.close()

 

  思想如下:

    1.Server端構建自身的數據頭部分,其中包含數據體整體長度,如果傳輸的是文件的話還可以包含文件名,文件大小等信息

    2.將數據頭部分json序列化后再轉換為Bytes類型

    3.使用struct.pack()模塊獲取數據頭的長度,得到一個長度為4的Bytes類型

    4.Server端將 數據頭長度 + 數據頭部分 + 數據體部分 全部發送給Client端

    5. Client端recv()接收值改為4,拿到數據頭長度Bytes類型

    6. Client端使用struct.unpack(數據頭長度Bytes類型)模塊反解出數據頭真實的長度

    7. Client端使用recv()接收值為數據頭真實的長度拿到真正的數據頭

    8. 通過json反序列化出真正的數據頭,在到其中取出數據體的長度

    9. 開始while循環不斷的讀取真實的數據體數據

 

 

解決方案3:iter()與偏函數(失敗案例)

 

  上面那麼做看似完美但還是美中不足。因為內存緩衝區本來就是只能取一次值,和迭代器很像,只能迭代一次便不能繼續迭代了。基於這一點我們來做一個終極優化:

  還記得iter()方法嗎?iter()方法除開創建迭代器外實際上還有一個參數:

 

def iter(source, sentinel=None):  # known special case of iter
    """
    iter(iterable) -> iterator
    iter(callable, sentinel) -> iterator

    Get an iterator from an object.  In the first form, the argument must
    supply its own iterator, or be a sequence.
    In the second form, the callable is called until it returns the sentinel.
    """
    pass

 

  我們來試試這個參數做什麼用的。

li = [1, 2, 3, 4]

def my_iter():
    return li.pop()

res = iter(my_iter, 2)  # 代表這個迭代器沒__next__一下就會執行my_iter函數,並且該函數返回值如果是2則終止迭代
print(res.__next__())  # 4
print(res.__next__())  # 3
print(res.__next__())  # StopIteration

 

  第二個參數看來可以設置迭代的終點。

 

  那麼偏函數是什麼呢?偏函數可以設定一個固定的參數給第一個位置的值

  效果如下:

from functools import partial  # 導入偏函數

def add(x, y):
    return x + y

func = partial(add, 1)  # 設置辨寒暑綁定的第一個參數的值
print(func(1))  # 2
print(func(5))  # 6

 

  現在我們仔細回想,當緩衝區的消息接收完畢後為空的狀態是會變成 b""的形式。那麼這個時候我們可以使用iter()方法設置為不斷的取出緩存中的值直到出現b"",而偏函數可以對recv()函數進行設置讓它始終取一個值,最後通過join來拼接出取出的所有值即可。

  可以使用 "".join(iter(partial(tcp_clien.recv,back_log)),b"")

 

  我們嘗試用函數來查看一下效果:

from functools import partial  # 導入偏函數

li = [b"","1","2","3","4","5"]  # 模擬內核緩衝區

def test(buffer_size):
    if buffer_size:  # 模擬recv的數據大小
        return li.pop()
    print("buffer_size必須為一個int類型的值")

res = "".join(iter(partial(test,1024),b""))
print(res)  # 54321

# join()方法會不斷的調用iter()下的__next__,每調用一次就執行一次偏函數。知道出現b""停止

 

  最後我們發現,這樣的做法是會產生recv()阻塞的,總體來說還是不能夠成功。因為join()方法會不斷的執行,即使內核緩衝區的數據被recv()讀完了也不會終止迭代而是繼續阻塞下次的recv(),故這種方式宣告失敗。(還是iter()的第二個參數導致的,或許讀取完后內核緩衝區中的數據並不是b""

 

  測試的Server端代碼如下:

from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024

tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)

while True:
    conn,addr=tcp_server.accept()
    print('新的Client鏈接',addr)
    while True:
        #
        try:
            cmd=conn.recv(buffer_size)
            if not cmd:break
            print('收到Client的命令',cmd)

            #執行命令,得到命令的運行結果cmd_res
            res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
                                 stderr=subprocess.PIPE,
                                 stdout=subprocess.PIPE,
                                 stdin=subprocess.PIPE)
            err=res.stderr.read()
            if err:
                cmd_res=err
            else:
                cmd_res=res.stdout.read()

            #
            if not cmd_res:
                cmd_res='執行成功'.encode('gbk')

            length=len(cmd_res)

            data_length=struct.pack('i',length)
            conn.send(data_length)
            conn.send(cmd_res)
        except Exception as e:
            print(e)
            break

 

  測試的Client代碼如下:

from socket import *
import struct
from functools import partial   #偏函數
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024

tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)

while True:
    cmd=input('>>: ').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))


    #解決粘包
    length_data=tcp_client.recv(4)
    length=struct.unpack('i',length_data)[0]
   
  #第一種方法
    recv_size=0
    recv_msg=b''
    while recv_size < length:
        #為何recv里是buffer_size,不是length,因為length如果為24G,系統內存沒有那麼大
        #所以每次buffer_size,當recv_size < length時,循環接收,直到recv_size =length,退出循環
        recv_msg += tcp_client.recv(buffer_size)
        recv_size=len(recv_msg) #1024

    #第二種方法 失敗版本,會引發recv()的阻塞,而不會終止迭代。因為join()方法會不斷的調用其iter()方法產生的迭代器,也就是調用其__next__方法,所以第二次沒消息的recv()會阻塞住。
    #recv_msg=''.join(iter(partial(tcp_client.recv, buffer_size), b''))
    print('命令的執行結果是 ',recv_msg.decode('gbk'))
tcp_client.close()

 

UDP協議為何不會產生粘包

 

  UDP協議是面向消息的協議,每一次的sendto()recvfrom()必須一一對應,否則就會收不到消息。

 

  UDP是面向消息的協議,每個UDP段都是一條消息,每sendto()一次就是發送一次消息,而不管接收方有沒有收到消息發送方只管自己的發送任務,這也是UDP被稱為不可靠傳輸協議的由來。接收端的套接字緩衝區採用了鏈式的結構來記錄每一個到達的UDP包,在每一個UDP包中都有了消息頭,包括端口,消息源等等..於是UDP就能夠去區分出一個明確的消息定義,即面向消息的通信是有消息邊界的,所以UDP的傳輸叫做數據報的形式。

 

  並且每一次recvform()buffer_size最大值如果不夠獲取完全部的內核緩衝區里的數據的話,那麼只會收夠指定的最大字節數量(即buffer_size的設定值),剩餘的就不要了。所以UDP不會存在粘包,多麼乾脆利落…

 

  我們還是用一個快遞員的那個圖來進行演示:

  還有一點需要注意一下。使用UDP協議進行通信的時候不管首先啟動哪一方都不會報錯,因為它只管發,不管有沒有人接收。

  所以,這也是我稱UDP協議比較隨便的原因。

 

  那麼隨便有沒有什麼好處呢?有的,速度快。不用建立雙向鏈接通道,但是其代價就是數據可靠性與安全性的問題,效率和安全從來都是相對的,這個也只能在從中做取捨。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

在美銷售50萬輛電動車 通用汽車做不到

據英國路透社5月8日報導,通用汽車公司日前在年度報告中表示,由於消費者對電動汽車的需求暫時還達不到預期目標,其最初設定的2017年前美國電動汽車50萬銷量目標或將落空。   通用公司資料顯示,2013年美國電動汽車保有量為153,034輛,2014年上升到180,834輛。價格高、續航里程不如人意、充電基礎設施不完善,這使得電動汽車並不太受消費者的青睞。另外,油價的下跌使得消費者們重新關注大型車輛,包括全尺寸掀背車和SUV。   而通用目前正在研發電動汽車雪佛蘭Bolt,續航里程可達200英里,預計2016年投產。聯邦稅返還後,售價約為3萬美元。通用還將在今年秋天推出改進設計的沃藍達增程型電動汽車,與當前版本相比,續航里程增加50英里,售價降低約1,200美元。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

加速電動車佈局!首爾3年內增設10萬個充電站

為了加速電動車(electric vehicles)普及率,南韓政府宣佈將主導建設名為「EV-Line」的行動充電站,在首都首爾地區擴增10萬個充電設施;此計畫預計在2018年完工,由當地業者Power Cube和KT南韓電信承包建設業務。充電站將包括停車場、公園、住宅區等各式建築物內。   Power Cube董事表示,首爾當地有8成民眾都住在公寓,而非獨棟房屋,因此家用車都停在公用停車廠。每一個「EV-Line」充電站每小時約可充3.3 KW電量,要充飽整台車約需耗費6至8小時。有些充電站則可提供每小時8KW電量,雖可以縮短一半以上的充電時間,但充電站內的安全考慮成為最大考驗。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案