分類
發燒車訊

005.OpenShift訪問控制-權限-角色

一 Kubetcl namespace

1.1 namespace描述


Kubernetes namespace提供了將一組相關資源組合在一起的機制。在Red Hat OpenShift容器平台中,project是一個帶有附加註釋的Kubernetes namespace。

namespace提供以下特性:

  1. 命名資源,以避免基本的命名衝突;
  2. 將管理權限授予受信任的用戶;
  3. 限制用戶資源消耗的能力;
  4. 用戶和用戶組隔離。

1.2 project


project提供了一種機制,通過這種機制可以管理普通用戶對資源的訪問。project允許一組用戶獨立於其他組組織和管理其內容,必須允許用戶訪問項目。如果允許創建項目,用戶將自動訪問自己的項目。

項目可以有單獨的name、display name和description。

name是項目的唯一標識符,在使用CLI工具或API時都是基於name,name的最大長度為63個字符。

display name是項目在web控制台中显示的方式(默認為name)。

description是項目的更詳細描述,並且在web控制台中也可見。

以下組件適用於項目:

  • Object:pod、service、rc等;
  • Policies:決定用戶可以或不能對對象執行哪些操作的規則;
  • Constraints:可以限制的每種對象的配額。

1.3 cluster管理


集群管理員可以創建項目並將項目的管理權限委託給任何用戶。在OpenShift容器平台中,項目用於對相關對象進行分組和隔離。

管理員可以讓用戶訪問某些項目,允許他們創建自己的項目,並在單個項目中賦予他們管理權限。

管理員可以將角色應用於允許或限制其創建項目能力的用戶和組,同時可以在用戶初始登錄之前分配角色。

限制項目創建:從通過身份驗證的用戶和組中刪除self-provisioning集群角色,將拒絕任何新項目的權限。

[root@master ~]$ oc adm policy remove-cluster-role-from-group \

self-provisioner \

system:authenticated \

system:authenticated:oauth

授予項目創建:項目創建授予具有self-供應者角色和self-provisione集群角色綁定的用戶。默認情況下,所有經過身份驗證的用戶都可以使用這些角色。

[root@master ~]$ oc adm policy add-cluster-role-to-group \

self-provisioner \

system:authenticated \

system:authenticated:oauth

1.4 創建project


如果項目創建權限被授予用戶,則可以使用oc命令創建project。

[root@master ~]$ oc new-project demoproject \

–description=”Demonstrate project creation” \

–display-name=”demo_project”

二 OpenShift角色

2.1 角色概述


role具有不同級別的訪問和策略,包括集群和本地策略。user和group可以同時與多個role關聯。運行oc description命令查看角色及其綁定的詳細信息。

在集群策略中具有cluster-admin缺省角色的用戶可以查看集群策略和所有本地策略。在給定的本地策略中具有admin缺省角色的用戶可以基於per-project查看策略。

可通過以下命令查看當前的集群綁定集,其中显示綁定到不同角色的用戶和組。

[root@demo ~]# oc describe clusterPolicyBindings :default

2.2 查看本地policy


儘管本地角色列表及其關聯的規則集在本地策略中是不可查看的,但是所有缺省角色仍然適用,並且可以添加到用戶或組中,cluster-admin缺省角色除外。但是,本地綁定是可見的。

可通過以下命令查看當前的本地綁定,其中显示綁定到不同角色的用戶和組。

[root@demo ~]# oc describe policyBindings :default

提示:默認情況下,在本地策略中,只會列出admin角色的綁定。但是,如果將其他默認角色添加到本地策略中的用戶和組,也會列出它們。

2.3 管理role綁定


向用戶或組添加或綁定角色,從而實現向用戶或組提供角色授予的相關訪問權限。可以使用oc adm policy命令在用戶和組之間添加和刪除角色。

當使用以下操作管理本地策略的用戶和組角色時,可以使用-n選項指定項目。如果沒有指定,則使用當前項目。

常見管理本地策略操作:





























命令 描述
oc adm policy who-can verb resource 設置哪些用戶可以對資源執行操作
oc adm policy add-role-to-user role username 將指定角色綁定到指定用戶
oc adm policy remove-role-from-user role username 從指定用戶中移除給定角色
oc adm policy remove-user username 刪除指定的用戶及其所有角色
oc adm policy add-role-to-group role groupname 將指定的角色綁定到指定的組
oc adm policy remove-role-fromgroup role groupname 從指定組中移除給定角色
oc adm policy remove-group groupname 刪除指定的組及其所有角色


還可以使用如下所示的的操作管理cluster policy的role binding,這類命令不需要-n選項,因為cluster policy不在namespace級別上操作。

常見管理cluster policy操作:




















命令 描述
oc adm policy add-cluster-role-to-user role username 將集群中所有項目的指定角色綁定到指定用戶
oc adm policy remove-cluster-role-from-user role username 為集群中的所有項目從指定用戶中刪除指定角色
oc adm policy add-cluster-role-togroup role groupname 為集群中的所有項目將指定的角色綁定到指定的組
oc adm policy remove-cluster-role-from-group role groupname 從集群中所有項目的指定組中移除給定角色


提示:oc policy命令應用於當前項目,而oc adm policy命令應用於集群範圍的操作。

示例:在example項目中為developer用戶提供admin角色。

[root@demo ~]# oc adm policy add-role-to-user admin developer -n example

[root@demo ~]# oc describe policybindings :default -n example #檢查綁定

三 安全上下文約束(SCCS)

3.1 SCCS概述


OpenShift提供安全上下文約束(SCCS),它控制pod可以執行的操作和它可以訪問的資源。默認情況下,任何容器的執行都只授予受限制的SCC定義的功能。

SCCS相關命令:

  1 [user@demo ~]$ oc get scc			                        #列出可用的SCC
  2 [user@demo ~]$ oc describe scc scc_name		                #現實特定SCC詳細信息
  3 [user@demo ~]$ oc adm policy add-scc-to-user scc_name user_name
  4 [user@demo ~]$ oc adm policy add-scc-to-group scc_name group_name	#要授予用戶或組特定的SCC
  5 [user@demo ~]$ oc adm policy remove-scc-from-user scc_name user_name
  6 [user@demo ~]$ oc adm policy remove-scc-from-group scc_name group_name	#從特定的SCC中刪除用戶或組


四 服務賬戶

4.1 服務賬戶


service account提供了一種靈活的方法來控制API訪問,而無需共享常規用戶的憑據。如果應用程序需要訪問受限制的SCC未授予的功能,可創建一個新的、特定的service account並將其添加到適當的SCC中。

例如,在缺省情況下,OpenShift不支持部署需要提升特權的應用程序。若有此需求,可創建一個service account,修改dc,然後添加service account至SCC。

示例:將anyuid配置為在容器中作為root用戶運行。

[user@demo ~]$ oc create serviceaccount useroot #創建一個名為useroot的新服務帳戶

[user@demo ~]$ oc patch dc/demo-app \

–patch ‘{“spec”:{“template”:{“spec”:{“serviceAccountName”: “useroot”}}}}’ #修改應用程序的DC

[user@demo ~]$ oc adm policy add-scc-to-user anyuid -z useroot #將useroot服務帳戶添加到anyuid SCC中,作為容器中的根用戶運行

4.2 Web管理user成員


OCP平台的默認配置是,在用戶首次登錄成功時,自動創建該用戶對象。

要管理允許訪問項目的用戶,請以項目管理員或集群管理員的身份登錄到web控制台,並選擇要管理的項目。在左側窗格中,單擊Resources——>membership進入項目member頁面。

在Users列中,在突出显示的文本框中輸入用戶名。在“添加另一個角色”列中,從用戶所在行的列表中選擇一個角色,然後單擊“添加”。

4.3 Cli管理user成員


CLI中如果自動創建對象功能被關閉,集群管理員可通過如下方式創建新用戶:

[root@master ~]$ oc create user demo-user

同時還需要在身份認證軟件中創建用戶,如為HTPasswdIdentityProvider才用戶命令如下:

[root@master ~]$ htpasswd /etc/origin/openshift-passwd demo-user

要向用戶添加項目角色,首先使用oc project命令輸入項目,然後使用oc policy add-role-to-user命令:

[root@master ~]$ oc policy add-role-to-user edit demo-user

要從用戶中刪除項目角色,使用oc policy remove-role-from-user命令:

[root@master ~]$ oc policy remove-role-from-user edit demo-user

並不是所有OpenShift角色都由項目限定範圍。要分配這些規則,請使用oc adm policy command命令。

[root@master ~]$ oc adm policy add-cluster-role-to-user cluster-admin admin

4.4 身份驗證和授權


身份驗證層標識與對OpenShift容器平台API的請求相關聯的用戶,然後授權層使用關於請求用戶的身份信息來確定是否應該允許該請求。

  • user和group


OCP容器平台中的用戶是一個可以向OpenShift API發出請求的實體。通常,這表示與OpenShift交互的develop或administrator的帳戶。

可以將用戶分配給一個或多個組,每個組表示一組特定的角色(或權限)。當需要通過管理授權策略給多個客戶授權時候,group會比較合適。例如允許訪問項目中的對象,而不是單獨授予用戶。

  • Authentication Tokens


API調用必須使用訪問令牌或X.509證書進行身份驗證,會話token表示用戶,並且是短期的,默認情況下在24小時內到期。

可以通過運行oc whoami命令來驗證經過身份驗證的用戶。

[root@master ~]$ oc login -u demo-user

[root@master ~]$ oc whoami

demo-user

4.5 身份驗證類型


本環境中,身份驗證由HTPasswdIdentityProvider模塊提供,該模塊根據使用htpasswd命令生成的文件驗證用戶名和密碼。

OpenShift容器平台支持的其他認證類型包括:

  • Basic Authentication (Remote)


一種通用的後端集成機制,允許用戶使用針對遠程標識提供者驗證的憑據登錄到OpenShift容器平台。用戶將他們的用戶名和密碼發送到OpenShift容器平台,OpenShift平台通過到服務器的請求驗證這些憑據,並將憑據作為基本的Auth頭傳遞。這要求用戶在登錄過程中向OpenShift容器平台輸入他們的憑據。

  • Request Header Authentication


用戶使用請求頭值(如X-RemoteUser)登錄到OpenShift容器平台。它通常與身份驗證代理結合使用,身份驗證代理對用戶進行身份驗證,然後通過請求頭值為OpenShift容器平台提供用戶標識。

  • Keystone Authentication


Keystone是一個OpenStack項目,提供標識、令牌、目錄和策略服務。OpenShift容器平台與Keystone集成,通過配置OpenStack Keystone v3服務器將用戶存儲在內部數據庫中,從而支持共享身份驗證。這種配置允許用戶使用Keystone憑證登錄OpenShift容器平台。

  • LDAP Authentication


用戶使用他們的LDAP憑證登錄到OpenShift容器平台。在身份驗證期間,LDAP目錄將搜索與提供的用戶名匹配的條目。如果找到匹配項,則嘗試使用條目的專有名稱(DN)和提供的密碼進行簡單綁定。

  • GitHub Authentication


GitHub使用OAuth,它允許與OpenShift容器平台集成使用OAuth身份驗證來促進令牌交換流。這允許用戶使用他們的GitHub憑證登錄到OpenShift容器平台。為了防止使用GitHub用戶id的未授權用戶登錄到OpenShift容器平台集群,可以將訪問權限限制在特定的GitHub組織中。

五 管理項目及賬戶

5.1 前置準備


準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

5.2 本練習準備


[student@workstation ~]$ lab secure-resources setup

5.3 創建htpasswd賬戶

  1 [kiosk@foundation0 ~]$ ssh root@master
  2 [root@master ~]# htpasswd -b /etc/origin/master/htpasswd user1 redhat
  3 [root@master ~]# htpasswd -b /etc/origin/master/htpasswd user2 redhat
  4 #添加基於htpasswd形式的user1和user2,密碼都為redhat。


5.4 設置策略

  1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用管理員登錄
  2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
  3 self-provisioner system:authenticated:oauth
  4 #刪除所有賦予普通創建項目的功能,該命令可參考本環境如下目錄中的命令。
  5 [student@workstation ~]$ cat /home/student/DO280/labs/secure-resources/configure-policy.sh
  6 #!/bin/bash
  7 oc adm policy remove-cluster-role-from-group \
  8     self-provisioner system:authenticated system:authenticated:oauth


5.5 驗證策略

  1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用普通用戶user1登錄
  2 [student@workstation ~]$ oc new-project test					#測試創建project
  3 Error from server (Forbidden): You may not request a new project via this API.


5.6 創建項目

  1 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用集群管理員登錄
  2 [student@workstation ~]$ oc new-project project-user1				#創建兩個項目
  3 [student@workstation ~]$ oc new-project project-user2


5.7 將項目與user關聯

  1 #選擇項目1
  2 Now using project "project-user1" on server "https://master.lab.example.com:443".
  3 [student@workstation ~]$ oc policy add-role-to-user admin user1		#將user1添加為項目1的管理員
  4 role "admin" added: "user1"
  5 [student@workstation ~]$ oc policy add-role-to-user edit user2		#將user2添加為項目1的開發員
  6 role "edit" added: "user2"
  7 
  8 [student@workstation ~]$ oc project project-user2			        #選擇項目2
  9 Now using project "project-user2" on server "https://master.lab.example.com:443".
 10 [student@workstation ~]$ oc policy add-role-to-user edit user2		#將user2添加為項目2的開發員
 11 role "edit" added: "user2"


5.8 驗證訪問

  1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用user1登錄
  2 [student@workstation ~]$ oc project project-user1					#驗證項目1的訪問
  3 Already on project "project-user1" on server "https://master.lab.example.com:443".
  4 [student@workstation ~]$ oc project project-user2					#驗證項目2的訪問
  5 error: You are not a member of project "project-user2".
  6 You have one project on this server: project-user1
  7 
  8 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com	#使用user2登錄
  9 [student@workstation ~]$ oc project project-user1
 10 Already on project "project-user1" on server "https://master.lab.example.com:443".	#驗證項目1的訪問
 11 [student@workstation ~]$ oc project project-user2
 12 Now using project "project-user2" on server "https://master.lab.example.com:443".	#驗證項目2的訪問


5.9 部署特權應用

  1 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com
  2 [student@workstation ~]$ oc project project-user1
  3 Now using project "project-user1" on server "https://master.lab.example.com:443".
  4 [student@workstation ~]$ oc new-app --name=nginx --docker-image=registry.lab.example.com/nginx:latest
  5 #使用在項目1上不具備admin權限的用戶user2登錄,並部署應用,會出現如下提示:





5.10 驗證部署

  1 [student@workstation ~]$ oc get pods




結論:由上可知,部署失敗是因為容器映像需要root用戶,pod以CrashLoopBackOff或錯誤狀態結束。

5.11 故障排除


若要解決此故障需要減少特定項目的安全限制。

要使用特權訪問運行容器,可創建一個允許pod使用操作系統普通用戶運行的service account。

如下部分需要具有項目管理員特權的用戶執行,而另一些操作需要具有集群管理員特權的用戶執行。

本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的configure-sc.sh腳本運行或複製。

  1 [student@workstation ~]$ oc login -u user1 -p redhat https://master.lab.example.com	#使用項目1的admin賬戶登錄 
  2 [student@workstation ~]$ oc create serviceaccount useroot		  #創建服務賬戶
  3 serviceaccount "useroot" created
  4 [student@workstation ~]$ oc login -u admin -p redhat https://master.lab.example.com	#使用集群管理員登錄
  5 [student@workstation ~]$ oc project project-user1			  #選擇項目1
  6 Already on project "project-user1" on server "https://master.lab.example.com:443".
  7 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid -z useroot  #設置SCC策略
  8 scc "anyuid" added to: ["system:serviceaccount:project-user1:useroot"]    #將服務帳戶與anyuid安全上下文關聯,此操作需要集群管理員用戶。
  9 [student@workstation ~]$ oc login -u user2 -p redhat https://master.lab.example.com	#切換user2用戶
 10 [student@workstation ~]$ oc project project-user1
 11 Already on project "project-user1" on server "https://master.lab.example.com:443".
 12 [student@workstation ~]$ oc patch dc nginx --patch='{"spec":{"template":{"spec":{"serviceAccountName": "useroot"}}}}'



#更新負責管理nginx的dc資源,任何開發人員用戶都可以執行此操作。本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的configure-sc.sh腳本運行或複製。

5.12 驗證確認

  1 [student@workstation ~]$ oc get pods
  2 NAME            READY     STATUS    RESTARTS   AGE
  3 nginx-2-98k8f   1/1       Running   0          3m
  4 

5.13 暴露服務

  1 [student@workstation ~]$ oc expose svc nginx
  2 route "nginx" exposed
  3 [student@workstation ~]$ oc get svc
  4 NAME      TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
  5 nginx     ClusterIP   172.30.118.63   <none>        80/TCP    13m
  6 [student@workstation ~]$ oc get route
  7 NAME      HOST/PORT                                  PATH      SERVICES   PORT      TERMINATION   WILDCARD
  8 nginx     nginx-project-user1.apps.lab.example.com             nginx      80-tcp                  None


5.14 測試訪問

  1 [student@workstation ~]$ curl -s http://nginx-project-user1.apps.lab.example.com

5.15 策略刪除演示

  1 [student@workstation ~]$ oc login -u admin -p redhat
  2 [student@workstation ~]$ oc adm policy add-cluster-role-to-group self-provisioner system:authenticated system:authenticated:oauth
  3 cluster role "self-provisioner" added: ["system:authenticated" "system:authenticated:oauth"]
  4 [student@workstation ~]$ oc delete project project-user1
  5 project "project-user1" deleted
  6 [student@workstation ~]$ oc delete project project-user2
  7 [root@master ~]# htpasswd -D /etc/origin/master/htpasswd user1 
  8 [root@master ~]# htpasswd -D /etc/origin/master/htpasswd user2


#為所有常規用戶重新啟用項目創建,即重置為初始狀態。本環境中,相關操作命令可以從/home/student/DO280/labs/secure-resources文件夾中的restore-policy.sh腳本運行或複製。

六 管理加密信息

6.1 secret特性


Secret對象類型提供了一種機制來保存敏感信息,如密碼、OCP客戶端配置文件、Docker配置文件和私有倉庫憑據。Secrets將敏感內容與Pod解耦。可以使用Volume插件將Secrets掛載到容器上,或者系統可以使用Secrets代表pod執行操作。

Secrets的主要特徵包括:

  • Secrets data可以獨立於其定義引用。
  • Secrets data Volume由臨時文件存儲支持。
  • 可以在名稱空間中共享Secrets data。

6.2 創建Secrets


在依賴於該Secrets的pod之前創建一個Secrets。

  1 [user@demo ~]$ oc create secret generic secret_name \
  2 --from-literal=key1=secret1 \
  3 --from-literal=key2=secret2	#用secret data創建secret對象
  4 [user@demo ~]$ oc secrets add --for=mount serviceaccount/serviceaccount-name \
  5 secret/secret_name		#更新pod的服務帳戶,允許引用該secrets。

例如,允許一個運行在指定服務帳戶下的pod掛載一個secrets

創建一個pod,該pod使用環境變量或數據卷作為文件的方式使用該secret,通常使用模板完成。

6.3 使用secret暴露Pod


secrets可以作為數據卷掛載,也可以作為環境變量以便供pod中的容器使用。

例如,要向pod公開一個secrets,首先創建一個secrets並將username和password以k/v形式配置,然後將鍵名分配給pod的YAML文件env定義。

示例:創建名為demo-secret的secrets,它定義用戶名和密碼為username/demo-user。

[user@demo ~]$ oc create secret generic demo-secret \

–from-literal=username=demo-user

要使用前面的secret作為MySQL數據庫pod的數據庫管理員密碼,請定義環境變量,並引用secret名稱和密碼。

  1 env:
  2   - name: MYSQL_ROOT_PASSWORD
  3     valueFrom:
  4       secretKeyRef:
  5        key: username
  6        name: demo-secret


6.4 web端管理secret


從web控制台管理secret:

1. 以授權用戶身份登錄到web控制台。

2. 創建或選擇一個項目來承載secret。

3. 導航到resource——>secrets。

6.5 Secret使用場景


  • password和user names


敏感信息(如password和user name)可以存儲在一個secret中,該secret被掛載為容器中的數據卷。數據显示為位於容器的數據卷目錄中的文件中的內容。然後,應用程序(如數據庫)可以使用這些secret對用戶進行身份驗證。

  • 傳輸層安全性(TLS)和密鑰對


通過讓集群將簽名證書和密鑰對生成到項目名稱空間中的secret中,可以實現對服務的通信的保護。證書和密鑰對使用PEM格式存儲以類似tls.crt和tls.key的格式存儲在secret的pod中。

七 ConfigMap對象

7.1 ConfigMap概述


ConfigMaps對象類似於secret,但其設計目的是支持處理不包含敏感信息的字符串。ConfigMap對象持有配置數據的鍵值對,這些配置數據可以在pods中使用,或者用於存儲系統組件(如控制器)的配置數據。

ConfigMap對象提供了將配置數據注入容器的機制。ConfigMap存儲精細的粒度信息,比如單個屬性,或者詳細信息,比如整個配置文件或JSON blob。

7.2 CLI創建ConfigMap


可以使用–from-literal選項從CLI創建ConfigMap對象。

示例:創建一個ConfigMap對象,該對象將IP地址172.20.30.40分配給名為serverAddress的ConfigMap密鑰。

  1 [user@demo ~]$ oc create configmap special-config \
  2 --from-literal=serverAddress=172.20.30.40
  3 [user@demo ~]$ oc get configmaps special-config -o yaml		#查看configMap
  4 apiVersion: v1
  5 data:
  6   key1: serverAddress=172.20.30.40
  7 kind: ConfigMap
  8 metadata:
  9   creationTimestamp: 2017-07-10T17:13:31Z
 10   name: special-config
 11 ……
 12 在配置映射的pod定義中填充環境變量APISERVER。
 13 env:
 14   - name: APISERVER
 15       valueFrom:
 16         configMapKeyRef:
 17           name: special-config
 18           key: serverAddress


7.3 web管理ConfigMap


從web控制台管理ConfigMap對象:

1. 以授權用戶身份登錄到web控制台。

2. 創建或選擇一個項目來承載ConfigMap。

3. 導航到資源→配置映射。

八 加密練習

8.1 前置準備


準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

8.2 本練習準備


[student@workstation ~]$ lab secure-secrets setup

8.3 創建項目

  1 [student@workstation ~]$ oc login -u developer -p redhat
  2 [student@workstation ~]$ cd /home/student/DO280/labs/secure-secrets/
  3 [student@workstation secure-secrets]$ less mysql-ephemeral.yml		#導入本環境MySQL模板







模板解讀:

該mysql-ephemeral.yml模板文件,包含openshift項目中的mysql臨時模板,pod所需的其他環境變量由模板參數初始化,並具有默認值。

但沒有secret定義,後續操作將手動創建模板所需的secret。

根據模板的要求,創建一個包含MySQL容器image使用的憑證的secret,將這個secret命名為mysql。

  • 應用程序訪問的數據庫用戶名由database-user定義。
  • 數據庫用戶的密碼由database-password定義。
  • 數據庫管理員密碼由database-root-password定義

8.4 創建新secret

  1 [student@workstation secure-secrets]$ oc create secret generic mysql \
  2 --from-literal='database-user'='mysql' \
  3 --from-literal='database-password'='redhat' \
  4 --from-literal='database-root-password'='do280-admin'
  5 [student@workstation secure-secrets]$ oc get secret mysql -o yaml	#確認secret




8.5 創建應用

  1 [student@workstation secure-secrets]$ oc new-app --file=mysql-ephemeral.yml
  2 [student@workstation secure-secrets]$ oc get pods		#確認應用
  3 NAME            READY     STATUS    RESTARTS   AGE
  4 mysql-1-j4fnz   1/1       Running   0          1m


8.6 端口轉發

  1 [student@workstation secure-secrets]$ cd
  2 [student@workstation ~]$ oc port-forward mysql-1-j4fnz 3306:3306



提示:驗證完成之前forward不要關閉。

8.7 確認驗證

  1 [student@workstation ~]$ mysql -uroot -pdo280-admin -h127.0.0.1	#新開終端測試MySQL



九 管理security policy

9.1 OCP authorization授權


OCP定義了用戶可以執行的兩組主要操作:

與項目相關的操作(也稱為本地策略):project-related

與管理相關的操作(也稱為集群策略):administration-related

由於這兩種策略都有大量可用的操作,所以將一些操作分組並定義為角色。














默認角色 描述
cluster-admin 此角色中的所有用戶都可以管理OpenShift集群。
cluster-status 此角色中的所有用戶都提供對集群信息的只讀訪問。


為管理本地政策,OCP提供以下角色:




















默認角色 描述
edit 角色中的用戶可以從項目中創建、更改和刪除公共應用程序資源,比如service和dc。 但是不能對限制範圍和配額等管理資源採取行動,也不能管理對項目的訪問權限。
basic-user 角色中的用戶具有對項目的讀訪問權。
self-provisioner 角色中的用戶可以創建新項目。這是一個集群角色,而不是項目角色。
admin 角色中的用戶可以管理項目中的所有資源,包括授予對項目的其他用戶的訪問權


除了能夠創建新應用程序之外,admin角色還允許用戶訪問項目資源,比如配額和限制範圍。

edit角色允許用戶在項目中充當開發人員,但要在項目管理員配置的約束下工作。

9.2 相關命令

  1 向集群用戶添加角色
  2 $ oc adm policy add-cluster-role-to-user cluster-role username
  3 示例:將普通用戶更改為集群管理員。
  4 $ oc adm policy add-cluster-role-to-user cluster-role username
  5 從用戶中刪除集群角色
  6 $ oc adm policy remove-cluster-role-from-user cluster-role username
  7 示例:將集群管理員更改為普通用戶。
  8 $ oc adm policy remove-cluster-role-from-user cluster-admin username
  9 將指定的用戶綁定到項目中的角色
 10 $ oc adm policy add-role-to-user role-name username -n project
 11 示例:在WordPress項目中dev用戶綁定basic-user角色。
 12 $ oc adm policy add-role-to-user basic-user dev -n wordpress


9.3 權限及規則


OpenShift將一組規則集合成一個角色,規則由謂詞和資源定義。如create user是OpenShift中的一條規則,它是一個名為cluster-admin的角色的所擁有的權限的一部分。

  1 $ oc adm policy who-can delete user

9.4 user類型


與OCP的交互基於用戶,OCP的user對象表示可以通過向該用戶或用戶組添加角色來從而實現相應權限的授予。

Regular users:通常以這種用戶類型與OCP交互,常規用戶用User對象表。例如,user1,user2。

System users:通常在安裝OCP中定義基礎設施時自動創建的,主要目的是使基礎設施能夠安全地與API交互。包括集群管理員(可以訪問所有內容)、每個節點的用戶、路由器和內部倉庫使用的用戶,以及各種其他用戶。還存在一個匿名系統用戶,默認情況下,該用戶用於未經身份驗證的請求。system user主要包括:system:admin、system:openshift-registry和system:node:node1.example.com。

Service accounts:這些是與項目關聯的特殊系統用戶。有些是在第一次創建項目時自動創建的,項目管理員可以創建多個,以便定義對每個項目內容的訪問。Service accounts由ServiceAccount對象表示。Service accounts主要包括:system:serviceaccount:default:deployer和system:serviceaccount:foo:builder。

每個用戶在訪問OpenShift容器平台之前必須進行身份驗證。沒有身份驗證或身份驗證無效的API請求將使用匿名系統用戶身份驗證來請求服務。身份驗證成功后,策略確定用戶被授權做什麼。

9.5 安全上下文約束(SCCS)


OpenShift提供了一種名為安全上下文約束的安全機制,它限制對資源的訪問,但不限制OpenShift中的操作。

SCC限制從OpenShift中運行的pod到主機環境的訪問:

  • 運行特權容器
  • 請求容器的額外功能
  • 使用主機目錄作為卷
  • 更改容器的SELinux上下文
  • 更改用戶ID


社區開發的一些容器可能需要放鬆安全上下文約束,因為它們可能需要訪問默認禁止的資源,例如文件系統、套接字或訪問SELinux上下文。

OpenShift定義的安全上下文約束(SCCs)可以使用以下命令作為集群管理員列出。

$ oc get scc

SCC通常有以下7中SCCS:

  • anyuid
  • hostaccess
  • hostmount-anyuid
  • nonroot
  • privileged
  • restricted(默認)


$ oc describe scc anyuid #查看某一種SCC詳情

OpenShift創建的所有容器都使用restricted類型的SCC,它提供了對OpenShift外部資源的有限訪問。

對於anyuid安全上下文,run as user策略被定義為RunAsAny,表示pod可以作為容器中可用的任何用戶ID運行。這允許需要特定用戶使用特定用戶ID運行命令的容器。

要將容器更改為使用不同的SCC運行,需要創建綁定到pod的服務帳戶。

$ oc create serviceaccount service-account-name #首先創建服務賬戶

$ oc adm policy add-scc-to-user SCC -z service-account #將服務帳戶與SCC關聯

要確定哪個帳戶可以創建需要更高安全性要求的pod,可以使用scc-subject-review子命令。

$ oc export pod pod-name > output.yaml

$ oc adm policy scc-subject-review -f output.yaml

9.6 OpenShift與SELinux


OpenShift要求在每個主機上啟用SELinux,以便使用強制訪問控制提供對資源的安全訪問。同樣,由OpenShift管理的Docker容器需要管理SELinux上下文,以避免兼容性問題。

為了最小化在不支持SELinux的情況下運行容器的風險,可以創建SELinux上下文策略。

為了更新SELinux上下文,可以使用現有的SCC作為起點生成一個新的SCC。

$ oc export scc restricted > custom_selinux.yml #導出默認的SCC

編輯導出的YAML文件以更改SCC名稱和SELinux上下文。

$ oc create -f yaml_file #使用修改后的ymal重新創建一個SCC

9.7 特權容器


有些容器可能需要訪問主機的運行時環境。S2I構建器容器需要訪問宿主docker守護進程來構建和運行容器。

例如,S2I構建器容器是一類特權容器,它要求訪問超出其自身容器的限制。這些容器可能會帶來安全風險,因為它們可以使用OpenShift節點上的任何資源。通過創建具有特權訪問權的服務帳戶,可以使用SCCs啟用特權容器的訪問。

十 資源訪問控制綜合實驗

10.1 前置準備


準備完整的OpenShift集群,參考《003.OpenShift網絡》2.1。

10.2 本練習準備

  1 [student@workstation ~]$ lab secure-review setup

10.3 創建用戶

  1 [root@master ~]# htpasswd /etc/origin/master/htpasswd user-review
  2 New password: 【redhat】
  3 Re-type new password: 【redhat】


10.4 修改策略

  1 [student@workstation ~]$ oc login -u admin -p redhat
  2 [student@workstation ~]$ oc adm policy remove-cluster-role-from-group \
  3 self-provisioner system:authenticated system:authenticated:oauth
  4 禁用所有常規用戶的項目創建功能


10.5 確認驗證

  1 [student@workstation ~]$ oc login -u user-review -p redhat
  2 [student@workstation ~]$ oc new-project test			#普通用戶無法創建項目
  3 Error from server (Forbidden): You may not request a new project via this API.


10.6 創建項目

  1 [student@workstation ~]$ oc login -u admin -p redhat
  2 [student@workstation ~]$ oc new-project secure-review		#使用管理員創建項目


10.7 授權用戶

  1 [student@workstation ~]$ oc project secure-review
  2 Already on project "secure-review" on server "https://master.lab.example.com:443".
  3 [student@workstation ~]$ oc policy add-role-to-user edit user-review	#將edit的role和user-review進行關聯


10.8 測試訪問

  1 [student@workstation ~]$ oc login -u user-review -p redhat
  2 [student@workstation ~]$ oc project secure-review		#測試訪問
  3 Already on project "secure-review" on server "https://master.lab.example.com:443".


10.9 檢查模板

  1 [student@workstation ~]$ cd /home/student/DO280/labs/secure-review/
  2 [student@workstation secure-review]$ less mysql-ephemeral.yml





模板解讀:

該mysql-ephemeral.yml模板文件,包含openshift項目中的mysql臨時模板,pod所需的其他環境變量由模板參數初始化,並具有默認值。

但沒有secret定義,後續操作將手動創建模板所需的secret。

根據模板的要求,創建一個包含MySQL容器image使用的憑證的secret,將這個secret命名為mysql。

  • 應用程序訪問的數據庫用戶名由database-user定義。
  • 數據庫用戶的密碼由database-password定義。
  • 數據庫管理員密碼由database-root-password定義


使用user-review developer用戶創建一個名為mysql的secret。這個secret應該存儲用戶名mysql、密碼redhat和數據庫管理員密碼do280-admin。

數據庫用戶名由database-user定義。此用戶的密碼由mysql secret密鑰定義。

數據庫管理員密碼由database-root-password定義。

10.10 創建secret

  1 [student@workstation secure-review]$ oc create secret generic mysql \
  2 --from-literal='database-user'='mysql' \
  3 --from-literal='database-password'='redhat' \
  4 --from-literal='database-root-password'='do280-admin'
  5 [student@workstation secure-review]$ oc get secret mysql -o yaml	#確認驗證secret


10.11 部署應用

  1 [student@workstation secure-review]$ oc new-app --file=mysql-ephemeral.yml
  2 [student@workstation secure-review]$ oc get pods
  3 NAME            READY     STATUS    RESTARTS   AGE
  4 mysql-1-2lr7t   1/1       Running   0          31s


10.12 轉發端口

  1 [student@workstation ~]$ oc port-forward mysql-1-2lr7t 3306:3306

10.13 測試訪問

  1 [student@workstation ~]$ mysql -umysql -predhat -h127.0.0.1

10.14 部署phpmyadmin應用


使用內部倉庫registry.lab.example.com的image部署phpmyadmin:4.7容器。phpmyadmin:4.7容器需要名為PMA_HOST的環境變量來提供MySQL服務器的IP地址。

使用模板創建一個基於FQND的MySQL pod的service。

為使用模板創建的MySQL服務器pod使用服務FQDN,該模板是mysql.secure-review.svc.cluster.local。

  1 [student@workstation ~]$ oc new-app --name=phpmyadmin \
  2 --docker-image=registry.lab.example.com/phpmyadmin/phpmyadmin:4.7 \
  3 -e PMA_HOST=mysql.secure-review.svc.cluster.local






結論:該命令會發出警告,提示需要root特權。默認情況下,OpenShift不支持使用操作系統的root用戶運行容器。

10.15 查看pod

  1 [student@workstation ~]$ oc get pods
  2 NAME                 READY     STATUS    RESTARTS   AGE
  3 mysql-1-2lr7t        1/1       Running   0          8m
  4 phpmyadmin-1-v7tl7   0/1       Error     2          1m
  5 因為沒有root權限,因此部署失敗,需要提權。


10.16 授予權限

  1 [student@workstation ~]$ oc login -u admin -p redhat			#使用管理員登錄
  2 [student@workstation ~]$ oc create serviceaccount phpmyadmin-account	#首先創建服務賬戶
  3 [student@workstation ~]$ oc adm policy add-scc-to-user anyuid -z phpmyadmin-account
  4 scc "anyuid" added to: ["system:serviceaccount:secure-review:phpmyadmin-account"]	#將服務帳戶與anyuid安全上下文關聯


10.17 更新應用

  1 [student@workstation ~]$ oc patch dc phpmyadmin --patch='{"spec":{"template":{"spec":{"serviceAccountName": "phpmyadmin-account"}}}}'


#更新負責管理phpmyadmin的dc資源,任何開發人員用戶都可以執行此操作。

本環境中,相關操作命令可以從/home/student/DO280/labs/secure-review文件夾中的patch-dc.sh腳本運行或複製。

10.18 確認驗證

  1 [student@workstation ~]$ oc login -u user-review -p redhat
  2 [student@workstation ~]$ oc get pods			#確認pod是否正常
  3 NAME                 READY     STATUS    RESTARTS   AGE
  4 mysql-1-2lr7t        1/1       Running   0          13m
  5 phpmyadmin-2-bdjvq   1/1       Running   0          1m


10.19 暴露服務

  1 [student@workstation ~]$ oc expose svc phpmyadmin --hostname=phpmyadmin.apps.lab.example.com

10.20 訪問測試

  1 [student@workstation ~]$ curl -s http://phpmyadmin.apps.lab.example.com

10.21 確認及刪除

  1 [student@workstation ~]$ lab secure-review grade		#環境腳本判斷
  2 [student@workstation ~]$ oc login -u admin -p redhat
  3 [student@workstation ~]$ oc adm policy add-cluster-role-to-group \
  4 self-provisioner system:authenticated system:authenticated:oauth
  5 [student@workstation ~]$ oc delete project secure-review
  6 [student@workstation ~]$ ssh root@master htpasswd -D \
  7 /etc/origin/master/htpasswd user-review			#刪除用戶
  8 [student@workstation ~]$ oc delete user user-review		#刪除項目

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

【其他文章推薦】

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

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

Linux 進程間通信(IPC)總結

概述

一個大型的應用系統,往往需要眾多進程協作,進程(Linux進程概念見附1)間通信的重要性顯而易見。本系列文章闡述了 Linux 環境下的幾種主要進程間通信手段。

進程隔離

進程隔離是為保護操作系統中進程互不干擾而設計的一組不同硬件和軟件的技術。這個技術是為了避免進程A寫入進程B的情況發生。 進程的隔離實現,使用了虛擬地址空間。進程A的虛擬地址和進程B的虛擬地址不同,這樣就防止進程A將數據信息寫入進程B。

虛擬地址空間

當創建一個進程時,操作系統會為該進程分配一個 4GB 大小的虛擬進程地址空間。之所以是 4GB ,是因為在 32 位的操作系統中,一個指針長度是 4 字節,而 4 字節指針的尋址能力是從 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即為 4GB 大小的容量。與虛擬地址空間相對的,還有一個物理地址空間,這個地址空間對應的是真實的物理內存。要注意的是這個 4GB 的地址空間是“虛擬”的,並不是真實存在的,而且每個進程只能訪問自己虛擬地址空間中的數據,無法訪問別的進程中的數據,通過這種方法實現了進程間的地址隔離。

針對 Linux 操作系統,將最高的1G字節(從虛擬地址 0xC0000000 到 0xFFFFFFFF )供內核使用,稱為內核空間,而較低的 3G 字節(從虛擬地址 0x00000000 到0xBFFFFFFF),供各個進程使用,稱為用戶空間。每個進程都可以通過系統調用進入到內核。其中在 Linux 系統中,進程的用戶空間是獨立的,而內核空間是共有的,進程切換時,用戶空間切換,內核空間不變

創建虛擬地址空間目的是為了解決進程地址空間隔離的問題。但程序要想執行,必須運行在真實的內存上,所以,必須在虛擬地址與物理地址間建立一種映射關係。這樣,通過映射機制,當程序訪問虛擬地址空間上的某個地址值時,就相當於訪問了物理地址空間中的另一個值。人們想到了一種分段、分頁的方法,它的思想是在虛擬地址空間和物理地址空間之間做一一映射。這種思想理解起來並不難,操作系統保證不同進程的地址空間被映射到物理地址空間中不同的區域上,這樣每個進程最終訪問到的物理地址空間都是彼此分開的。通過這種方式,就實現了進程間的地址隔離。

 

系統調用/內核態/用戶態

雖然從邏輯上抽離出用戶空間和內核空間;但是不可避免的的是,總有那麼一些用戶空間需要訪問內核的資源;比如應用程序訪問文件,網絡是很常見的事情,怎麼辦呢?

用戶空間訪問內核空間的唯一方式就是系統調用;通過這個統一入口接口,所有的資源訪問都是在內核的控制下執行,以免導致對用戶程序對系統資源的越權訪問,從而保障了系統的安全和穩定。用戶軟件良莠不齊,要是它們亂搞把系統玩壞了怎麼辦?因此對於某些特權操作必須交給安全可靠的內核來執行。

當一個任務(進程)執行系統調用而陷入內核代碼中執行時,我們就稱進程處於內核運行態(或簡稱為內核態)此時處理器處於特權級最高的(0級)內核代碼中執行。當進程在執行用戶自己的代碼時,則稱其處於用戶運行態(用戶態)。即此時處理器在特權級最低的(3級)用戶代碼中運行。處理器在特權等級高的時候才能執行那些特權CPU指令。

IPC 通信原理

理解了上面的幾個概念,我們再來看看進程之間是如何實現通信的。

通常的做法是消息發送方將要發送的數據存放在內存緩存區中,通過系統調用進入內核態。然後內核程序在內核空間分配內存,開闢一塊內核緩存區,調用 copy_from_user() 函數將數據從用戶空間的內存緩存區拷貝到內核空間的內核緩存區中。同樣的,接收方進程在接收數據時在自己的用戶空間開闢一塊內存緩存區,然後內核程序調用 copy_to_user() 函數將數據從內核緩存區拷貝到接收進程的內存緩存區。這樣數據發送方進程和數據接收方進程就完成了一次數據傳輸,我們稱完成了一次進程間通信。如下圖:

進程間通信方式

Linux 進程間基本的通信方式主要有:管道(pipe) (包括匿名管道和命名管道)、信號(signal)、消息隊列(queue)、共享內存、信號量和套接字。

管道

管道的實質是一個內核緩衝區(調用 pipe 函數來開闢),管道的作用正如其名,需要通信的兩個進程在管道的兩端,進程利用管道傳遞信息。管道對於管道兩端的進程而言,就是一個文件,但是這個文件比較特殊,它不屬於文件系統並且只存在於內存中。 Linux一切皆文件,操作系統為管道提供操作的方法:文件操作,用 fork 來共享管道原理。

管道依據是否有名字分為匿名管道和命名管道(有名管道),這兩種管道有一定的區別。

匿名管道有幾個重要的限制:

  1. 管道是半雙工的,數據只能在一個方向上流動,A進程傳給B進程,不能反向傳遞
  2. 管道只能用於父子進程或兄弟進程之間的通信,即具有親緣關係的進程。

命名管道允許沒有親緣關係的進程進行通信。命名管道不同於匿名管道之處在於它提供了一個路徑名與之關聯,這樣一個進程即使與創建有名管道的進程不存在親緣關係,只要可以訪問該路徑,就能通過有名管道互相通信。

pipe 函數接受一個參數,是包含兩個整數的數組,如果調用成功,會通過 pipefd[2] 傳出給用戶程序兩個文件描述符,需要注意 pipefd[0] 指向管道的讀端, pipefd[1] 指向管道的寫端,那麼此時這個管道對於用戶程序就是一個文件,可以通過 read(pipefd [0]);或者 write(pipefd [1]) 進行操作。pipe 函數調用成功返回 0,否則返回 -1.

那麼再來看看通過管道進行通信的步驟:

  • 父進程創建管道,得到兩個文件描述符指向管道的兩端
  • 利用fork函數創建齣子進程,則子進程也得到兩個文件描述符指向同一管道
  • 父進程關閉讀端(pipe[0]),子進程關閉寫端pipe[1],則此時父進程可以往管道中進行寫操作,子進程可以從管道中讀,從而實現了通過管道的進程間通信。

     

管道的特點:

  • 只能單向通信

兩個文件描述符,用一個,另一個不用,不用的文件描述符就要 close

  • 只能血緣關係的進程進行通信

  • 依賴於文件系統

  • 生命周期隨進程

  • 面向字節流的服務

面向字節流:數據無規則,沒有明顯邊界,收發數據比較靈活:對於用戶態,可以一次性發送也可以分次發送,當然接受數據也如此;而面向數據報:數據有明顯邊界,數據只能整條接受 

  • 管道內部提供了同步機制

臨界資源: 大家都能訪問到的共享資源

臨界區: 對臨界資源進行操作的代碼

同步: 臨界資源訪問的可控時序性(一個操作完另一個才可以操作)

互斥: 對臨界資源同一時間的唯一訪問性(保護臨界資源安全)

說明:因為管道通信是單向的,在上面的例子中我們是通過子進程寫父進程來讀,如果想要同時父進程寫而子進程來讀,就需要再打開另外的管道;

管道的讀寫端通過打開的文件描述符來傳遞,因此要通信的兩個進程必須從它們的公共祖先那裡繼承管道的件描述符。 上面的例子是父進程把文件描述符傳給子進程之後父子進程之 間通信,也可以父進程fork兩次,把文件描述符傳給兩個子進程,然後兩個子進程之間通信, 總之 需要通過fork傳遞文件描述符使兩個進程都能訪問同一管道,它們才能通信。

四個特殊情況:

  1. 如果所有指向管道寫端的文件描述符都關閉了,而仍然有進程從管道的讀端讀數據,那麼管道中剩餘的數據都被讀取后,再次read會返回0,就像讀到文件末尾一樣

  2. 如果有指向管道寫端的文件描述符沒關閉,而持有管道寫端的進程也沒有向管道中寫數據,這時有進程從管道讀端讀數據,那麼管道中剩餘的數據都被讀取后,再次read會阻塞,直到管道中有數據可讀了才讀取數據並返回。

  3. 如果所有指向管道讀端的文件描述符都關閉了,這時有進程指向管道的寫端write,那麼該進程會收到信號SIGPIPE,通常會導致進程異常終止。

  4. 如果有指向管道讀端的文件描述符沒關閉,而持有管道寫端的進程也沒有從管道中讀數據,這時有進程向管道寫端寫數據,那麼在管道被寫滿時再write會阻塞,直到管道中有空位置了才寫入數據並返回。

命名管道FIFO

在管道中,只有具有血緣關係的進程才能進行通信,對於後來的命名管道,就解決了這個問題。FIFO 不同於管道之處在於它提供一個路徑名與之關聯,以 FIFO 的文件形式存儲於文件系統中。命名管道是一個設備文件,因此,即使進程與創建FIFO的進程不存在親緣關係,只要可以訪問該路徑,就能夠通過 FIFO 相互通信。值得注意的是, FIFO (first input first output) 總是按照先進先出的原則工作,第一個被寫入的數據將首先從管道中讀出。

命名管道的創建

創建命名管道的系統函數有兩個: mknod 和 mkfifo。兩個函數均定義在頭文件 sys/stat.h,
函數原型如下:

#include <sys/types.h>
#include <sys/stat.h>
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);

函數 mknod 參數中 path 為創建的命名管道的全路徑名: mod 為創建的命名管道的模指明其存取權限; dev 為設備值,該值取決於文件創建的種類,它只在創建設備文件時才會用到。這兩個函數調用成功都返回 0,失敗都返回 -1。

命名管道打開特性:

  1. 如果用只讀打開命名管道,open 函數將阻塞等待直至有其他進程以寫的方式打開這個命名管道,如果沒有進程以寫的方式發開這個命名管道,程序將停在此處

  2. 如果用只寫打開命名管道,open 函數將阻塞等到直至有其他進程以讀的方式打開這個命名管道,如果沒有進程以讀的方式發開這個命名管道,程序將停在此處;

  3. 如果用讀寫打開命名管道,則不會阻塞(但是管道是單向)

System V IPC   

IPC(Inter-Process Communication)是指多個進程之間相互通信,交換信息的方法,System V 是 Unix 操作系統最早的商業發行版,由 AT&T(American Telephone & Telegraph)開發。System V IPC 是指 Linux 引入自 System V 的進程通信機制,一共有三種:

  • 信號量,用來管理對共享資源的訪問;

  • 共享內存,用來高效地實現進程間的數據共享;

  • 消息隊列,用來實現進程間數據的傳遞。

這三種統稱 IPC 資源,每個 IPC 資源都是請求時動態創建的,都是永駐內存,除非被進程显示釋放,都是可以被任一進程使用。每個 IPC 資源都使用一個 32 位的 IPC 關鍵字和 32 位的 IPC 標識符,前者類似文件系統中的路徑名,由程序自由定製,後者類似打開文件的文件描述符,由內核統一分配,在系統內部是唯一的,當多個進程使用同一個IPC資源通信時需要該資源的 IPC 標識符。     

創建新的 IPC 資源時需要指定 IPC 關鍵字,如果沒有與之關聯的 IPC 資源,則創建一個新的 IPC 資源;如果已經存在,則判斷當前進程是否具有訪問權限,是否超過資源使用限制等,如果符合條件則返回該資源的 IPC 標識符。為了避免兩個不同的 IPC 資源使用相同的 IPC 關鍵字,創建時可以指定IPC關鍵字為 IPC_PRIVATE,由內核負責生成一個唯一的關鍵字。   

創建新的 IPC 資源時最後一個參數可以包括三個標誌,PC_CREAT 說明如果IPC資源不存在則必須創建它,IPC_EXCL 說明如果資源已經存在且設置了 PC_CREAT 標誌則創建失敗,IPC_NOWAIT 說明訪問 IPC 資源時進程從不阻塞。 

信號量

信號量(semaphore)是一種用於提供不同進程之間或者一個給定的不同線程間同步手段的原語。信號量多用於進程間的同步與互斥,簡單的說一下同步和互斥的意思:

同步:處理競爭就是同步,安排進程執行的先後順序就是同步,每個進程都有一定的先後執行順序。

互斥:互斥訪問不可共享的臨界資源,同時會引發兩個新的控制問題(互斥可以說是特殊的同步)。

競爭:當併發進程競爭使用同一個資源的時候,我們就稱為競爭進程。

共享資源通常分為兩類:一類是互斥共享資源,即任一時刻只允許一個進程訪問該資源;另一類是同步共享資源,即同一時刻允許多個進程訪問該資源;信號量是解決互斥共享資源的同步問題而引入的機制。

下面說一下信號量的工作機制,可以直接理解成計數器(當然其實加鎖的時候肯定不能這麼簡單,不只只是信號量了),信號量會有初值(>0),每當有進程申請使用信號量,通過一個 P 操作來對信號量進行-1操作,當計數器減到 0 的時候就說明沒有資源了,其他進程要想訪問就必須等待(具體怎麼等還有說法,比如忙等待或者睡眠),當該進程執行完這段工作(我們稱之為臨界區)之後,就會執行 V 操作來對信號量進行 +1 操作。

  • 臨界區:臨界區指的是一個訪問共用資源(例如:共用設備或是共用存儲器)的程序片段,而這些共用資源又無法同時被多個線程訪問的特性。

  • 臨界資源:只能被一個進程同時使用(不可以多個進程共享),要用到互斥。

我們可以說信號量也是進程間通信的一種方式,比如互斥鎖的簡單實現就是信號量,一個進程使用互斥鎖,並通知(通信)其他想要該互斥鎖的進程,阻止他們的訪問和使用。

當有進程要求使用共享資源時,需要執行以下操作:

  1. 系統首先要檢測該資源的信號量;

  2. 若該資源的信號量值大於 0,則進程可以使用該資源,此時,進程將該資源的信號量值減1;

  3. 若該資源的信號量值為 0,則進程進入休眠狀態,直到信號量值大於 0 時進程被喚醒,訪問該資源;

       當進程不再使用由一個信號量控制的共享資源時,該信號量值增加 1,如果此時有進程處於休眠狀態等待此信號量,則該進程會被喚醒

每個信號量集都有一個與其相對應的結構,該結構定義如下:

/* Data structure describing a set of semaphores.  */  
struct semid_ds  
{  
    struct ipc_perm sem_perm;   /* operation permission struct */  
    struct sem *sem_base;       /* ptr to array of semaphores in set */  
    unsigned short sem_nsems;   /* # of semaphores in set */  
    time_t sem_otime;           /* last-semop() time */  
    time_t sem_ctime;           /* last-change time */  
};  
  
/* Data structure describing each of semaphores.  */  
struct sem  
{  
    unsigned short semval;  /* semaphore value, always >= 0 */  
    pid_t          sempid;  /* pid for last successful semop(), SETVAL, SETALL */  
    unsigned short semncnt; /* # processes awaiting semval > curval */  
    unsigned short semzcnt; /* # processes awaiting semval == 0 */  
};  

信號量集的結構圖如下所示:

消息隊列

消息隊列,是消息的鏈接表,存放在內核中。一個消息隊列由一個標識符(即隊列 ID)來標識。其具有以下特點:

  1. 消息隊列是面向記錄的,其中的消息具有特定的格式以及特定的優先級。

  2. 消息隊列獨立於發送與接收進程。進程終止時,消息隊列及其內容並不會被刪除。

  3. 消息隊列可以實現消息的隨機查詢,消息不一定要以先進先出的次序讀取,也可以按消息的類型讀取。

原型

1 #include <sys/msg.h>
2 // 創建或打開消息隊列:成功返回隊列ID,失敗返回-1
3 int msgget(key_t key, int flag);
4 // 添加消息:成功返回0,失敗返回-1
5 int msgsnd(int msqid, const void *ptr, size_t size, int flag);
6 // 讀取消息:成功返回消息數據的長度,失敗返回-1
7 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
8 // 控制消息隊列:成功返回0,失敗返回-1
9 int msgctl(int msqid, int cmd, struct msqid_ds *buf);

在以下兩種情況下,msgget 將創建一個新的消息隊列:

  • 如果沒有與鍵值key相對應的消息隊列,並且flag中包含了IPC_CREAT標誌位。

  • key參數為IPC_PRIVATE

函數msgrcv在讀取消息隊列時,type參數有下面幾種情況:

  • type == 0,返回隊列中的第一個消息;

  • type > 0,返回隊列中消息類型為 type 的第一個消息;

  • type < 0,返回隊列中消息類型值小於或等於 type 絕對值的消息,如果有多個,則取類型值最小的消息。

可以看出,type 值非 0 時用於以非先進先出次序讀消息。也可以把 type 看做優先級的權值。

共享內存

共享內存是 System V 版本的最後一個進程間通信方式。共享內存,顧名思義就是允許兩個不相關的進程訪問同一個邏輯內存,共享內存是兩個正在運行的進程之間共享和傳遞數據的一種非常有效的方式。不同進程之間共享的內存通常為同一段物理內存。進程可以將同一段物理內存連接到他們自己的地址空間中,所有的進程都可以訪問共享內存中的地址。如果某個進程向共享內存寫入數據,所做的改動將立即影響到可以訪問同一段共享內存的任何其他進程。

特別提醒:共享內存並未提供同步機制,也就是說,在第一個進程結束對共享內存的寫操作之前,並無自動機制可以阻止第二個進程開始對它進行讀取,所以我們通常需要用其他的機制來同步對共享內存的訪問,例如信號量。

共享內存的通信原理
在 Linux 中,每個進程都有屬於自己的進程控制塊(PCB)和地址空間(Addr Space),並且都有一個與之對應的頁表,負責將進程的虛擬地址與物理地址進行映射,通過內存管理單元(MMU)進行管理。兩個不同的虛擬地址通過頁表映射到物理空間的同一區域,它們所指向的這塊區域即共享內存。

共享內存的通信原理示意圖:

 

對於上圖我的理解是:當兩個進程通過頁表將虛擬地址映射到物理地址時,在物理地址中有一塊共同的內存區,即共享內存,這塊內存可以被兩個進程同時看到。這樣當一個進程進行寫操作,另一個進程讀操作就可以實現進程間通信。但是,我們要確保一個進程在寫的時候不能被讀,因此我們使用信號量來實現同步與互斥。

對於一個共享內存,實現採用的是引用計數的原理,當進程脫離共享存儲區后,計數器減一,掛架成功時,計數器加一,只有當計數器變為零時,才能被刪除。當進程終止時,它所附加的共享存儲區都會自動脫離。

為什麼共享內存速度最快?

藉助上圖說明:Proc A 進程給內存中寫數據, Proc B 進程從內存中讀取數據,在此期間一共發生了兩次複製

(1)Proc A 到共享內存       (2)共享內存到 Proc B

因為直接在內存上操作,所以共享內存的速度也就提高了。

共享內存的接口函數以及指令

查看系統中的共享存儲段

ipcs -m

刪除系統中的共享存儲段

ipcrm -m [shmid]

shmget ( ):創建共享內存

int shmget(key_t key, size_t size, int shmflg);

[參數key]:由ftok生成的key標識,標識系統的唯一IPC資源。

[參數size]:需要申請共享內存的大小。在操作系統中,申請內存的最小單位為頁,一頁是4k字節,為了避免內存碎片,我們一般申請的內存大小為頁的整數倍。

[參數shmflg]:如果要創建新的共享內存,需要使用IPC_CREAT,IPC_EXCL,如果是已經存在的,可以使用IPC_CREAT或直接傳0。

[返回值]:成功時返回一個新建或已經存在的的共享內存標識符,取決於shmflg的參數。失敗返回-1並設置錯誤碼。

shmat ( ):掛接共享內存

void *shmat(int shmid, const void *shmaddr, int shmflg);

[參數shmid]:共享存儲段的標識符。

[參數*shmaddr]:shmaddr = 0,則存儲段連接到由內核選擇的第一個可以地址上(推薦使用)。

[參數shmflg]:若指定了SHM_RDONLY位,則以只讀方式連接此段,否則以讀寫方式連接此段。

[返回值]:成功返回共享存儲段的指針(虛擬地址),並且內核將使其與該共享存儲段相關的shmid_ds結構中的shm_nattch計數器加1(類似於引用計數);出錯返回-1。

shmdt ( ):去關聯共享內存:當一個進程不需要共享內存的時候,就需要去關聯。該函數並不刪除所指定的共享內存區,而是將之前用shmat函數連接好的共享內存區脫離目前的進程。

int shmdt(const void *shmaddr);

[參數*shmaddr]:連接以後返回的地址。

[返回值]:成功返回0,並將shmid_ds結構體中的 shm_nattch計數器減1;出錯返回-1。

shmctl ( ):銷毀共享內存

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

[參數shmid]:共享存儲段標識符。

[參數cmd]:指定的執行操作,設置為IPC_RMID時表示可以刪除共享內存。

[參數*buf]:設置為NULL即可。

[返回值]:成功返回0,失敗返回-1。

POSIX 消息隊列   

POSIX 消息隊列是 POSIX 標準在 2001 年定義的一種 IPC 機制,與 System V 中的消息隊列相比有如下差異:

  • 更簡單的基於文件的應用接口,Linux 通過 mqueue 的特殊文件系統來實現消息隊列,隊列名跟文件名類似,必須以”/”開頭,每個消息隊列在文件系統內都有一個對應的索引節點,返回的隊列描述符實際是一個文件描述符

  • 完全支持消息優先級,消息在隊列中是按照優先級倒序排列的(即0表示優先級最低)。當一條消息被添加到隊列中時,它會被放置在隊列中具有相同優先級的所有消息之後。如果一個應用程序無需使用消息優先級,那麼只需要將msg_prio指定為0即可。

  • 完全支持消息到達的異步通知,當新消息到達且當前隊列為空時會通知之前註冊過表示接受通知的進程。在任何一個時刻都只有一個進程能夠向一個特定的消息隊列註冊接收通知。如果一個消息隊列上已經存在註冊進程了,那麼後續在該隊列上的註冊請求將會失敗。可以給進程發送信號或者另起一個線程調用通知函數完成通知。當通知完成時,註冊即被撤銷,進程需要繼續接受通知則必須重新註冊。

  • 用於阻塞發送與接收操作的超時機制,可以指定阻塞的最長時間,超時自動返回 

套接字:

套接字是更為基礎的進程間通信機制,與其他方式不同的是,套接字可用於不同機器之間的進程間通信。

有兩種類型的套接字:基於文件的和面向網絡的。

  • Unix 套接字是基於文件的,並且擁有一個“家族名字”–AF_UNIX,它代表地址家族 (address family):UNIX。

  • 第二類型的套接字是基於網絡的,它也有自己的家族名字–AF_INET,代表地址家族 (address family):INTERNET

不管採用哪種地址家族,都有兩種不同的套接字連接:面向連接的和無連接的。

  • 面向連接的套接字 (SOCK_STREAM):進行通信前必須建立一個連接,面向連接的通信提供序列化的、可靠地和不重複的數據交付,而沒有記錄邊界。

這意味着每條信息可以被拆分成多個片段,並且每個片段都能確保到達目的地,然後在目的地將信息拼接起來。

實現這種連接類型的主要協議是傳輸控制協議 (TCP)。

  • 無連接的套接字 (SOCK_DGRAM):在通信開始之前並不需要建立連接,在數據傳輸過程中並無法保證它的順序性、可靠性或重複性。

然而,數據報確實保存了記錄邊界,這就意味着消息是以整體發送的,而並非首先分成多個片段。

由於面向連接的套接字所提供的保證,因此它們的設置以及對虛擬電路連接的維護需要大量的開銷。然而,數據報不需要這些開銷,即它的成本更加“低廉”

實現這種連接類型的主要協議是用戶數據報協議 (UDP)。

信號

信號是軟件層次上對中斷機制的一種模擬,是一種異步通信方式,進程不必通過任何操作來等待信號的到達。信號可以在用戶空間進程和內核之間直接交互,內核可以利用信號來通知用戶空間的進程發生了哪些系統事件。

信號來源:

信號事件的發生有兩個來源:硬件來源,比如我們按下了鍵盤或者其它硬件故障;軟件來源,最常用發送信號的系統函數是 kill, raise, alarm 和 setitimer 以及 sigqueue 函數,軟件來源還包括一些非法運算等操作。

進程對信號的響應:

進程可以通過三種方式來響應信號:

  • 忽略信號,即對信號不做任何處理,但是有兩個信號是不能忽略的:SIGKLL 和 SIGSTOP;

  • 捕捉信號,定義信號處理函數,當信號發生時,執行相應的處理函數;

  • 執行缺省操作,Linux 對每種信號都規定了默認操作。

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

內容概要:

單一數據讀取方式:

  第一種:slice_input_producer()

# 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
[images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

  第二種:string_input_producer()

# 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

reader = tf.WholeFileReader() # 定義文件讀取器
key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

  !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

  !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

  !!!以上兩種方法都可以生成文件名隊列。

(隨機)批量數據讀取方式:

batchsize=2  # 每次讀取的樣本數量
tf.train.batch(tensors, batch_size=batchsize)
tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

  !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

 TFRecord文件的打包與讀取

 

 一、單一數據讀取方式

第一種:slice_input_producer()

def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

案例1:

import tensorflow as tf

images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
labels = [1, 2, 3, 4]

# [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

# 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
# [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)

data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
print(type(data))   # <class 'list'>

with tf.Session() as sess:
    # sess.run(tf.local_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()  # 線程的協調器
    threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器

    for i in range(10):
        print(sess.run(data))

    coord.request_stop()
    coord.join(threads)

"""
運行結果:
[b'image2.jpg', 2]
[b'image1.jpg', 1]
[b'image3.jpg', 3]
[b'image4.jpg', 4]
[b'image2.jpg', 2]
[b'image1.jpg', 1]
[b'image3.jpg', 3]
[b'image4.jpg', 4]
[b'image2.jpg', 2]
[b'image3.jpg', 3]
"""

  !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

  !!!num_epochs設置

 

 第二種:string_input_producer()

def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

文件讀取器

  不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

  該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

 案例2:讀取csv文件

iimport tensorflow as tf

filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']

file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
# reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
print(type(file_queue))

init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
with tf.Session() as sess:
    sess.run(init)
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    try:
        while not coord.should_stop():
            for i in range(6):
                print(sess.run([key, value]))
            break
    except tf.errors.OutOfRangeError:
        print('read done')
    finally:
        coord.request_stop()
    coord.join(threads)

"""
reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
運行結果:
[b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
[b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
[b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
[b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
[b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
[b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
"""
"""
reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
運行結果:
[b'data/B.csv:1', b'4.jpg,4']
[b'data/B.csv:2', b'5.jpg,5']
[b'data/B.csv:3', b'6.jpg,6']
[b'data/C.csv:1', b'7.jpg,7']
[b'data/C.csv:2', b'8.jpg,8']
[b'data/C.csv:3', b'9.jpg,9']
"""

案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

import tensorflow as tf

filename = ['1.jpg', '2.jpg']
filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
reader = tf.WholeFileReader()              # 文件讀取器
key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes

with tf.Session() as sess:
    tf.local_variables_initializer().run()
    coord = tf.train.Coordinator()      # 線程的協調器
    threads = tf.train.start_queue_runners(sess, coord)

    for i in range(filename.__len__()):
        image_data = sess.run(value)
        with open('img_%d.jpg' % i, 'wb') as f:
            f.write(image_data)
    coord.request_stop()
    coord.join(threads)

 

 二、(隨機)批量數據讀取方式:

  功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

案例4:slice_input_producer() 與 batch()

import tensorflow as tf
import numpy as np

images = np.arange(20).reshape([10, 2])
label = np.asarray(range(0, 10))
images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果

batchsize = 6   # 每次獲取元素的數量
input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)

# 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
# image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)

with tf.Session() as sess:
    coord = tf.train.Coordinator()      # 線程的協調器
    threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    for cnt in range(2):
        print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
        image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
        print(image_batch_v, label_batch_v, label_batch_v.__len__())

    coord.request_stop()
    coord.join(threads)

"""
運行結果:
第1次獲取數據,每次batch=6...
[[ 0.  1.]
 [ 2.  3.]
 [ 4.  5.]
 [ 6.  7.]
 [ 8.  9.]
 [10. 11.]] [0 1 2 3 4 5] 6
第2次獲取數據,每次batch=6...
[[12. 13.]
 [14. 15.]
 [16. 17.]
 [18. 19.]
 [ 0.  1.]
 [ 2.  3.]] [6 7 8 9 0 1] 6
"""

 案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

 1 import tensorflow as tf
 2 import glob
 3 import cv2 as cv
 4 
 5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
 6     """
 7     從本地批量的讀取圖片
 8     :param filename: 圖片路徑(包括圖片的文件名),[]
 9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
12     :return: batch_size張圖片數據, Tensor
13     """
14     global new_img
15     # 創建文件隊列
16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
17     # 創建文件讀取器
18     reader = tf.WholeFileReader()
19     # 讀取文件隊列中的文件
20     _, img_bytes = reader.read(file_queue)
21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
22     # 對圖片進行解碼
23     if picture_format == ".bmp":
24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
25     elif picture_format == ".jpg":
26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
27     else:
28         pass
29     # 重新設置圖片的大小
30     # new_img = tf.image.resize_images(new_img, input_image_shape)
31     new_img = tf.reshape(new_img, input_image_shape)
32     # 設置圖片的數據類型
33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
34 
35     # return new_img
36     return tf.train.batch([new_img], batch_size)
37 
38 
39 def main():
40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
42     print(type(image_batch))
43     # image_path = glob.glob(r'.\*.jpg')
44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
45 
46     sess = tf.Session()
47     sess.run(tf.local_variables_initializer())
48     tf.train.start_queue_runners(sess=sess)
49 
50     image_batch = sess.run(image_batch)
51     print(type(image_batch))    # <class 'numpy.ndarray'>
52 
53     for i in range(image_batch.__len__()):
54         cv.imshow("win_"+str(i), image_batch[i])
55     cv.waitKey()
56     cv.destroyAllWindows()
57 
58 def start():
59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
62 
63 
64     with tf.Session() as sess:
65         sess.run(tf.local_variables_initializer())
66         coord = tf.train.Coordinator()      # 線程的協調器
67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
68         image_batch = sess.run(image_batch)
69         print(type(image_batch))    # <class 'numpy.ndarray'>
70 
71         for i in range(image_batch.__len__()):
72             cv.imshow("win_"+str(i), image_batch[i])
73         cv.waitKey()
74         cv.destroyAllWindows()
75 
76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
79         coord.request_stop()
80         coord.join(threads)
81 
82 
83 if __name__ == "__main__":
84     # main()
85     start()

從本地批量的讀取圖片案例

 

案列6:TFRecord文件打包與讀取

 1 def write_TFRecord(filename, data, labels, is_shuffler=True):
 2     """
 3     將數據打包成TFRecord格式
 4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
 5     :param data: 需要打包的文件路徑名;list
 6     :param labels: 對應文件的標籤;list
 7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
 8     :return: None
 9     """
10     im_data = list(data)
11     im_labels = list(labels)
12 
13     index = [i for i in range(im_data.__len__())]
14     if is_shuffler:
15         np.random.shuffle(index)
16 
17     # 創建寫入器,然後使用該對象寫入樣本example
18     writer = tf.python_io.TFRecordWriter(filename)
19     for i in range(im_data.__len__()):
20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
22 
23         # # 獲取當前的圖片數據  方式一:
24         # data = cv2.imread(im_d)
25         # # 創建樣本
26         # ex = tf.train.Example(
27         #     features=tf.train.Features(
28         #         feature={
29         #             "image": tf.train.Feature(
30         #                 bytes_list=tf.train.BytesList(
31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
32         #             "label": tf.train.Feature(
33         #                 int64_list=tf.train.Int64List(
34         #                     value=[im_l])),
35         #         }
36         #     )
37         # )
38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
39         data = tf.gfile.FastGFile(im_d, "rb").read()
40         ex = tf.train.Example(
41             features=tf.train.Features(
42                 feature={
43                     "image": tf.train.Feature(
44                         bytes_list=tf.train.BytesList(
45                             value=[data])), # 此時的data已經是bytes類型
46                     "label": tf.train.Feature(
47                         int64_list=tf.train.Int64List(
48                             value=[im_l])),
49                 }
50             )
51         )
52 
53         # 寫入將序列化之後的樣本
54         writer.write(ex.SerializeToString())
55     # 關閉寫入器
56     writer.close()

TFRecord文件打包案列

 

 1 import tensorflow as tf
 2 import cv2
 3 
 4 def read_TFRecord(file_list, batch_size=10):
 5     """
 6     讀取TFRecord文件
 7     :param file_list: 存放TFRecord的文件名,List
 8     :param batch_size: 每次讀取圖片的數量
 9     :return: 解析後圖片及對應的標籤
10     """
11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
12     reader = tf.TFRecordReader()
13     _, ex = reader.read(file_queue)
14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
15 
16     feature = {
17         'image': tf.FixedLenFeature([], tf.string),
18         'label': tf.FixedLenFeature([], tf.int64)
19     }
20     example = tf.parse_example(batch, features=feature)
21 
22     images = tf.decode_raw(example['image'], tf.uint8)
23     images = tf.reshape(images, [-1, 32, 32, 3])
24 
25     return images, example['label']
26 
27 
28 
29 def main():
30     # filelist = ['data/train.tfrecord']
31     filelist = ['data/test.tfrecord']
32     images, labels = read_TFRecord(filelist, 2)
33     with tf.Session() as sess:
34         sess.run(tf.local_variables_initializer())
35         coord = tf.train.Coordinator()
36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
37 
38         try:
39             while not coord.should_stop():
40                 for i in range(1):
41                     image_bth, _ = sess.run([images, labels])
42                     print(_)
43 
44                     cv2.imshow("image_0", image_bth[0])
45                     cv2.imshow("image_1", image_bth[1])
46                 break
47         except tf.errors.OutOfRangeError:
48             print('read done')
49         finally:
50             coord.request_stop()
51         coord.join(threads)
52         cv2.waitKey(0)
53         cv2.destroyAllWindows()
54 
55 if __name__ == "__main__":
56     main()

TFReord文件的讀取案列

 

,

內容概要:

單一數據讀取方式:

  第一種:slice_input_producer()

# 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
[images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

  第二種:string_input_producer()

# 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

reader = tf.WholeFileReader() # 定義文件讀取器
key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

  !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

  !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

  !!!以上兩種方法都可以生成文件名隊列。

(隨機)批量數據讀取方式:

batchsize=2  # 每次讀取的樣本數量
tf.train.batch(tensors, batch_size=batchsize)
tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

  !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

 TFRecord文件的打包與讀取

 

 一、單一數據讀取方式

第一種:slice_input_producer()

def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

案例1:

import tensorflow as tf

images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
labels = [1, 2, 3, 4]

# [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

# 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
# [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)

data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
print(type(data))   # <class 'list'>

with tf.Session() as sess:
    # sess.run(tf.local_variables_initializer())
    sess.run(tf.local_variables_initializer())
    coord = tf.train.Coordinator()  # 線程的協調器
    threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器

    for i in range(10):
        print(sess.run(data))

    coord.request_stop()
    coord.join(threads)

"""
運行結果:
[b'image2.jpg', 2]
[b'image1.jpg', 1]
[b'image3.jpg', 3]
[b'image4.jpg', 4]
[b'image2.jpg', 2]
[b'image1.jpg', 1]
[b'image3.jpg', 3]
[b'image4.jpg', 4]
[b'image2.jpg', 2]
[b'image3.jpg', 3]
"""

  !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

  !!!num_epochs設置

 

 第二種:string_input_producer()

def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

文件讀取器

  不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

  該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

 案例2:讀取csv文件

iimport tensorflow as tf

filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']

file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
# reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
print(type(file_queue))

init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
with tf.Session() as sess:
    sess.run(init)
    coord = tf.train.Coordinator()
    threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    try:
        while not coord.should_stop():
            for i in range(6):
                print(sess.run([key, value]))
            break
    except tf.errors.OutOfRangeError:
        print('read done')
    finally:
        coord.request_stop()
    coord.join(threads)

"""
reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
運行結果:
[b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
[b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
[b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
[b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
[b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
[b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
"""
"""
reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
運行結果:
[b'data/B.csv:1', b'4.jpg,4']
[b'data/B.csv:2', b'5.jpg,5']
[b'data/B.csv:3', b'6.jpg,6']
[b'data/C.csv:1', b'7.jpg,7']
[b'data/C.csv:2', b'8.jpg,8']
[b'data/C.csv:3', b'9.jpg,9']
"""

案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

import tensorflow as tf

filename = ['1.jpg', '2.jpg']
filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
reader = tf.WholeFileReader()              # 文件讀取器
key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes

with tf.Session() as sess:
    tf.local_variables_initializer().run()
    coord = tf.train.Coordinator()      # 線程的協調器
    threads = tf.train.start_queue_runners(sess, coord)

    for i in range(filename.__len__()):
        image_data = sess.run(value)
        with open('img_%d.jpg' % i, 'wb') as f:
            f.write(image_data)
    coord.request_stop()
    coord.join(threads)

 

 二、(隨機)批量數據讀取方式:

  功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

案例4:slice_input_producer() 與 batch()

import tensorflow as tf
import numpy as np

images = np.arange(20).reshape([10, 2])
label = np.asarray(range(0, 10))
images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果

batchsize = 6   # 每次獲取元素的數量
input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)

# 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
# image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)

with tf.Session() as sess:
    coord = tf.train.Coordinator()      # 線程的協調器
    threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    for cnt in range(2):
        print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
        image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
        print(image_batch_v, label_batch_v, label_batch_v.__len__())

    coord.request_stop()
    coord.join(threads)

"""
運行結果:
第1次獲取數據,每次batch=6...
[[ 0.  1.]
 [ 2.  3.]
 [ 4.  5.]
 [ 6.  7.]
 [ 8.  9.]
 [10. 11.]] [0 1 2 3 4 5] 6
第2次獲取數據,每次batch=6...
[[12. 13.]
 [14. 15.]
 [16. 17.]
 [18. 19.]
 [ 0.  1.]
 [ 2.  3.]] [6 7 8 9 0 1] 6
"""

 案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

 1 import tensorflow as tf
 2 import glob
 3 import cv2 as cv
 4 
 5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
 6     """
 7     從本地批量的讀取圖片
 8     :param filename: 圖片路徑(包括圖片的文件名),[]
 9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
12     :return: batch_size張圖片數據, Tensor
13     """
14     global new_img
15     # 創建文件隊列
16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
17     # 創建文件讀取器
18     reader = tf.WholeFileReader()
19     # 讀取文件隊列中的文件
20     _, img_bytes = reader.read(file_queue)
21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
22     # 對圖片進行解碼
23     if picture_format == ".bmp":
24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
25     elif picture_format == ".jpg":
26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
27     else:
28         pass
29     # 重新設置圖片的大小
30     # new_img = tf.image.resize_images(new_img, input_image_shape)
31     new_img = tf.reshape(new_img, input_image_shape)
32     # 設置圖片的數據類型
33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
34 
35     # return new_img
36     return tf.train.batch([new_img], batch_size)
37 
38 
39 def main():
40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
42     print(type(image_batch))
43     # image_path = glob.glob(r'.\*.jpg')
44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
45 
46     sess = tf.Session()
47     sess.run(tf.local_variables_initializer())
48     tf.train.start_queue_runners(sess=sess)
49 
50     image_batch = sess.run(image_batch)
51     print(type(image_batch))    # <class 'numpy.ndarray'>
52 
53     for i in range(image_batch.__len__()):
54         cv.imshow("win_"+str(i), image_batch[i])
55     cv.waitKey()
56     cv.destroyAllWindows()
57 
58 def start():
59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
62 
63 
64     with tf.Session() as sess:
65         sess.run(tf.local_variables_initializer())
66         coord = tf.train.Coordinator()      # 線程的協調器
67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
68         image_batch = sess.run(image_batch)
69         print(type(image_batch))    # <class 'numpy.ndarray'>
70 
71         for i in range(image_batch.__len__()):
72             cv.imshow("win_"+str(i), image_batch[i])
73         cv.waitKey()
74         cv.destroyAllWindows()
75 
76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
79         coord.request_stop()
80         coord.join(threads)
81 
82 
83 if __name__ == "__main__":
84     # main()
85     start()

從本地批量的讀取圖片案例

 

案列6:TFRecord文件打包與讀取

 1 def write_TFRecord(filename, data, labels, is_shuffler=True):
 2     """
 3     將數據打包成TFRecord格式
 4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
 5     :param data: 需要打包的文件路徑名;list
 6     :param labels: 對應文件的標籤;list
 7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
 8     :return: None
 9     """
10     im_data = list(data)
11     im_labels = list(labels)
12 
13     index = [i for i in range(im_data.__len__())]
14     if is_shuffler:
15         np.random.shuffle(index)
16 
17     # 創建寫入器,然後使用該對象寫入樣本example
18     writer = tf.python_io.TFRecordWriter(filename)
19     for i in range(im_data.__len__()):
20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
22 
23         # # 獲取當前的圖片數據  方式一:
24         # data = cv2.imread(im_d)
25         # # 創建樣本
26         # ex = tf.train.Example(
27         #     features=tf.train.Features(
28         #         feature={
29         #             "image": tf.train.Feature(
30         #                 bytes_list=tf.train.BytesList(
31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
32         #             "label": tf.train.Feature(
33         #                 int64_list=tf.train.Int64List(
34         #                     value=[im_l])),
35         #         }
36         #     )
37         # )
38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
39         data = tf.gfile.FastGFile(im_d, "rb").read()
40         ex = tf.train.Example(
41             features=tf.train.Features(
42                 feature={
43                     "image": tf.train.Feature(
44                         bytes_list=tf.train.BytesList(
45                             value=[data])), # 此時的data已經是bytes類型
46                     "label": tf.train.Feature(
47                         int64_list=tf.train.Int64List(
48                             value=[im_l])),
49                 }
50             )
51         )
52 
53         # 寫入將序列化之後的樣本
54         writer.write(ex.SerializeToString())
55     # 關閉寫入器
56     writer.close()

TFRecord文件打包案列

 

 1 import tensorflow as tf
 2 import cv2
 3 
 4 def read_TFRecord(file_list, batch_size=10):
 5     """
 6     讀取TFRecord文件
 7     :param file_list: 存放TFRecord的文件名,List
 8     :param batch_size: 每次讀取圖片的數量
 9     :return: 解析後圖片及對應的標籤
10     """
11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
12     reader = tf.TFRecordReader()
13     _, ex = reader.read(file_queue)
14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
15 
16     feature = {
17         'image': tf.FixedLenFeature([], tf.string),
18         'label': tf.FixedLenFeature([], tf.int64)
19     }
20     example = tf.parse_example(batch, features=feature)
21 
22     images = tf.decode_raw(example['image'], tf.uint8)
23     images = tf.reshape(images, [-1, 32, 32, 3])
24 
25     return images, example['label']
26 
27 
28 
29 def main():
30     # filelist = ['data/train.tfrecord']
31     filelist = ['data/test.tfrecord']
32     images, labels = read_TFRecord(filelist, 2)
33     with tf.Session() as sess:
34         sess.run(tf.local_variables_initializer())
35         coord = tf.train.Coordinator()
36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
37 
38         try:
39             while not coord.should_stop():
40                 for i in range(1):
41                     image_bth, _ = sess.run([images, labels])
42                     print(_)
43 
44                     cv2.imshow("image_0", image_bth[0])
45                     cv2.imshow("image_1", image_bth[1])
46                 break
47         except tf.errors.OutOfRangeError:
48             print('read done')
49         finally:
50             coord.request_stop()
51         coord.join(threads)
52         cv2.waitKey(0)
53         cv2.destroyAllWindows()
54 
55 if __name__ == "__main__":
56     main()

TFReord文件的讀取案列

 

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

【其他文章推薦】

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

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

10.DRF-認證租車,如何寫文案,貨運,銷售文案,FB行銷

Django rest framework源碼分析(1)—-認證

一、基礎

1.1.安裝

兩種方式:

  • github
  • pip直接安裝
pip install djangorestframework

1.2.需要先了解的一些知識

理解下面兩個知識點非常重要,django-rest-framework源碼中到處都是基於CBV和面向對象的封裝

(1)面向對象封裝的兩大特性

把同一類方法封裝到類中

將數據封裝到對象中

(2)CBV

基於反射實現根據請求方式不同,執行不同的方法

原理:url–>view方法–>dispatch方法(反射執行其它方法:GET/POST/PUT/DELETE等等)

二、簡單實例

2.1.settings

先創建一個project和一個app(我這裏命名為API)

首先要在settings的app中添加

INSTALLED_APPS = [
    'rest_framework',
]

2.2.url

from django.contrib import admin
from django.urls import path
from API.views import AuthView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('api/v1/auth/',AuthView.as_view()),
]

2.3.models

一個保存用戶的信息

一個保存用戶登錄成功后的token

from django.db import models

class UserInfo(models.Model):
    USER_TYPE = (
        (1,'普通用戶'),
        (2,'VIP'),
        (3,'SVIP')
    )

    user_type = models.IntegerField(choices=USER_TYPE)
    username = models.CharField(max_length=32)
    password = models.CharField(max_length=64)

class UserToken(models.Model):
    user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
    token = models.CharField(max_length=64)

2.4.views

用戶登錄(返回token並保存到數據庫)

from django.shortcuts import render
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()

class AuthView(object):
    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)

2.5.利用postman發請求

如果用戶名和密碼正確的話 會生成token值,下次該用戶再登錄時,token的值就會更新

數據庫中可以看到token的值

當用戶名或密碼錯誤時,拋出異常

三、添加認證

基於上面的例子,添加一個認證的類

3.1.url

path('api/v1/order/',OrderView.as_view()),

3.2.views

from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()

class AuthView(object):
    '''用於用戶登錄驗證'''
    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)


class Authentication(APIView):
    '''認證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass

class OrderView(APIView):
    '''訂單相關業務'''

    authentication_classes = [Authentication,]    #添加認證
    def get(self,request,*args,**kwargs):
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)

3.3用postman發get請求

請求的時候沒有帶token,可以看到會显示“用戶認證失敗”

這樣就達到了認證的效果,django-rest-framework的認證是怎麼實現的呢,下面基於這個例子來剖析drf的源碼。

四、drf的認證源碼分析

源碼流程圖

請求先到dispatch

dispatch()主要做了兩件事

  • 封裝request
  • 認證  

具體看我寫的代碼裏面的註釋

def dispatch(self, request, *args, **kwargs):
	"""
	`.dispatch()` is pretty much the same as Django's regular dispatch,
	but with extra hooks for startup, finalize, and exception handling.
	 """
	self.args = args
    self.kwargs = kwargs
    #對原始request進行加工,豐富了一些功能
    #Request(
    #     request,
    #     parsers=self.get_parsers(),
    #     authenticators=self.get_authenticators(),
    #     negotiator=self.get_content_negotiator(),
    #     parser_context=parser_context
    # )
    #request(原始request,[BasicAuthentications對象,])
    #獲取原生request,request._request
    #獲取認證類的對象,request.authticators
    #1.封裝request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        #2.認證
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

	except Exception as exc:
        response = self.handle_exception(exc)

	self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

4.1.reuqest

(1)initialize_request()

可以看到initialize()就是封裝原始request

def initialize_request(self, request, *args, **kwargs):
    """
    Returns the initial request object.
    """
    parser_context = self.get_parser_context(request)

    return Request(
        request,
        parsers=self.get_parsers(),
        #[BasicAuthentication(),],把對象封裝到request裏面了
        authenticators=self.get_authenticators(),    
        negotiator=self.get_content_negotiator(), parser_context=parser_context )

(2)get_authenticators()

通過列表生成式,返回對象的列表

def get_authenticators(self):
    """
    Instantiates and returns the list of authenticators that this view can use.
    """
    return [auth() for auth in self.authentication_classes]

(3)authentication_classes

APIView裏面有個 authentication_classes 字段

可以看到默認是去全局的配置文件找(api_settings)

class APIView(View):

    # The following policies may be set at either globally, or per-view.
    renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
    parser_classes = api_settings.DEFAULT_PARSER_CLASSES
    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
    throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
    permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
    content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
    metadata_class = api_settings.DEFAULT_METADATA_CLASS
    versioning_class = api_settings.DEFAULT_VERSIONING_CLASS

4.2.認證

self.initial(request, *args, **kwargs)

def dispatch(self, request, *args, **kwargs):
    """
    `.dispatch()` is pretty much the same as Django's regular dispatch,
    but with extra hooks for startup, finalize, and exception handling.
    """
    self.args = args
    self.kwargs = kwargs
    #對原始request進行加工,豐富了一些功能
    #Request(
    #     request,
    #     parsers=self.get_parsers(),
    #     authenticators=self.get_authenticators(),
    #     negotiator=self.get_content_negotiator(),
    #     parser_context=parser_context
    # )
    #request(原始request,[BasicAuthentications對象,])
    #獲取原生request,request._request
    #獲取認證類的對象,request.authticators
    #1.封裝request
    request = self.initialize_request(request, *args, **kwargs)
    self.request = request
    self.headers = self.default_response_headers  # deprecate?

    try:
        #2.認證
        self.initial(request, *args, **kwargs)

        # Get the appropriate handler method
        if request.method.lower() in self.http_method_names:
            handler = getattr(self, request.method.lower(),
                                  self.http_method_not_allowed)
        else:
            handler = self.http_method_not_allowed

        response = handler(request, *args, **kwargs)

	except Exception as exc:
        response = self.handle_exception(exc)

	self.response = self.finalize_response(request, response, *args, **kwargs)
    return self.response

(1)initial()

主要看 self.perform_authentication(request),實現認證

def initial(self, request, *args, **kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer, request.accepted_media_type = neg

    # Determine the API version, if versioning is in use.
    version, scheme = self.determine_version(request, *args, **kwargs)
    request.version, request.versioning_scheme = version, scheme

    # Ensure that the incoming request is permitted
    #3.實現認證
    self.perform_authentication(request)
    self.check_permissions(request)
    self.check_throttles(request)

(2)perform_authentication()

調用了request.user

def perform_authentication(self, request):
    """
    Perform authentication on the incoming request.

	Note that if you override this and simply 'pass', then authentication
	will instead be performed lazily, the first time either
	`request.user` or `request.auth` is accessed.
	"""
    request.user

(3)user

request.user的request的位置

點進去可以看到Request有個user方法,加 @property 表示調用user方法的時候不需要加括號“user()”,可以直接調用:request.user

@property
def user(self):
    """
    Returns the user associated with the current request, as authenticated
    by the authentication classes provided to the request.
    """
    if not hasattr(self, '_user'):
        with wrap_attributeerrors():
            #獲取認證對象,進行一步步的認證
            self._authenticate()
    return self._user

(4)_authenticate()

循環所有authenticator對象

def _authenticate(self):
    """
    Attempt to authenticate the request using each authentication instance
    in turn.
    """
    #循環認證類的所有對象
    #執行對象的authenticate方法
    for authenticator in self.authenticators:
        try:
            #執行認證類的authenticate方法
            #這裏分三種情況
            #1.如果authenticate方法拋出異常,self._not_authenticated()執行
            #2.有返回值,必須是元組:(request.user,request.auth)
            #3.返回None,表示當前認證不處理,等下一個認證來處理
            user_auth_tuple = authenticator.authenticate(self)
        except exceptions.APIException:
            self._not_authenticated()
            raise

        if user_auth_tuple is not None:
            self._authenticator = authenticator
            self.user, self.auth = user_auth_tuple
            return

	self._not_authenticated()

返回值就是例子中的:

token_obj.user-->>request.user
token_obj-->>request.auth
#在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
return (token_obj.user,token_obj)     #例子中的return

當都沒有返回值,就執行self._not_authenticated(),相當於匿名用戶,沒有通過認證

def _not_authenticated(self):
    """
    Set authenticator, user & authtoken representing an unauthenticated request.

	Defaults are None, AnonymousUser & None.
	"""
    self._authenticator = None

    if api_settings.UNAUTHENTICATED_USER:
        self.user = api_settings.UNAUTHENTICATED_USER()   #AnonymousUser匿名用戶
    else:
        self.user = None

	if api_settings.UNAUTHENTICATED_TOKEN:
        self.auth = api_settings.UNAUTHENTICATED_TOKEN()  #None
	else:
        self.auth = None

面向對象知識:

子類繼承 父類,調用方法的時候:

  • 優先去自己裏面找有沒有這個方法,有就執行自己的
  • 只有當自己裏面沒有這個方法的時候才會去父類找

因為authenticate方法我們自己寫,所以當執行authenticate()的時候就是執行我們自己寫的認證

父類中的authenticate方法

def authenticate(self, request):
    return (self.force_user, self.force_token)

我們自己寫的

class Authentication(APIView):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

認證的流程就是上面寫的,弄懂了原理,再寫代碼就更容易理解為什麼了。

4.3.配置文件

繼續解讀源碼

默認是去全局配置文件中找,所以我們應該在settings.py中配置好路徑

api_settings源碼

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

def reload_api_settings(*args, **kwargs):
    setting = kwargs['setting']
    if setting == 'REST_FRAMEWORK':
        api_settings.reload()

setting中‘REST_FRAMEWORK’中找

全局配置方法:

API文件夾下面新建文件夾utils,再新建auth.py文件,裏面寫上認證的類

settings.py

#設置全局認證
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]   #裏面寫你的認證的類的路徑
}

auth.py

# API/utils/auth.py

from rest_framework import exceptions
from API import models


class Authentication(object):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass

在settings裏面設置的全局認證,所有業務都需要經過認證,如果想讓某個不需要認證,只需要在其中添加下面的代碼:

authentication_classes = []    #裏面為空,代表不需要認證
from django.shortcuts import render,HttpResponse
from django.http import JsonResponse
from rest_framework.views import APIView
from API import models
from rest_framework.request import Request
from rest_framework import exceptions
from rest_framework.authentication import BasicAuthentication

ORDER_DICT = {
    1:{
        'name':'apple',
        'price':15
    },
    2:{
        'name':'dog',
        'price':100
    }
}

def md5(user):
    import hashlib
    import time
    #當前時間,相當於生成一個隨機的字符串
    ctime = str(time.time())
    m = hashlib.md5(bytes(user,encoding='utf-8'))
    m.update(bytes(ctime,encoding='utf-8'))
    return m.hexdigest()

class AuthView(APIView):
    '''用於用戶登錄驗證'''

    authentication_classes = []    #裏面為空,代表不需要認證

    def post(self,request,*args,**kwargs):
        ret = {'code':1000,'msg':None}
        try:
            user = request._request.POST.get('username')
            pwd = request._request.POST.get('password')
            obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
            if not obj:
                ret['code'] = 1001
                ret['msg'] = '用戶名或密碼錯誤'
            #為用戶創建token
            token = md5(user)
            #存在就更新,不存在就創建
            models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
            ret['token'] = token
        except Exception as e:
            ret['code'] = 1002
            ret['msg'] = '請求異常'
        return JsonResponse(ret)




class OrderView(APIView):
    '''訂單相關業務'''


    def get(self,request,*args,**kwargs):
        # self.dispatch
        #request.user
        #request.auth
        ret = {'code':1000,'msg':None,'data':None}
        try:
            ret['data'] = ORDER_DICT
        except Exception as e:
            pass
        return JsonResponse(ret)

API/view.py代碼

再測試一下我們的代碼

不帶token發請求

帶token發請求

五、drf的內置認證

rest_framework裏面內置了一些認證,我們自己寫的認證類都要繼承內置認證類 “BaseAuthentication”

4.1.BaseAuthentication源碼:

class BaseAuthentication(object):
    """
    All authentication classes should extend BaseAuthentication.
    """

    def authenticate(self, request):
        """
        Authenticate the request and return a two-tuple of (user, token).
        """
        #內置的認證類,authenticate方法,如果不自己寫,默認則拋出異常
        raise NotImplementedError(".authenticate() must be overridden.")

    def authenticate_header(self, request):
        """
        Return a string to be used as the value of the `WWW-Authenticate`
        header in a `401 Unauthenticated` response, or `None` if the
        authentication scheme should return `403 Permission Denied` responses.
        """
        #authenticate_header方法,作用是當認證失敗的時候,返回的響應頭
        pass

4.2.修改自己寫的認證類

自己寫的Authentication必須繼承內置認證類BaseAuthentication

# API/utils/auth/py

from rest_framework import exceptions
from API import models
from rest_framework.authentication import BaseAuthentication


class Authentication(BaseAuthentication):
    '''用於用戶登錄驗證'''
    def authenticate(self,request):
        token = request._request.GET.get('token')
        token_obj = models.UserToken.objects.filter(token=token).first()
        if not token_obj:
            raise exceptions.AuthenticationFailed('用戶認證失敗')
        #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
        return (token_obj.user,token_obj)

    def authenticate_header(self, request):
        pass

4.3.其它內置認證類

rest_framework裏面還內置了其它認證類,我們主要用到的就是BaseAuthentication,剩下的很少用到

六、總結

自己寫認證類方法梳理

(1)創建認證類

  • 繼承BaseAuthentication —>>1.重寫authenticate方法;2.authenticate_header方法直接寫pass就可以(這個方法必須寫)

(2)authenticate()返回值(三種)

  • None —–>>>當前認證不管,等下一個認證來執行
  • raise exceptions.AuthenticationFailed(‘用戶認證失敗’) # from rest_framework import exceptions
  • 有返回值元祖形式:(元素1,元素2) #元素1複製給request.user; 元素2複製給request.auth

(3)局部使用

  • authentication_classes = [BaseAuthentication,]

(4)全局使用

#設置全局認證
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
}

源碼流程

—>>dispatch

    –封裝request

       —獲取定義的認證類(全局/局部),通過列表生成式創建對象 

     —initial

       —-peform_authentication

         —–request.user (每部循環創建的對象)

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

.NET進行客戶端Web開發又一利器 – Ant Design Blazor

你好,我是Dotnet9,繼上篇介紹Bootstrap風格的BlazorUI組件庫后,今天我來介紹另一款Blazor UI組件庫:一套基於 Ant Design 和 Blazor 的企業級組件庫。

本文導航:

  • 一、關於Ant Design Blazor
  • 二、Ant Design Blazor的社區貢獻
    • 2.1 項目關注度
    • 2.2 Ant Design官方認可
    • 2.3 微軟官方認可
  • 三、Ant Design Blazor UI庫介紹
  • 四、Ant Design Blazor後續計劃
  • 五、Ant Design Blazor技術交流

一、關於Ant Design Blazor

項目名稱:Ant Design Blazor

項目作者:James Yeung(社區發起者,目前項目參与度高,有較多貢獻者)

開源許可協議:MIT

項目地址:https://github.com/ant-design-blazor/ant-design-blazor

特性

  • 提煉自企業級中後台產品的交互語言和視覺風格。
  • 開箱即用的高質量 Blazor 組件,可在多種託管方式共享。
  • 支持基於 WebAssembly 的客戶端和基於 SignalR 的服務端 UI 事件交互。
  • 支持漸進式 Web 應用(PWA)
  • 使用 C# 構建,多範式靜態語言帶來高效的開發體驗。
  • ️ 基於 .NET Standard 2.1,可直接引用豐富的 .NET 類庫。
  • 可與已有的 ASP.NET Core MVC、Razor Pages 項目無縫集成。

關於開源協議:MIT

參考百度百科

被授權人權利

被授權人有權利使用、複製、修改、合併、出版發行、散布、再授權及販售軟件及軟件的副本。

被授權人可根據程序的需要修改授權條款為適當的內容。

被授權人義務

在軟件和軟件的所有副本中都必須包含版權聲明和許可聲明。

其他重要特性

此授權條款並非屬copyleft的自由軟件授權條款,允許在自由/開放源碼軟件或非自由軟件(proprietary software)所使用。

MIT的內容可依照程序著作權者的需求更改內容。此亦為MIT與BSD(The BSD license, 3-clause BSD license)本質上不同處。

MIT條款可與其他授權條款並存。另外,MIT條款也是自由軟件基金會(FSF)所認可的自由軟件授權條款,與GPL兼容。

二、Ant Design Blazor的社區貢獻

該庫是國內目前社區宣傳度做的最好的一款Blazor UI組件庫,對於Blazor的社區推廣起到很大的作用,Dotnet9是通過該庫作者的一篇文章《如何用 Blazor 實現 Ant Design 組件庫?》開始關注Blazor的,關於該庫作者的心路歷程,大家可點擊原文了解。

距離作者發文已有3月之久,文中作者的部分期望應該說是實現了一個個小目標了,也體現在了對社區的貢獻上(對Blazor推廣作用):

2.1 項目關注度

作者將庫發布在Github上,README支持中英文,日常代碼提交使用英文,讓全球的.Neter參与其中,使得更多的社區成員開始關注Ant Design Blazor,也使得更多的社區成員開始關注Blazor的發展了。

庫作者發文時star統計(2020年03月21日)

3個月後的今天star統計(2020年06月20日)

2.2 Ant Design官方認可

原文作者的小期望:

在為了與官方高度一致上的努力,還會繼續。希望有一天能在豐富 Blazor 生態的同時,還能成為被 Ant Design 生態認可的框架實現,能成為他們 Design 夢的一個延續。

Ant Design官方前端實現介紹鏈接

2.3 微軟官方認可

微軟Build2020開發者大會Blazor介紹中,提及Ant Design Pro。

一圖勝千言,得到微軟認可是對作者最大的獎勵,也是對社區的最好宣傳。

三、Ant Design Blazor UI庫組件介紹

Ant Design Blazor UI組件瀏覽地址:https://ant-design-blazor.github.io/

Ant Design Blazor的開發初衷是盡量與Ant Design組件庫一致,可對比查看:Ant Design

下面只對部分組件截圖介紹,更多組件請戳上面鏈接查看:

3.1 首頁介紹

網站風格和Ant Design官網高度一致,更方便熟悉Ant Design組件的朋友使用。

3.2 組件概覽

組件整體印象,這隻是其中一部分,豐富的組件需要點擊Ant Design Blazor了解更多喲。

四、Ant Design Blazor後續計劃

目前組件開發基本已經完成,可應用於常規項目開發,組件庫後續計劃:

  • 6月底發布0.1版本;
  • 添加測試、完善文檔、企業級應用和反饋;
  • 完成一個開箱即用的模板(偉大目標,像Ant Design Pro靠攏);
  • 添加頁面生成工具,類似UMI添加block,查看Ant Design的區塊介紹。

五、Ant Design Blazor技術交流

  • 微信群
    可添加作者微信號拉你入群:JamesYeungMVP

  • 釘釘群

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

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

分類
發燒車訊

銷量冠軍——吉利亮相振威新能源汽車展

由中國土木工程學會城市公共交通學會、廣東省新能源汽車產業協會、廣東省充電設施協會、充電設施線上網及振威展覽股份聯合舉辦的2017新能源汽車產業博覽會將於6月16-18日,8月23-25日分別在深圳會展中心及上海新國際博覽中心隆重舉行。本次展會涵蓋了整車、核心三電(電池、電機、電控)、充電設備等三大產業板塊及新能源汽車整體解決方案、零部件等全產業鏈展覽展示。是國內以新能源汽車產業為主題全方位展示的第一平臺。

本次展會迎來了一位重量級大咖——吉利汽車。據瞭解,吉利汽車已於近日確認參加本次展會,展位號:E5215、9T220。本次吉利將帶來旗下全款車型隆重亮相。為什麼說吉利是重量級大咖呢?

一、用銷量說話

從2016年4月,吉利推出的第一款純電動汽車—吉利帝豪EV開始,國內整個新能源汽車格局就開始有了變化。從最初的比亞迪、北汽新能源為主的新能源汽車市場,到吉利帝豪EV的加入後,整個銷售市場的格局完全被打破。作為“藍色吉利行動”的首款戰略車型,其超長的續航里程、超低的使用成本、超高的性價比,使帝豪EV在不到9個月的時間裡完成了終端銷售16975台,位元列2016年中國純電動單車型銷量冠軍。

然而,這並不是吉利的最終成績!

根據乘聯會近期公佈的4月新能源汽車銷量資料,吉利新能源旗下的汽車憑藉著6295輛的銷量領跑4月份新能源汽車銷量排行榜。其中吉利帝豪EV300以2586台的銷售成績成為A級純電動轎車的銷量冠軍。此次改款的帝豪EV300不僅升級了續航里程,還增加了ITCS智慧溫控管理系統等配置,並憑藉良好的用戶口碑在純電動轎車領域持續發力。

二、立足使用者需求,以產品力打動客戶

目前純電動車的三大痛點非常明確:一是續航問題,一是充電時長問題,最後一個是針對惡劣天氣的充電問題。而帝豪EV300針對這三大痛點做了全面升級,新車綜合工況里程由之前的253公里增加至300公里,並且在勻速工況下續航里程達360公里。極大的增加了出行半徑及減少了充電的頻率。

對於充電時長,帝豪EV300設置了多種充電模式。首先是升級了6.6KW車載慢充,將慢充時間縮短到7小時,利用晚上峰穀時間充電既能安心充滿,又能節省費用。此外,還添加了60KW公共快充、10KW快充盒、6.6KW慢充樁、3.3KW家用慢充盒、1.8KW隨車應急線充電五種充電模式,極大地提供了充電的便利性,滿足不同客戶的需求。

此外,帝豪EV300還標配了國內首創的ITCS電池智慧溫控管理系統,該系統完美實現了動力電池低能耗的低溫預熱和高溫冷卻技術突破。即使在-30度的極寒天氣,動力電池仍然可以正常使用,在-20度時還能實現快速充電。此外,在高溫天氣ITCS能夠智慧降溫,可確保動力電池正常使用且續航能力不受高溫影響,同時也極大的提高了動力電池的安全性。

據組委會介紹。目前,除了吉利之外,還雲集了比亞迪、上汽、知豆、五洲龍、成功汽車、南京金龍、陸地方舟、新吉奧、英威騰、科陸電子、鼎充、ABB、科大智慧、中恒、盛弘、科華恒盛、萬馬線纜等新能源汽車產業鏈上的各種大咖,這將是繼四月上海車展後以新能源汽車為主題的又一行業盛會!

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

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

分類
發燒車訊

聆風全球銷量42700輛 日產高管稱令人感到“失望”

日產公司近日發佈了2012財年第二季度報告,日產首席運營官賀俊之稱,目前,日產聆風的全球銷量為42700輛。他同時表示,整體銷量不佳令人感到“失望”。

截至目前為止,日產聆風在日本的銷量為19000輛,約占全球銷量的一半。日產公司此前制定了2012年在美國銷售2萬輛聆風的目標,雖然現狀與之相距甚遠,但賀俊之堅稱,日產將繼續電動汽車的開發,拓展電動汽車市場。

賀俊之指出,日產聆風上市兩周年將至,兩年的經驗讓日產公司瞭解了阻礙消費者購買電動汽車的原因、消費者的使用和充電習慣等,這些資訊有利於未來日產團隊採取措施應對相關問題。

值得一提的是,今年10月份,日產聆風共售出1579輛,較9月份的984輛大幅增加,並遠高於8月份685輛的銷量。正如賀俊之所說,作為第一批大規模生產電動汽車的企業,日產將致力於“零排放”工作,希望民眾能理解日產肩負的重任。

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

【其他文章推薦】

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

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

分類
發燒車訊

中國汽車技術研究中心正在制定新能源車碰撞安全標準

中國汽車技術研究中心副主任高和生日前指出,最大的隱患在於電池,在受到撞擊之後電池會變形、起火甚至爆炸,這對汽車的安全帶來了巨大的隱患,因此中心目前也正在考慮制定新能源汽車的相關安全標準。但並沒有透露具體的出臺時間。

隨著國家的大力宣導,目前新能源汽車的發展正在逐步提速,雖然私人購車數量不多,但越來越多的新能源汽車已經逐步進入到如計程車等服務領域,而近一段時間頻頻出現的安全問題,也讓人們對新能源汽車產生了擔憂。

同時高和生透露,C-NCAP未來將把進口車碰撞作為工作重點之一,儘管廠家會很反對,但是本著對消費者負責的態度,我們還是會做這個事情,我們將從當前熱銷的進口車中選擇車型進行碰撞試驗,而銷量很小的車型則不在考慮範圍。

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

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

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

FB行銷專家,教你從零開始的技巧

分類
發燒車訊

歐盟計畫出臺統一的電動汽車補貼政策

目前歐盟國家對的補貼參差不齊,在法國購買電動汽車可獲得最多至7000歐元的補貼,而在德國沒有補貼。為幫助歐洲汽車工業克服時艱,歐盟擬出臺統一的電動汽車補貼政策。

歐盟工業專員塔賈尼(Antonio Tajani)在其草擬的行動計畫中對這一政策目標作說明時說,歐洲汽車工業迫切需要得到支援,以應對挑戰。歐洲汽車工業間接或直接地創造1200個工作崗位,而部分廠商深陷危機。塔賈尼表示,過去數月是歐洲汽車工業經歷的艱難時刻,一些企業如法國PSA、義大利菲亞特、德國歐寶等遭遇強大的重組壓力,產品滯銷。

塔賈尼透過行動計畫承諾,將為歐洲汽車工業提供研發資金,並從歐洲社保基金中拿錢培訓員工,幫助其掌握新的技術。塔還想阻止歐洲國家單一購車補貼行為,在全歐範圍內推行統一的購買電動車補貼政策。法國的做法是,對購買法國本土生產的電動車或混合動力汽車予以最多至7000歐元的補貼,以支持本國汽車業發展和維護其競爭力。而在德國,汽車工業協會也曾多次提出要求予以補貼,但遭德政府拒絕。塔將于本週四飛往柏林遊說其擬出臺方案。

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

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

分類
發燒車訊

中國高新汽車國際峰會

 

展會名稱:

中國高新汽車國際峰會與Automechanika Shanghai

展館地址:

中國‧上海,浦東嘉里大酒店

展會日期:

2012年12月11-13日

主辦單位:

法蘭克福展覽有限公司

網址:

會議演講主題:

 

演講主題範圍包括商業模式,技術創新,基礎設施,服務和生態系統等相關的題目:   ‧ 汽車行業可持續發展的新興商業模式和生態系統合作 ‧ 提高能源效率的混合動力,清潔燃料和電動汽車解決方案 ‧ 個人移動服務的創新設計,技術,市場和服務 ‧ 商用車輛和運輸業的綠色商業模式和經營策略 ‧ 連網車輛和遠程信息處理技術,服務和應用所產生的新收益源 ‧ 充電技術,基礎設施和不斷發展的商業模式 ‧ 先進的電池技術和管理系統,以提高經濟負擔能力 ‧ 替代低碳燃料,能源效率和減少廢氣排放 ‧ 最高性能和效率的動力/傳動系統和電子控制系統 ‧ 先進的材料和製造工藝,以提高生產力和降低成本 ‧ 綠色汽車市場的發展,包括以消費者為導向的策略,售後市場的增長和經銷商的機會 ‧ 未來發展和國際化的投資及融資策略

演講者及參會者

峰會的來賓將有來自C級管理,戰略規劃等有影響力的高層決策者,產品創新與服務高管,專業技術人員和工程師:   ‧ 客運和商用車原始設備製造商 ‧ 主要供應商和零部件製造商 ‧ 戰略投資者 ‧ 專業技術人員和工程師 ‧ 車隊管理人員和經營者 ‧ 公用事業和能源服務公司 ‧ 替代燃料供應商 ‧ 先進的傳動系統和電池系統開發商 ‧ 服務提供商 ‧ 政府管理部門和市政領導 ‧ 具有影響力的行業研究機構  

 

連絡資訊

連絡人:葉家欣小姐

電話:(852) 2230 9202

電子郵件:keiann.yip@hongkong.messefrankfurt.com

官方網站:www.nextgenautosummit-china.com

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

【其他文章推薦】

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

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!