Class文件結構全面解析(下)

接上回書

書接,分享了Class文件的主要構成,同時也詳細分析了魔數、次版本號、主版本號、常量池集合、訪問標誌的構造,接下來我們就繼續學習。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

類索引和父類索引

類索引(this_class)和父類索引(super_class)都是一個u2類型的數據,類索引用於確定這個類的全限定名,父類索引用於確定這個類的父類全限定名。由於java語言不允許多重繼承,所以父類索引只有一個。

類索引和父類索引各自指向常量池中類型為CONSTANT_Class_info的類描述符,再通過類描述符中的索引值找到常量池中類型為CONSTANT_Utf8_info的字符串。再來看一下之前的Class文件例子:

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

結合之前javap分析出來的常量池內容:

   #3 = Class         #17        // OneMoreStudy
   #4 = Class         #18        // java/lang/Object
  #17 = Utf8          OneMoreStudy
  #18 = Utf8          java/lang/Object

類索引為0x0003,去常量池裡找索引為3的類描述符,類描述符中的索引為17,再去找索引為17的字符串,就是“OneMoreStudy”。

父類索引為0x0004,去常量池裡找索引為4的類描述符,類描述符中的索引為18,再去常量池裡找索引為18的字符串,就是“java/lang/Object”。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

接口索引集合

接口索引集合(interface)是一組u2類型的數據的集合,由於java語言允許實現多個接口,所以接口索引也有多個,它們按照implements語句后的接口順序從左到右依次排列在接口索引集合中。接口索引集合的第一項數據是接口集合計數值(interfaces_count),表示有多少接口索引。如果該類沒有實現任何接口,那麼該計數值為0,後面的接口索引表不佔任何字節。之前的例子OneMoreStudy類沒有實現任何接口,所以接口集合計數值就是0,如下圖:

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

字段表集合

字段表(field_info)是用來描述接口或類中聲明的變量。包括類級變量(靜態變量)和實例級變量(成員變量),但是不包括在方法內部聲明的局部變量。具體結構如下錶:

類型 名稱 數量 描述
u2 access_flags 1 字段的訪問標誌
u2 name_index 1 字段的簡單名稱索引
u2 descriptor_index 1 字段的描述符索引
u2 attributes_count 1 字段的屬性計數值
attribute_info attributes attributes_count 字段的屬性

字段表中的access_flags,和類的access_flags是非常類似的,但是標識和含義是不一樣的。具體如下錶:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 字段是否public
ACC_PRIVATE 0x0002 字段是否private
ACC_PROTECTED 0x0004 字段是否protected
ACC_STATIC 0x0008 字段是否static
ACC_FINAL 0x0010 字段是否為final
ACC_VOLATILE 0x0040 字段是否volatile
ACC_TRANSIENT 0x0080 字段是否transient
ACC_SYNTHETIC 0x1000 字段是否由編譯器自動產生的
ACC_ENUM 0x4000 字段是否enum

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

這裏提到了簡單名稱、描述符,和全限定名有什麼區別呢?稍微說一下。

簡單名稱是沒有類型和參數修飾的方法或字段名稱,比如OneMoreStudy類中的number字段和plusOne()方法的簡單名稱分別是“number”和“plusOne”。

全限定名是把類全名中的“.”替換成“/”就可以了,比如java.lang.Object類的全限定名就是“java/lang/Object”。

描述符是用來描述字段的數據類型、方法的參數列表(包括數量、類型以及順序)和返回值。基礎數據類型和無返回的void類型都有一個大寫字母表示,對象類型用字符L加對象的全限定名來表示,如下錶:

標識字符 含義
B 基本類型byte
C 基本類型char
D 基本類型double
F 基本類型float
I 基本類型int
J 基本類型long
S 基本類型short
Z 基本類型boolean
V 特殊類型void
L 對象類型 如 Ljava/lang/Object

對於數組類型,每一維度使用一個前置的“[”字符來描述,比如java.lang.Object[][]的二維數據,就是“[[Ljava/lang/Object”。在描述方法時,按照先參數列表,后返回值的順序描述,參數列表按照嚴格順序放在“()”值中,比如boolean equals(Object anObject),就是“(Ljava/lang/Object)B”。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

再來看一下之前的Class文件例子:

OneMoreStudy類中只有一個字段number,所以字段計數值為0x0001。字段number只被private修飾,沒有其他修飾,所以字段的訪問標誌位為0x0002。字段的簡單名稱索引是0x0005,去常量池中找索引為5的字符串,為“number”。字段的描述符索引為0x0006,去常量池中找索引為6的字符串,為“I”,是基本類型int。以下是常量池相關內容:

   #5 = Utf8          number
   #6 = Utf8          I

字段number的屬性計數值為0x0000,也就是沒有需要額外描述的信息。

字段表集合中不會列出從父類或者父接口中繼承而來的字段,但有可能列出原版Java代碼中沒有的字段,比如在內部類中為了保持對外部類的訪問性,會自動添加指向外部類實例的字段。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

方法表集合

方法表的結構和字段表的是一樣的,也是依次包括了訪問標誌(access_flags)、名稱索引(name_index)、描述符索引(descriptor_index)和屬性表集合(attributes)。具體如下錶:

類型 名稱 數量 描述
u2 access_flags 1 方法的訪問標誌
u2 name_index 1 方法的簡單名稱索引
u2 descriptor_index 1 方法的描述符索引
u2 attributes_count 1 方法的屬性計數值
attribute_info attributes attributes_count 方法的屬性

對於方法的訪問標誌,所有標誌位和取值如下錶:

標誌名稱 標誌值 含義
ACC_PUBLIC 0x0001 方法是否public
ACC_PRIVATE 0x0002 方法是否private
ACC_PROTECTED 0x0004 方法是否protected
ACC_STATIC 0x0008 方法是否static
ACC_FINAL 0x0010 方法是否為final
ACC_SYNCHRONIZED 0x0020 方法是否sychronized
ACC_BRIDGE 0x0040 方法是否是由編譯器產生的橋接方法
ACC_VARARGS 0x0080 方法是否接受不定參數
ACC_NATIVE 0x0100 方法是否為native
ACC_ABSTRACT 0x0400 方法是否為abstract
ACC_STRICT 0x0800 方法是否為strictfp
ACC_SYNTHETIC 0x1000 方法是否由編譯器自動產生

方法中的Java代碼,經過編譯器編程成字節碼指令后,放在方法屬性表集合中一個名為“Code”的屬性里,後面會有更多分享。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

再來看一下之前的Class文件例子:

方法計算值為0x0003,表示集合中有兩個方法(編譯器自動添加的無參構造方法和源碼中的plusOne方法)。第一個方法的訪問標誌是0x0001,表示只有ACC_PUBLIC標誌為true。

名稱索引為0x0007,在常量池中為索引為7的字符串為“ ”,這就是編譯器自動添加的無參構造方法。描述符索引為0x0008,在常量池中為索引為7的字符串為“()V”,方法的屬性計數值為0x0001,表示該方法有1個屬性,屬性名稱索引為0x0009,在常量池中為索引為7的字符串為“Code”。以下是常量池相關內容:

   #7 = Utf8          <init>
   #8 = Utf8          ()V
   #9 = Utf8          Code

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

屬性表集合

屬性表(attribute_info)在前面的分享中出現了幾次,在Class文件、字段表、方法表都可以有自己的屬性表集合,用來描述某些場景下特有的信息。

屬性表不在要求具有嚴格的順序,並且只要不與已有的屬性名重複,任何人實現的編譯器都可以寫入自己定義的屬性信息,Java虛擬機在運行時會忽略掉它不認識的屬性。

我總結了一些比較常見的屬性,如下錶:

屬性名稱 使用位置 含義
Code 方法表 Java代碼編譯成的字節碼指令
ConstantValue 字段表 final關鍵字定義的常量值
Exceptions 方法表 方法拋出的異常
InnerClasses 類文件 內部類列表
LineNumberTable Code屬性 Java源碼的行號與字節碼指定的對應關係
LocalVariableTable Code屬性 方法的局部變量描述
SourceFile 類文件 記錄源文件名稱

對於每個屬性,它的名稱都從常量池中引用一個CONSTANT_Utf8_info類型的常量,而屬性值的結構則是完全自定義的,只需要用一個u4類型來說明屬性值所佔的位數就可以了。具體結構如下:

類型 名稱 數量 含義
u2 attribute_name_index 1 屬性名稱索引
u2 attribute_length 1 屬性值所佔的位數
u1 info attribute_length 屬性值

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

總結

Class文件主要由魔數、次版本號、主版本號、常量池集合、訪問標誌、類索引、父類索引、接口索引集合、字段表集合、方法表集合和屬性表集合組成。隨着JDK版本的不斷升級,Class文件結構也在不斷更新,學習之路,永不止步。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

Orleans 3.0 為我們帶來了什麼

 

原文:https://devblogs.microsoft.com/dotnet/orleans-3-0/

作者:Reuben BondOrleans首席軟件開發工程師

翻譯:艾心

這是一篇來自Orleans團隊的客座文章,Orleans是一個使用.NET創建分佈式應用的跨平台框架。獲取更多信息,請查看https://github.com/dotnet/orleans。

我們激動的宣布Orleans3.0的發布。自Orleans2.0以來,加入了大量的改進與修復,以及一些新特性。這些變化是由許多人在生產環境的大量場景中運行基於Orleans應用程序的經驗,以及全球Orleans社區的智慧和熱情推動的,他們致力於使代碼庫更好、更快、更靈活。非常感謝所有以各種方式為這個版本做出貢獻的人。

自Orleans 2.0以來的關鍵變化:

Orleans 2.0發佈於18個多月前,從那時起Orleans便取得了巨大的進步。以下是自Orleans 2.0以來的重大變化:

  • 分佈式ACID事務-多個Grains加入到一個事務中,不管他們的狀態存儲在哪裡
  • 一個新的調度器,在某些情況下,僅它就可以將性能提升30%以上
  • 一種基於Roslyn代碼分析的新的代碼生成器
  • 重寫集群成員以提升恢復速度
  • 聯合(Co-hosting)支持

還有很多其他的提升以及修復。

自從致力於開發Orleans2.0以來,團隊就建立了一套實現或者繼承某些功能的良性循環,包括通用主機、命名選項,在準備將這些功能好成為.NETCore的一部分之前與.NET團隊密切合作、提供反饋和改進“upstream”,在以後的版本中會切換到.NET版本附帶的最終實現。在開發Orleans 3.0期間,這個循環繼續着,在最終發布為.NET Core 3.0的一部分之前,Orleans 3.0.0-beta1使用了Bedrock代碼。類似的,TCP套接字連接對TLS的支持是作為Orleans 3.0的一部分實現的,並計劃成為.NET Core未來版本的一部分。我們把這種持續的合作視為是我們對更大的.NET生態系統的貢獻,這是真正的開源精神。

使用ASP.NET Bedrock替換網絡層

一段時間以來,社區和內部合作夥伴一直要求支持與TLS的安全通信。在3.0版本中,我們引入了TLS支持,可以通過Microsoft.Orleans.Connections.Security包獲取。有關更多信息,請查看TransportLayerSecurity範例。實現TLS支持之所以是一個重大任務要歸因於上一個版本中Orleans網絡層的實現方式:它並不容易適應使用SslStream的方式,而SslStream又是實現TLS最常用的方法。在TLS的推動下,我們着手重寫Orleans的網絡層。

Orleans 3.0使用了一個來自ASP.NET團隊倡議的基於Bedrock項目構建的網絡層替換了自己的整個網絡層,Bedrock旨在幫助開發者構建快速的、健壯的網絡客戶端和服務器。

ASP.NET團隊和Orleans團隊一同合作設計了同時支持網絡客戶端和服務端的抽象,這些抽象與傳輸無關,並且可以通過中間件實現定製化。這些抽象允許我們通過配置修改網絡,而不用修改內部的、特定於Orleans的網絡代碼。Orleans的TLS支持是作為Bedrock中間件實現的,我們的目的是使之通用,以便與.NET生態圈的其他人共享。

儘管這項工作是的動力是啟用TLS支持,但是在夜間負載測試中,我們看到了平均吞吐量提升了大約30%。

網絡層重寫還包括藉助使用MemoryPool<byte>替換我們的自定義緩存池,在進行這項修改時,序列化更多的使用到了Span<T>。有一些代碼路徑之前是依靠調用BlockingCollection<T>的專有線程進行阻塞,現在使用Channel<T>來異步傳輸消息。這將導致更少的專有線程佔用,同時將工作移動到了.NET線程池。

Orleans的核心連接協議自發布以來一直都是固定的。在Orleans3.0中,我們已經增加了通過協議協商(negotiation)逐步更新網絡層的支持。Orleans 3.0中添加的協議協商支持未來的功能增強,如定製核心序列化器,同時向後保持兼容性。新的網絡協議的一個優點是支持全雙工Silo到Silo的連接,而不是以前在Silo之間建立的單工連接對。協議版本可以通過ConnectionOptions.ProtocolVersion進行配置。

通過通用主機進行聯合託管

Orleans與其他框架共同進行聯合託管,如ASP.NETCore,得益於.NET通用主機,相同的進程中(使用聯合託管)現在要比以前容易多了。

下面是一個使用UseOrleans將Orleans和ASP.NETCore一起添加到主機的例子:

 1 var host = new HostBuilder()
 2   .ConfigureWebHostDefaults(webBuilder =>
 3   {
 4     // Configure ASP.NET Core
 5     webBuilder.UseStartup<Startup>();
 6   })
 7   .UseOrleans(siloBuilder =>
 8   {
 9     // Configure Orleans
10     siloBuilder.UseLocalHostClustering();
11   })
12   .ConfigureLogging(logging =>
13   {
14     /* Configure cross-cutting concerns such as logging */
15   })
16   .ConfigureServices(services =>
17   {
18     /* Configure shared services */
19   })
20   .UseConsoleLifetime()
21   .Build();
22 
23 // Start the host and wait for it to stop.
24 await host.RunAsync();

使用通過主機構建器,Orleans將與其他託管程序共享同一個服務提供者。這使得這些服務可以訪問Orleans。例如,一個開發者可以注入IClusterClient或者IGrainFactory到ASP.NETCore MVC Controller中,然後從MVC應用中直接調用Grains。

這個功能可以簡化你的部署拓撲或者向現有程序中額外添加功能。一些團隊內部使用聯合託管,通過ASP.NET Core健康檢查將Kubernetes活躍性和就緒性探針添加到其Orleans Silo中。

可靠性提高

得益於擴展了Gossip,集群現在可以更快的從失敗中恢復。在以前的Orleans版本中,Silo會向其他Silo發送成員Gossip信息,指示他們更新成員信息。現在Gossip消息包括集群成員的版本化、不可變快照。這樣可以縮短Silo加入或者離開集群的收斂時間(例如在更新、擴展或者失敗后),並減輕共享成員存儲上的爭用,從而加快集群轉換的速度。故障檢測也得到了改進,利用更多的診斷信息和改進功能以確保更快、更準確的檢測。故障檢測涉及集群中的Silo,他們相互監控,每個Silo會定期向其他Silo的子集發送健康探測。Silo和客戶端現在還主動與已聲明為已失效的Silo的連接斷開,它們將拒絕與此類Silo的連接。

現在,消息錯誤得到了更一致的處理,從而將錯誤提示信息傳播回調用者。這有助於開發者更快地發現錯誤。例如,當消息無法被完全序列化或者反序列化時,詳細的異常信息將會被返回到原始調用方。

可擴展性增強

現在,Streams可以有自定義的數據適配器,從而允許他們以任何格式提取數據。這使得開發人員更好的控制Streamitems在存儲中的表示方式。他還使Stream提供者可以控制如何寫入數據,從而允許Streams與老的系統和Orleans服務集成。

Grain擴展允許通過自己的通信接口附件新的組件,從而在運行時向Grain添加其他行為。例如,Orleans事務使用Grain擴展對用戶透明的向Grain中添加事務生命周期方法,如“準備”、“提交”和“中止”。Grain擴展現在也可用於Grain服務和系統目標。

現在,自定義事務狀態可以聲明其在事務中能夠扮演的角色。例如,將事務生命周期事件寫入服務總線隊列的事務狀態實現不能滿足事務管理器的職責,因為它(該事務狀態的職責)是只寫的。

由於預定義的放置策略現在可以公開訪問,因此在配置期間可以替換任何放置控制器。

共同努力

既然Orleans 3.0已經發布,我們也就會將注意力轉向未來的版本-我們有一些令人興奮的計劃!快來加入我們在GitHub和Gitter上的社區,幫助我們實現這些計劃。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

abp(net core)+easyui+efcore實現倉儲管理系統——ABP WebAPI與EasyUI結合增刪改查之二(二十八)




 










   

       在上一篇 文章中我們學習了TreeGrid的一些基礎知識,接下我們來創建我們開發組織管理功能用到的一些類。關於如何創建類我們之前的文章中已經寫了很多了,這裡會有些簡略。

 

四、定義應用服務接口需要用到的DTO類

      為了在進行查詢時使用, PagedOrgResultRequestDto被用來將模塊數據傳遞到基礎設施層.

       1. 在Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“ABP.TPLMS.Application”項目,在彈出菜單中選擇“添加” > “新建文件夾”,並重命名為“Orgs”

      2. 使用鼠標右鍵單擊我們剛才創建的“Orgs”文件夾,在彈出菜單中選擇“添加” > “新建文件夾”,並重命名為“Dto”。

      3.右鍵單擊“Dto”文件夾,然後選擇“添加” > “類”。 將類命名為 Paged OrgResultRequestDto,然後選擇“添加”。代碼如下。

using Abp.Application.Services.Dto;
using Abp.Application.Services.Dto;
using System;
using System.Collections.Generic;
using System.Text;
 

namespace ABP.TPLMS.Orgs.Dto
{

public class PagedOrgResultRequestDto : PagedResultRequestDto
    {
        public string Keyword { get; set; }
    }
}

      4.右鍵單擊“Dto”文件夾,然後選擇“添加” > “類”。 將類命名為 OrgDto,然後選擇“添加”。代碼如下。

using Abp.Application.Services.Dto;
using Abp.AutoMapper;
using ABP.TPLMS.Entitys;
using System;
using System.Collections.Generic;
using System.Text;
 

namespace ABP.TPLMS.Orgs.Dto
{

    [AutoMapFrom(typeof(Org))]
    public class OrgDto : EntityDto<int>
    {

        int m_parentId = 0;
        public string Name { get; set; }       

        public string HotKey { get; set; }
        public int ParentId { get { return m_parentId; } set { m_parentId = value; } }       

        public string ParentName { get; set; }
        public bool IsLeaf { get; set; }
        public bool IsAutoExpand { get; set; }       

        public string IconName { get; set; }
        public int Status { get; set; }
        public int Type { get; set; }      
        public string BizCode { get; set; }       

        public string CustomCode { get; set; }
        public DateTime CreationTime { get; set; }
        public DateTime UpdateTime { get; set; }

        public int CreateId { get; set; }
        public int SortNo { get; set; }
        public int _parentId {
            get { return m_parentId; }          

        }
    }
}

 

      5.右鍵單擊“Dto”文件夾,然後選擇“添加” > “類”。 將類命名為 CreateUpdateOrgDto,然後選擇“添加”。代碼如下。

using Abp.Application.Services.Dto;
using Abp.AutoMapper;
using ABP.TPLMS.Entitys;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text;

 
namespace ABP.TPLMS.Orgs.Dto
{

    [AutoMapTo(typeof(Org))]
    public class CreateUpdateOrgDto : EntityDto<int>
    {

        public string Name { get; set; }
        [StringLength(255)]
        public string HotKey { get; set; }

        public int ParentId { get; set; }

        [Required]
        [StringLength(255)]
        public string ParentName { get; set; }

        public bool IsLeaf { get; set; }
        public bool IsAutoExpand { get; set; }

        [StringLength(255)]
        public string IconName { get; set; }

        public int Status { get; set; }
        public int Type { get; set; }

        [StringLength(255)]
        public string BizCode { get; set; }

        [StringLength(100)]
        public string CustomCode { get; set; }
        public DateTime CreationTime { get; set; }
        public DateTime UpdateTime { get; set; }

        public int CreateId { get; set; }
        public int SortNo { get; set; } 

    }
}

 

 

 

 

五、定義IOrgAppService接口

        6. 在Visual Studio 2017的“解決方案資源管理器”中,鼠標右鍵單擊“Org”文件夾,然後選擇“添加” > “新建項”,在彈出對話框中選擇“接口”。為應用服務定義一個名為 IOrgAppService 的接口。代碼如下。

using System;
using System.Collections.Generic;
using System.Text;
using Abp.Application.Services;
using ABP.TPLMS.Orgs.Dto; 

 

namespace ABP.TPLMS.Orgs
{
  public  interface IOrgAppService : IAsyncCrudAppService<//定義了CRUD方法

             OrgDto, //用來展示組織信息
             int, //Org實體的主鍵
             PagedOrgResultRequestDto, //獲取組織信息的時候用於分頁
             CreateUpdateOrgDto, //用於創建組織信息
             CreateUpdateOrgDto> //用於更新組織信息

    {
    }
}

 

      六、實現IOrgAppService

       7.在Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊“Org”文件夾,然後選擇“添加” > “新建項”,在彈出對話框中選擇“類”。為應用服務定義一個名為 OrgAppService 的服務類。代碼如下。

using Abp.Application.Services;
using Abp.Domain.Repositories;
using ABP.TPLMS.Entitys;
using ABP.TPLMS.Orgs.Dto;
using System;
using System.Collections.Generic;
using System.Text;
 

namespace ABP.TPLMS.Orgs
{

    public class OrgAppService : AsyncCrudAppService<Org, OrgDto, int, PagedOrgResultRequestDto,
                            CreateUpdateOrgDto, CreateUpdateOrgDto>, IOrgAppService 

    {
        public OrgAppService(IRepository<Org, int> repository)

            : base(repository)

        { 

        }
    }
}

七 創建OrgController繼承自TPLMSControllerBase

      1. 在Visual Studio 2017的“解決方案資源管理器”中,右鍵單擊在領域層“ABP.TPLMS.Web.Mvc”項目中的Controller目錄。 選擇“添加” > “新建項…”。如下圖。

 

     2. 在彈出對話框“添加新項-ABP.TPLMS.Web.Mvc”中選擇“控制器類”,然後在名稱輸入框中輸入“OrgsController”,然後點擊“添加”按鈕。

     3.在OrgsController.cs文件中輸入如下代碼,通過構造函數注入對應用服務的依賴。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Abp.AspNetCore.Mvc.Authorization;
using Abp.Web.Models;
using ABP.TPLMS.Controllers;
using ABP.TPLMS.Orgs;
using ABP.TPLMS.Orgs.Dto;
using ABP.TPLMS.Web.Models.Orgs;
using Microsoft.AspNetCore.Mvc;


namespace ABP.TPLMS.Web.Controllers
{
    [AbpMvcAuthorize]
    public class OrgsController : TPLMSControllerBase
    {
        private readonly IOrgAppService _orgAppService;
        private const int MAX_COUNT= 1000;
        
        public OrgsController(IOrgAppService orgAppService)
        {
            _orgAppService = orgAppService;
        }
        [HttpGet]
        // GET: /<controller>/
        public IActionResult Index()
        {
            return View();
        }
        [DontWrapResult]
        [HttpPost]
        public string List()
        {         
            PagedOrgResultRequestDto paged = new PagedOrgResultRequestDto();
            paged.MaxResultCount = MAX_COUNT;
           var userList = _orgAppService.GetAll(paged).GetAwaiter().GetResult().Items;
            int total = userList.Count;
            var json = JsonEasyUI(userList, total);
            return json;
        }       
    }
}

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

中油換電站今年採競標,Gogoro累計 285 站、光陽 146 站

中油從 107 年起,依據經濟部智慧電動機車能源補充設施普及計畫(公建計畫)於加油站建置電動機車充電、換電設備,去年僅睿能 Gogoro 參與設 144 站,今年增加光陽 Ionex,兩家採公開競標方式,由光陽 Ionex 獲得 3 標共 146 站,睿能 Gogoro 得到 1 標 48 站;其中,睿能 Gogoro 扣除合約到期站數,再加上先前自建設站,在台灣中油加油站中有 285 站可為消費者服務。

經濟部工業局委託台灣銀行辦理共同供應契約設備系統招標,其中電池交換設備系統得標廠商都需符合安全規範審核,108 年換電設備系統之得標廠商共有睿能 Gogoro 及光陽 Ionex 兩家,中油考量消費者的便利性及設備使用公平性兩大原則,於採購系統設備前即採取公開方式辦理合作經營招標。

中油表示,北部地區有 52 站與睿能 Gogoro 合作的換電站,今年合約陸續到期,睿能 Gogoro 依據契約與決標結果遷移設備,為執行公建計畫,中油將此52站列入今年建置194站換電站的地點,公建換電站依各縣市站點打散,再平均分4案開標,讓每一標案站點均涵蓋各縣市,投標結果為光陽 Ionex 獲得 3 標(146 站)、睿能 Gogoro 得 1 標(48 站),原先台灣中油與睿能 Gogoro 合約到期的52站中,有38站改由光陽Ionex得標。

中油指出,上述政府電動機車充、換電站公建計畫,於 107 年已執行 161 站,其中 144 座換電站均為睿能 Gogoro 所有,另有充電 17 站,今年預計建置 216 座充換電站,其中 194 座為換電站,光陽 Ionex 有 146 站、睿能 Gogoro 48 站,另有充電 22 站,以總數來說,再加上睿能 Gogoro 之前的自建站 79 站,其仍在台灣中油有 285 站,光陽 Ionex 有 146 站為消費者服務,明年亦將配合政府預算持續建置更多站點,協助政府綠能政策推動。

(本文內容由 授權使用。首圖來源:Gogoro)

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

【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

遷移桌面程序到MS Store(12)——WPF使用UWP InkToolbar和InkCanvas

我們在提到了對Win10 API的調用,但仍存在無法在WPF中使用UWP控件的問題,雖然都是XAML控件,但卻是兩套命名空間下的同名類型,無法混用。
人總會被現實打敗,強大如某軟也得向生活低頭,UWP一直沒有起色,某軟的老大又一心去搞Azure。Windows平台的重振,似乎想走回頭路,從1903版本開始,支持在.NET Framwork的WPF和WinForm工程中,直接使用部分的UWP控件了。首當其沖的,就是有點騷包的InkToolbar和InkCanvas。

接下來我們就來試試如何在WPF工程中,使用UWP的InkToolbar和InkCanvas。
首先創建一個空的WPF工程,完成后,在Nuget的搜索界面填入 Microsoft.Toolkit.Wpf.UI.Controls ,選中第一個進行安裝。

完成安裝后,打開MainWindow.xaml,添加對命名空間的引用xmlns:Controls=”clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls”。接着就可以在<Grid>節點中添加UWP版本的InkToolbar和InkCanvas控件了。

<Window x:Class="WPFInkSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WPFInkSample"
        xmlns:Controls="clr-namespace:Microsoft.Toolkit.Wpf.UI.Controls;assembly=Microsoft.Toolkit.Wpf.UI.Controls"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <Controls:InkToolbar TargetInkCanvas="{x:Reference myInkCanvas}" Grid.Row="0" />
        <Controls:InkCanvas x:Name="myInkCanvas" Grid.Row="1" />
    </Grid>
</Window>

同時我們還需要在MainWindow.xaml.cs中設置InputDeviceTypes。

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.myInkCanvas.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen;
        }
    }

然後按下F5運行,某軟的騷操作來了……因為僅在1903以後的版本才支持這種騷操作(10.0.18226是稍早的preview版),所以需要做額外的處理才可以。

我們這裡有兩種選擇,一是通過來打包這個WPF程序,然後在Packaging工程的屬性里,將Target version和Minimum version同時設置為Windows 10, version 1903 (10.0.18362) 。這是MSDN上推薦的標準做法,這樣做的好處在於,打包好的程序可以直接上傳MS Store。
如果我們想保持exe的可執行文件形式,還有另一種做法,在Project文件上右鍵點擊Add->New Item,添加一個manifest文件。
在這個文件中,找到<!–Windows 10–>,然後做如下編輯:

  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!-- A list of the Windows versions that this application has been tested on
           and is designed to work with. Uncomment the appropriate elements
           and Windows will automatically select the most compatible environment. -->
  
      <!-- Windows Vista -->
      <!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
  
      <!-- Windows 7 -->
      <!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
  
      <!-- Windows 8 -->
      <!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
  
      <!-- Windows 8.1 -->
      <!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
  
      <!-- Windows 10 -->
      <maxversiontested Id="10.0.18362.0"/>
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  
    </application>
  </compatibility>

保存后,再通過F5運行,即發現一切正常,不在出現之前的運行時錯誤了。
本篇我們介紹了如何在WPF工程中使用UWP InkToolbar和InkCavas。因為這個功能僅在1903后的版本支持,所以下一篇我們會介紹如何簡單地判斷Win10 API 版本,在運行時判斷是否執行對應版本的代碼。
Github:

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

生產者-消費者模型在Hudi中的應用

介紹

生產者-消費者模型用於解耦生產者與消費者,平衡兩者之間的能力不平衡,該模型廣泛應用於各個系統中,Hudi也使用了該模型控制對記錄的處理,即記錄會被生產者生產至隊列中,然後由消費者從隊列中消費,更具體一點,對於更新操作,生產者會將文件中老的記錄放入隊列中等待消費者消費,消費后交由HoodieMergeHandle處理;對於插入操作,生產者會將新記錄放入隊列中等待消費者消費,消費后交由HandleCreateHandle處理。

入口

前面的文章中提到過無論是HoodieCopyOnWriteTable#handleUpdate處理更新時直接生成了一個SparkBoundedInMemoryExecutor對象,還是HoodieCopyOnWriteTable#handleInsert處理插入時生成了一個CopyOnWriteLazyInsertIterable對象,再迭代時調用該對象的CopyOnWriteLazyInsertIterable#computeNext方法生成SparkBoundedInMemoryExecutor對象。最後兩者均會調用SparkBoundedInMemoryExecutor#execute開始記錄的處理,該方法核心代碼如下

  public E execute() {
    try {
      ExecutorCompletionService<Boolean> producerService = startProducers();
      Future<E> future = startConsumer();
      // Wait for consumer to be done
      return future.get();
    } catch (Exception e) {
      throw new HoodieException(e);
    }
  }

該方法會啟動所有生產者和單個消費者進行處理。

Hudi定義了BoundedInMemoryQueueProducer接口表示生產者,其子類實現如下

  • FunctionBasedQueueProducer,基於Function來生產記錄,在合併日誌log文件和數據parquet文件時使用,以便提供RealTimeView
  • IteratorBasedQueueProducer,基於迭代器來生產記錄,在插入更新時使用。

定義了BoundedInMemoryQueueConsumer類表示消費者,其主要子類實現如下

  • CopyOnWriteLazyInsertIterable$CopyOnWriteInsertHandler,主要處理CopyOnWrite表類型時的插入。
    • MergeOnReadLazyInsertIterable$MergeOnReadInsertHandler,主要處理MergeOnRead

表類型時的插入,其為CopyOnWriteInsertHandler的子類。

  • CopyOnWriteLazyInsertIterable$UpdateHandler,主要處理CopyOnWrite表類型時的更新。

整個生產消費相關的類繼承結構非常清晰。

對於生產者的啟動,startProducers方法核心代碼如下

  public ExecutorCompletionService<Boolean> startProducers() {
    // Latch to control when and which producer thread will close the queue
    final CountDownLatch latch = new CountDownLatch(producers.size());
    final ExecutorCompletionService<Boolean> completionService =
        new ExecutorCompletionService<Boolean>(executorService);
    producers.stream().map(producer -> {
      return completionService.submit(() -> {
        try {
          preExecute();
          producer.produce(queue);
        } catch (Exception e) {
          logger.error("error producing records", e);
          queue.markAsFailed(e);
          throw e;
        } finally {
          synchronized (latch) {
            latch.countDown();
            if (latch.getCount() == 0) {
              // Mark production as done so that consumer will be able to exit
              queue.close();
            }
          }
        }
        return true;
      });
    }).collect(Collectors.toList());
    return completionService;
  }

該方法使用CountDownLatch來協調生產者線程與消費者線程的退出動作,然後調用produce方法開始生產,對於插入更新時的IteratorBasedQueueProducer而言,其核心代碼如下

  public void produce(BoundedInMemoryQueue<I, ?> queue) throws Exception {
    ...
    while (inputIterator.hasNext()) {
      queue.insertRecord(inputIterator.next());
    }
    ...
  }

可以看到只要迭代器還有記錄(可能為插入時的新記錄或者更新時的舊記錄),就會往隊列中不斷寫入。

對於消費者的啟動,startConsumer方法的核心代碼如下

  private Future<E> startConsumer() {
    return consumer.map(consumer -> {
      return executorService.submit(() -> {
        ...
        preExecute();
        try {
          E result = consumer.consume(queue);
          return result;
        } catch (Exception e) {
          queue.markAsFailed(e);
          throw e;
        }
      });
    }).orElse(CompletableFuture.completedFuture(null));
  }

消費時會先進行執行前的準備,然後開始消費,其中consume方法的核心代碼如下

  public O consume(BoundedInMemoryQueue<?, I> queue) throws Exception {
    Iterator<I> iterator = queue.iterator();

    while (iterator.hasNext()) {
      consumeOneRecord(iterator.next());
    }

    // Notifies done
    finish();

    return getResult();
  }

可以看到只要隊列中還有記錄,就可以獲取該記錄,然後調用不同BoundedInMemoryQueueConsumer子類的consumeOneRecord進行更新插入處理。

值得一提的是Hudi對隊列進行了流控,生產者不能無限制地將記錄寫入隊列中,隊列緩存的大小由用戶配置,隊列能放入記錄的條數由採樣的記錄大小和隊列緩存大小控制。

在生產時,會調用BoundedInMemoryQueue#insertRecord將記錄寫入隊列,其核心代碼如下

  public void insertRecord(I t) throws Exception {
    ...
    rateLimiter.acquire();
    // We are retrieving insert value in the record queueing thread to offload computation
    // around schema validation
    // and record creation to it.
    final O payload = transformFunction.apply(t);
    adjustBufferSizeIfNeeded(payload);
    queue.put(Option.of(payload));
  }

首先獲取一個許可(Semaphore),未成功獲取會被阻塞直至成功獲取,然後獲取記錄的負載以便調整隊列,然後放入內部隊列(LinkedBlockingQueue)中,其中adjustBufferSizeIfNeeded方法的核心代碼如下

  private void adjustBufferSizeIfNeeded(final O payload) throws InterruptedException {
    if (this.samplingRecordCounter.incrementAndGet() % RECORD_SAMPLING_RATE != 0) {
      return;
    }

    final long recordSizeInBytes = payloadSizeEstimator.sizeEstimate(payload);
    final long newAvgRecordSizeInBytes =
        Math.max(1, (avgRecordSizeInBytes * numSamples + recordSizeInBytes) / (numSamples + 1));
    final int newRateLimit =
        (int) Math.min(RECORD_CACHING_LIMIT, Math.max(1, this.memoryLimit / newAvgRecordSizeInBytes));

    // If there is any change in number of records to cache then we will either release (if it increased) or acquire
    // (if it decreased) to adjust rate limiting to newly computed value.
    if (newRateLimit > currentRateLimit) {
      rateLimiter.release(newRateLimit - currentRateLimit);
    } else if (newRateLimit < currentRateLimit) {
      rateLimiter.acquire(currentRateLimit - newRateLimit);
    }
    currentRateLimit = newRateLimit;
    avgRecordSizeInBytes = newAvgRecordSizeInBytes;
    numSamples++;
  }

首先看是否已經達到採樣頻率,然後計算新的記錄平均大小和限流速率,如果新的限流速率大於當前速率,則可釋放一些許可(供阻塞的生產者獲取後繼續生產),否則需要獲取(回收)一些許可(許可變少後生產速率自然就降低了)。該操作可根據採樣的記錄大小動態調節速率,不至於在記錄負載太大和記錄負載太小時,放入同等個數,從而起到動態調節作用。

在消費時,會調用BoundedInMemoryQueue#readNextRecord讀取記錄,其核心代碼如下

  private Option<O> readNextRecord() {
    ...
    rateLimiter.release();
    Option<O> newRecord = Option.empty();
    while (expectMoreRecords()) {
      try {
        throwExceptionIfFailed();
        newRecord = queue.poll(RECORD_POLL_INTERVAL_SEC, TimeUnit.SECONDS);
        if (newRecord != null) {
          break;
        }
      } catch (InterruptedException e) {
        throw new HoodieException(e);
      }
    }
    ...

    if (newRecord != null && newRecord.isPresent()) {
      return newRecord;
    } else {
      // We are done reading all the records from internal iterator.
      this.isReadDone.set(true);
      return Option.empty();
    }
  }

可以看到首先會釋放一個許可,然後判斷是否還可以讀取記錄(還在生產或者停止生產但隊列不為空都可讀取),然後從內部隊列獲取記錄或返回。

上述便是生產者-消費者在Hudi中應用的分析。

總結

Hudi採用了生產者-消費者模型來控制記錄的處理,與傳統多生產者-多消費者模型不同的是,Hudi現在只支持多生產者-單消費者模型,單消費者意味着Hudi暫時不支持文件的併發寫入。而對於生產消費的隊列的實現,Hudi並未僅僅只是基於LinkedBlockingQueue,而是採用了更精細化的速率控制,保證速率會隨着記錄負載大小的變化和配置的隊列緩存大小而動態變化,這也降低了系統發生OOM的概率。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

Alibaba Nacos 學習(五):K8S Nacos搭建,使用nfs

 

準備環境

Centos7  192.168.50.21 k8s-master 2G
Centos7  192.168.50.22 k8s-node01 2G
Centos7  192.168.50.23 k8s-node02 2G

K8S集群搭建參考 

 

master安裝好Git ,yum install git

master,node01,node02  安裝 nfs-utils

yum install nfs-utils

master,node01,node02添加nfs exports配置,為了解決後續的nfs報錯異常

/data/mysql-slave *(insecure,fsid=0,rw,async,no_root_squash)
/data/mysql-master *(insecure,fsid=0,rw,async,no_root_squash)
/data/nfs-share *(rw,fsid=0,sync,no_root_squash)
mysql-slave 數據庫從庫 
mysql-master 數據庫主庫
nfs-share nocas文件掛在目錄

後面的yml中會提到
master,node01,node02創建目錄
mkdir /data/mysql-slave
mkdir /data/mysql-master
mkdir /data/nfs-share 

 master 克隆代碼

   git clone https://github.com/nacos-group/nacos-k8s.git

克隆完成進入以下目錄

 cd /opt/nacos-k8s/deploy/

 

1.nfs安裝

kubectl create -f nfs/rbac.yaml 
kubectl create -f nfs/class.yaml 

修改nfs/deployment.yaml IP配置

 

 

 

kubectl create -f nfs/deployment.yaml

查看安裝狀態

kubectl get pod -l app=nfs-client-provisioner

 

 

 

2.mysql部署

cd /opt/nacos-k8s/deploy/mysql/

修改數據配置文件ip

vi mysql-master-nfs.yaml

 

 

 部署主庫

kubectl create -f mysql-master-nfs.yaml 

修改存庫ip

vi mysql-slave-nfs.yaml
kubectl create -f mysql-slave-nfs.yaml 

主從部署非常慢 耐心等待,如果報nfs相關的錯,重啟nfs即可

service nfs restart

 

 

3. 部署nacos

cd /opt/nacos-k8s/deploy/nacos/

 

 

 

 

 

kubectl create -f nacos-pvc-nfs.yaml 

 查看訪問端口

kubectl get svc|grep nacos

 

 

 

 

 查看K8S集群狀態

 

 Failed to pull image “nacos/nacos-server:latest”: rpc error: code = Unknown desc = context canceled

進去對應節點機器 ,拉取鏡像后,重新應用即可

kubectl apply -f

 4. 部署問題

部署過程中大部分都是NFS問題

可以參考

mount.nfs: No route to host
Warning FailedMount 100s (x5 over 10m) kubelet, node2 Unable to mount volumes for pod “nfs-client-provisioner-594f778474-whhb5_default(56aef93a-9d31-11e9-a4c4-00163e069f44)”: timeout expired waiting for volumes to attach or mount for pod “default”/”nfs-client-provisioner-594f778474-whhb5”. list of unmounted volumes=[nfs-client-root]. list of unattached volumes=[nfs-client-root nfs-client-provisioner-token-8dcrx]

修改deployment.yaml中server的IP地址為某個node節點的內網IP地址,圖1已標註

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

PHP 的 self 關鍵字用法

之前有人詢問 self 關鍵字的用法,答案是比較明顯的:靜態成員函數內不能用 this 調用非成員函數,但可以用 self 調用靜態成員函數/變量/常量;其他成員函數可以用 self 調用靜態成員函數以及非靜態成員函數。隨着討論的深入,發現 self 並沒有那麼簡單。鑒於此,本文先對幾個關鍵字做對比和區分,再總結 self 的用法。

parentstatic 以及 this 的區別

要想將徹底搞懂 self ,要與 parentstatic 以及 this 區分開。以下分別做對比。

parent

selfparent 的區分比較容易: parent 引用父類/基類被隱蓋的方法(或變量), self則引用自身方法(或變量)。例如構造函數中調用父類構造函數:

class Base {
    public function __construct() {
        echo "Base contructor!", PHP_EOL;
    }
}

class Child {
    public function __construct() {
        parent::__construct();
        echo "Child contructor!", PHP_EOL;
    }
}

new Child;
// 輸出:
// Base contructor!
// Child contructor!

 

static

static 常規用途是修飾函數或變量使其成為類函數和類變量,也可以修飾函數內變量延長其生命周期至整個應用程序的生命周期。但是其與 self 關聯上是PHP 5.3以來引入的新用途:靜態延遲綁定。

有了 static 的靜態延遲綁定功能,可以在運行時動態確定歸屬的類。例如:

class Base {
    public function __construct() {
        echo "Base constructor!", PHP_EOL;
    }

    public static function getSelf() {
        return new self();
    }

    public static function getInstance() {
        return new static();
    }

    public function selfFoo() {
        return self::foo();
    }

    public function staticFoo() {
        return static::foo();
    }

    public function thisFoo() {
        return $this->foo();
    }

    public function foo() {
        echo  "Base Foo!", PHP_EOL;
    }
}

class Child extends Base {
    public function __construct() {
        echo "Child constructor!", PHP_EOL;
    }

    public function foo() {
        echo "Child Foo!", PHP_EOL;
    }
}

$base = Child::getSelf();
$child = Child::getInstance();

$child->selfFoo();
$child->staticFoo();
$child->thisFoo();

 

程序輸出結果如下:

Base constructor!
Child constructor!
Base Foo!
Child Foo!
Child Foo!

 

在函數引用上, selfstatic 的區別是:對於靜態成員函數, self 指向代碼當前類, static 指向調用類;對於非靜態成員函數, self 抑制多態,指向當前類的成員函數, static 等同於 this ,動態指向調用類的函數。

parentselfstatic 三個關鍵字聯合在一起看挺有意思,分別指向父類、當前類、子類,有點“過去、現在、未來”的味道。

this

selfthis 是被討論最多,也是最容易引起誤用的組合。兩者的主要區別如下:

  1. this 不能用在靜態成員函數中, self 可以;
  2. 對靜態成員函數/變量的訪問, 建議 用 self ,不要用 $this::$this-> 的形式;
  3. 對非靜態成員變量的訪問,不能用 self ,只能用 this ;
  4. this 要在對象已經實例化的情況下使用, self 沒有此限制;
  5. 在非靜態成員函數內使用, self 抑制多態行為,引用當前類的函數;而 this 引用調用類的重寫(override)函數(如果有的話)。

self 的用途

看完與上述三個關鍵字的區別, self 的用途是不是呼之即出?一句話總結,那就是: self總是指向“當前類(及類實例)”。詳細說則是:

  1. 替代類名,引用當前類的靜態成員變量和靜態函數;
  2. 抑制多態行為,引用當前類的函數而非子類中覆蓋的實現;

槽點

  1. 這幾個關鍵字中,只有 this 要加 $ 符號且必須加,強迫症表示很難受;
  2. 靜態成員函數中不能通過 $this-> 調用非靜態成員函數,但是可以通過 self:: 調用,且在調用函數中未使用 $this-> 的情況下還能順暢運行。此行為貌似在不同PHP版本中表現不同,在當前的7.3中ok;
  3. 在靜態函數和非靜態函數中輸出 self ,猜猜結果是什麼?都是 string(4) "self" ,迷之輸出;
  4. return $this instanceof static::class; 會有語法錯誤,但是以下兩種寫法就正常:
    $class = static::class;
    return $this instanceof $class;
    // 或者這樣:
    return $this instanceof static;

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

特斯拉發表電動卡車 Cybertruck,一台從科幻電影走出來的鋼鐵車

今天的 Elon Musk 看起來不像鋼鐵人,更像蝙蝠俠,因為他們的新車 Cybertruck,不但外型酷似蝙蝠車,同時還能防彈防撞,並且擁有超越保時捷的加速度,跟勝過市面上卡車的拖吊能力,更驚人的是,售價只要 39,900 美元起。

眾所期待的特斯拉新車 Cybertruck 今日正式發表,和之前流出的影像不同,Cybertruck 酷似隱形戰機 F-117 的設計,讓人聯想到蝙蝠車,甚至懷疑這是不是一台防雷達偵測的戰車?

Tesla Cybertruck 全車採用冷鑄鋼板,能夠抵擋 9mm 口徑手槍的射擊,現場展示用重鎚敲擊也毫髮無傷;車窗玻璃同樣採用防彈設計,然而有趣的是,現場展示時,被大鐵球砸出了一片雪花。「至少,它沒被打穿,你坐在裡面很安全。」Elon Musk 笑著說。

Cybertruck 為了因應負重,搭載了適應性氣壓懸吊系統,針對高速公路,或是越野泥巴路,能夠自動調整懸吊高度,同時也順便使用這個氣壓系統,做了一個高壓出力裝置,使用者可以自行加裝不同氣壓工具,像是高壓水槍或是電鑽等。

當重裝電動機車開上後廂時,懸吊系統會自動調整車尾高度,讓車身保持平衡。

車尾與其他皮卡車開放式貨斗不同,Cybertruck 採用封閉式貨斗,並有升降式尾門,現場展示時,將這台電動機車 ATV 直接騎上貨斗後,還能直接充電,顯然是在致敬蝙蝠車跟蝙蝠機車。

Cybertruck 如同其他皮卡車,車尾裝有釣鉤,能夠充當拖車使用,而歸功於它的強力馬達,拖車能力屌打了皮卡車霸主 Ford F-150,在現場展示的影片中,特斯拉讓 Cybertruck 跟 F-150 互相拖住對方,進行拔河測試,結果 F-150 整台被 Cybertruck 拖走。

F-150 慘遭 Cybertruck 拖走。

馬斯克強調,一般皮卡車需要另外裝載發電機才能使用電動工具,Cybertruck 直接提供了電源,因此省下不少空間,同時還提供強大的拖力。

此外,做為一台卡車,Cybertruck 莫名其妙地擁有超越保時捷的加速度,根據現場公布數據,最頂級版的 0-100 公里加速時間不到 3 秒。現場展示了 Cybertruck 與 Porsche 911 賽跑的影片,起步雖然小輸一點,但隨後就超越了 911。

現場展示競速影片,大約 1 秒後,Cybertruck 就超過了 911。

Tesla Cybertruck 共有 3 種版本,依照馬達數量來分別,最低價 39,900 美元起,最高 69,900 美元。Cybertruck 從今天起在美國開放預購,實際交車時間預計要等到 2021 年底。頂級的三馬達款,更預計要等到 2022 年底才會開始生產。

如同馬斯克開場所說,卡車在過去幾十年來都長得差不多,特斯拉要打造一台完全不一樣的卡車,同時還要保持零排放,跟超高性能,從今天的現場展示來看,特斯拉再次完成一個不可能的任務。在興奮之餘也別忘了,這一切都是現場展示,實際上如何,就有待實際交車後驗證了!

(合作媒體:。圖片來源:)

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

【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象

[apue] 神奇的 Solaris pipe

說到 pipe 大家可能都不陌生,經典的pipe調用配合fork進行父子進程通訊,簡直就是Unix程序的標配。

然而Solaris上的pipe卻和Solaris一樣是個奇葩(雖然Solaris前途黯淡,但是不妨礙我們從它裏面挖掘一些有價值的東西),

有着和一般pipe諸多的不同之處,本文就來說說Solaris上神奇的pipe和一般pipe之間的異同。

 

1.solaris pipe 是全雙工的

一般系統上的pipe調用是半雙工的,只能單向傳遞數據,如果需要雙向通訊,我們一般是建兩個pipe分別讀寫。像下面這樣:

 1     int n, fd1[2], fd2[2]; 
 2     if (pipe (fd1) < 0 || pipe(fd2) < 0)
 3         err_sys ("pipe error"); 
 4 
 5     char line[MAXLINE]; 
 6     pid_t pid = fork (); 
 7     if (pid < 0) 
 8         err_sys ("fork error"); 
 9     else if (pid > 0)
10     {
11         close (fd1[0]);  // write on pipe1 as stdin for co-process
12         close (fd2[1]);  // read on pipe2 as stdout for co-process
13         while (fgets (line, MAXLINE, stdin) != NULL) { 
14             n = strlen (line); 
15             if (write (fd1[1], line, n) != n)
16                 err_sys ("write error to pipe"); 
17             if ((n = read (fd2[0], line, MAXLINE)) < 0)
18                 err_sys ("read error from pipe"); 
19 
20             if (n == 0) { 
21                 err_msg ("child closed pipe"); 
22                 break;
23             }
24             line[n] = 0; 
25             if (fputs (line, stdout) == EOF)
26                 err_sys ("fputs error"); 
27         }
28 
29         if (ferror (stdin))
30             err_sys ("fputs error"); 
31 
32         return 0; 
33     }
34     else { 
35         close (fd1[1]); 
36         close (fd2[0]); 
37         if (fd1[0] != STDIN_FILENO) { 
38             if (dup2 (fd1[0], STDIN_FILENO) != STDIN_FILENO)
39                 err_sys ("dup2 error to stdin"); 
40             close (fd1[0]); 
41         }
42 
43         if (fd2[1] != STDOUT_FILENO) { 
44             if (dup2 (fd2[1], STDOUT_FILENO) != STDOUT_FILENO)
45                 err_sys ("dup2 error to stdout"); 
46             close (fd2[1]); 
47         }
48 
49         if (execl (argv[1], "add2", (char *)0) < 0)
50             err_sys ("execl error"); 
51     }

這個程序創建兩個管道,fd1用來寫請求,fd2用來讀應答;對子進程而言,fd1重定向到標準輸入,fd2重定向到標準輸出,讀取stdin中的數據相加然後寫入stdout完成工作。父進程在取得應答後向標準輸出寫入結果。

如果在Solaris上,可以直接用一個pipe同時讀寫,代碼可以重寫成這樣:

 1 int fd[2];
 2 if (pipe(fd) < 0) 
 3     err_sys("pipe error\n");
 4 
 5 char line[MAXLINE];
 6 pid_t pid = fork();
 7 if (pid < 0)
 8     err_sys("fork error\n");
 9 else if (pid > 0)
10 {
11     close(fd[1]);
12     while (fgets(line, MAXLINE, stdin) != NULL) {
13         n = strlen(line);
14         if (write(fd[0], line, n) != n)
15             err_sys("write error to pipe\n")
16         if ((n = read(fd[0], line, MAXLINE)) < 0) 
17             err_sys("read error from pipe\n");
18 
19         if (n == 0) 
20             err_sys("child closed pipe\n");
21         line[n] = 0;
22         if (fputs(line, stdout) == EOF) 
23             err_sys("fputs error\n");
24     }
25 
26     if (ferror(stdin))
27         err_sys("fputs error\n");
28 
29     return 0;
30 }
31 else {
32     close(fd[0]);
33     if (fd[1] != STDIN_FILENO)
34         if (dup2(fd[1], STDIN_FILENO) != STDIN_FILENO)
35             err_sys("dup2 error to stdin\n");
36 
37     if (fd[1] != STDOUT_FILENO) {
38         if (dup2(fd[1], STDOUT_FILENO) != STDOUT_FILENO)
39             err_sys("dup2 error to stdout\n");
40         close(fd[1]);
41     }
42 
43     if (execl(argv[1], argv[2], (char *)0) < 0)
44         err_sys("execl error\n");
45 
46 }

代碼清爽多了,不用去考慮fd1[0]和fd2[1]是啥意思是一件很養腦的事。

不過這樣的代碼只能在Solaris上運行(聽說BSD也支持?),如果考慮到可移植性,還是寫上面的比較穩妥。

 

測試程序

 

 

2. solaris pipe 可以脫離父子關係建立

pipe 好用但是沒法脫離fork使用,一般的pipe如果想讓任意兩個進程通訊,得藉助它的變身fifo來實現。

關於FIFO,詳情可參考我之前寫的一篇文章:

 

而Solaris上的pipe沒這麼多事,加入兩個調用:fattach / fdetach,你就可以像使用FIFO一樣使用pipe了:

 1 int fd[2];
 2 if (pipe(fd) < 0)
 3     err_sys("pipe error\n");
 4 
 5 if (fattach(fd[1], "./pipe") < 0)
 6     err_sys("fattach error\n");
 7 
 8 printf("attach to file pipe ok\n");
 9 
10 close(fd[1]);
11 char line[MAXLINE];
12 while (fgets(line, MAXLINE, stdin) != NULL) {
13     n = strlen(line);
14     if (write(fd[0], line, n) != n)
15         err_sys("write error to pipe\n");
16     if ((n = read(fd[0], line, MAXLINE)) < 0)
17         err_sys("read error from pipe\n");
18 
19     if (n == 0) 
20         err_sys("child closed pipe\n");
21 
22     line[n] = 0;
23     if (fputs(line, stdout) == EOF)
24         err_sys("fputs error\n");
25 }
26 
27 if (ferror(stdin))
28     err_sys("fputs error\n");
29 
30 if (fdetach("./pipe") < 0)
31     err_sys("fdetach error\n");
32 
33 printf("detach from file pipe ok\n");

在pipe調用之後立即加入fattach調用,可以將管道關聯到文件系統的一個文件名上,該文件必需事先存在,且可讀可寫。

在fattach調用之前這個文件(./pipe)是個普通文件,打開讀寫都是磁盤IO;

在fattach調用之後,這個文件就變身成為一個管道了,打開讀寫都是內存流操作,且管道的另一端就是attach的那個進程。

子進程也需要改造一下,以便使用pipe通訊:

 1 int fd, n, int1, int2;
 2 char line[MAXLINE];
 3 fd = open("./pipe", O_RDWR);
 4 if (fd < 0)
 5     err_sys("open file pipe failed\n");
 6 
 7 printf("open file pipe ok, fd = %d\n", fd);
 8 while ((n = read(fd, line, MAXLINE)) > 0) {
 9     line[n] = 0;
10     if (sscanf(line, "%d%d", &int1, &int2) == 2) {
11         sprintf(line, "%d\n", int1 + int2);
12         n = strlen(line);
13         if (write(fd, line, n) != n)
14             err_sys("write error\n");
15 
16         printf("i am working on %s\n", line);
17     }
18     else {
19         if (write(fd, "invalid args\n", 13) != 13)
20             err_sys("write msg error\n");
21     }
22 }
23 
24 close(fd);

打開pipe就如同打開普通文件一樣,open直接搞定。當然前提是attach進程必需已經在運行。

當attach進程detach后,管道文件又將恢復它的本來面目。

 

脫離了父子關係的pipe其實可以建立多對一關係(多對多關係不可以,因為只能有一個進程attach)。

例如開4個cmd窗口,分別執行以下命令:

./padd2 abc
./add2
./add2
./add2

 向attach進程(padd2)發送9個計算請求后,可以看到輸出結果如下:

-bash-3.2$ ./padd2 abc
attach to file pipe ok
1 1
2
2 2
4
3 3 
6
4 4
8
5 5
10
6 6 
12
7 7 
14
8 8
16
9 9
18

 再回來看各個open管道的進程,輸出分別如下:

-bash-3.2$ ./add2
open file pipe ok, fd = 3
source: 1 1
i am working on 2
source: 4 4
i am working on 8
source: 7 7 
i am working on 14 

 

-bash-3.2$ ./add2
open file pipe ok, fd = 3
source: 2 2
i am working on 4
source: 5 5
i am working on 10
source: 9 9
i am working on 18 

 

-bash-3.2$ ./add2
open file pipe ok, fd = 3
source: 2 2
i am working on 4
source: 5 5
i am working on 10
source: 9 9
i am working on 18 

 

-bash-3.2$ ./add2
open file pipe ok, fd = 3
source: 3 3
i am working on 6
source: 6 6
i am working on 12
source: 8 8 
i am working on 16

 

可以發現一個很有趣的現象,就是各個add2進程基本是輪着來獲取請求的,可以猜想底層的pipe可能有一個進程排隊機制。

但是反過來使用pipe就不行了。就是說當啟動一個add3(區別於上例的add2與padd2)作為fattach端打開pipe,啟動多個padd3作為open端使用pipe,

然後通過命令行給padd3傳遞要相加的值,可以寫一個腳本同時啟動多個padd3,來查看效果:

#! /bin/sh
./padd3 1 1 &
./padd3 2 2 &
./padd3 3 3 &
./padd3 4 4 &

 這個腳本中啟動了4個加法進程,同時向add3發送4個加法請求,腳本中四個進程輸出如下:

-bash-3.2$ ./padd3.sh
-bash-3.2$ open file pipe ok, fd = 3
1 1 = 2
open file pipe ok, fd = 3
2 2 = 4
open file pipe ok, fd = 3
open file pipe ok, fd = 3
4 4 = 37

 可以看到3+3的請求被忽略了,轉到add3查看輸出:

-bash-3.2$ ./add3
attach to file pipe ok
source: 1 1
i am working on 1 + 1 = 2
source: 2 2
i am working on 2 + 2 = 4
source: 3 34 4
i am working on 3 + 34 = 37

 原來是3+3與4+4兩個請求粘連了,導致add3識別成一個3+34的請求,所以出錯了。

多運行幾遍腳本后,發現還有這樣的輸出:

-bash-3.2$ ./padd3.sh
-bash-3.2$ open file pipe ok, fd = 3
4 4 = 2
open file pipe ok, fd = 3
2 2 = 4
open file pipe ok, fd = 3
3 3 = 6
open file pipe ok, fd = 3
1 1 = 8

  4+4=2?1+1=8?再看add3這頭的輸出:

-bash-3.2$ ./add3
attach to file pipe ok
source: 1 1
i am working on 1 + 1 = 2
source: 2 2
i am working on 2 + 2 = 4
source: 3 3
i am working on 3 + 3 = 6
source: 4 4
i am working on 4 + 4 = 8

 完全正常呢。

經過一番推理,發現是4+4的請求取得了1+1請求的應答;1+1的請求取得了4+4的應答。

可見這樣的結構還有一個弊端,同時請求的進程可能無法得到自己的應答,應答與請求之間相互錯位。

所以想用fattach來實現多路請求的人還是洗洗睡吧,畢竟它就是一個pipe不是,還能給它整成tcp么?

而之前的例子可以,是因為請求是順序發送的,上個請求得到應答后才發送下個請求,所以不存在這個例子的問題(但是實用性也不高)。

 

測試程序

 

 

3. solaris pipe 可以通過connld模塊實現類似tcp的多路連接

第2條剛說不能實現多路連接,第3條就接着來打臉了,這是由於Solaris上的pipe都是基於STREAMS技術構建,

而STREAMS是支持靈活的PUSH、POP流處理模塊的,再加上STREAMS傳遞文件fd的能力,就可以支持類似tcp中accept的能力。

即每個open pipe文件的進程,得到的不是原來管道的fd,而是新創建管道的fd,而管道的另一側fd則通過已有的管道發送到attach進程,

後者使用這個新的fd與客戶進程通訊。為了支持多路連接,我們的代碼需要重新整理一下,首先看客戶端:

1 int fd;
2 char line[MAXLINE];
3 fd = cli_conn("./pipe");
4 if (fd < 0)
5     return 0;

這裏將open相關邏輯封裝到了cli_conn函數中,以便之後復用:

 1 int cli_conn(const char *name)
 2 {
 3     int fd;
 4     if ((fd = open(name, O_RDWR)) < 0) {
 5         printf("open pipe file failed\n");
 6         return -1;
 7     }
 8 
 9     if (isastream(fd) == 0) {
10         close(fd);
11         return -2;
12     }
13 
14     return fd;
15 }

可以看到與之前幾乎沒有變化,只是增加了isastream調用防止attach進程沒有啟動。

再來看下服務端:

 1 int listenfd = serv_listen("./pipe");
 2 if (listenfd < 0)
 3     return 0;
 4 
 5 int acceptfd = 0;
 6 int n = 0, int1 = 0, int2 = 0;
 7 char line[MAXLINE];
 8 uid_t uid = 0;
 9 while ((acceptfd = serv_accept(listenfd, &uid)) >= 0)
10 {
11     printf("accept a client, fd = %d, uid = %ld\n", acceptfd, uid);
12     while ((n = read(acceptfd, line, MAXLINE)) > 0) {
13         line[n] = 0;
14         printf("source: %s\n", line);
15         if (sscanf(line, "%d%d", &int1, &int2) == 2) {
16             sprintf(line, "%d\n", int1 + int2);
17             n = strlen(line);
18             if (write(acceptfd, line, n) != n) {
19                 printf("write error\n");
20                 return 0;
21             }
22             printf("i am working on %d + %d = %s\n", int1, int2, line);
23         }
24         else {
25             if (write(acceptfd, "invalid args\n", 13) != 13) {
26                 printf("write msg error\n");
27                 return 0;
28             }
29         }
30     }
31 
32     close(acceptfd);
33 }
34 
35 if (fdetach("./pipe") < 0) {
36     printf("fdetach error\n");
37     return 0;
38 }
39 
40 printf("detach from file pipe ok\n");
41 close(listenfd);

首先調用serv_listen建立基本pipe,然後不斷在該pipe上調用serv_accept來獲取獨立的客戶端連接。之後的邏輯與以前一樣。

現在重點看下封裝的這兩個方法:

 1 int serv_listen(const char *name)
 2 {
 3     int tempfd;
 4     int fd[2];
 5     unlink(name);
 6     tempfd = creat(name, FIFO_MODE);
 7     if (tempfd < 0) {
 8         printf("creat failed\n");
 9         return -1;
10     }
11 
12     if (close(tempfd) < 0) {
13         printf("close temp fd failed\n");
14         return -2;
15     }
16 
17     if (pipe(fd) < 0) {
18         printf("pipe error\n");
19         return -3;
20     }
21 
22     if (ioctl(fd[1], I_PUSH, "connld") < 0) {
23         printf("I_PUSH connld failed\n");
24         close(fd[0]);
25         close(fd[1]);
26         return -4;
27     }
28 
29     printf("push connld ok\n");
30     if (fattach(fd[1], name) < 0) {
31         printf("fattach error\n");
32         close(fd[0]);
33         close(fd[1]);
34         return -5;
35     }
36 
37     printf("attach to file pipe ok\n");
38     close(fd[1]);
39     return fd[0];
40 }

serv_listen封裝了與建立基本pipe相關的代碼,首先確保pipe文件存在且可讀寫,然後創建普通的pipe,在fattach調用之前必需先PUSH一個connld模塊到該pipe STREAM中。這樣就大功告成!

 1 int serv_accept(int listenfd, uid_t *uidptr)
 2 {
 3     struct strrecvfd recvfd;
 4     if (ioctl(listenfd, I_RECVFD, &recvfd) < 0) {
 5         printf("I_RECVFD from listen fd failed\n");
 6         return -1;
 7     }
 8 
 9     if (uidptr)
10         *uidptr = recvfd.uid;
11 
12     return recvfd.fd;
13 }

當有客戶端連接上來的時候,使用I_RECVFD接收connld返回的另一個pipe的fd。之後的數據將在該pipe進行。

看了看,感覺和tcp的listen與accept別無二致,看來天下武功,至精深處都是英雄所見略同。

之前的多個客戶端同時運行的例子再跑一遍,觀察attach端輸出:

-bash-3.2$ ./add4
push connld ok
attach to file pipe ok
accept a client, fd = 4, uid = 101
source: 1 1
i am working on 1 + 1 = 2
accept a client, fd = 4, uid = 101
source: 2 2
i am working on 2 + 2 = 4
accept a client, fd = 4, uid = 101
source: 3 3
i am working on 3 + 3 = 6
accept a client, fd = 4, uid = 101
source: 4 4
i am working on 4 + 4 = 8

 一切正常。再看下腳本中四個進程的輸出:

-bash-3.2$ ./padd4.sh
-bash-3.2$ open file pipe ok, fd = 3
1 1 = 2
open file pipe ok, fd = 3
2 2 = 4
open file pipe ok, fd = 3
3 3 = 6
open file pipe ok, fd = 3
4 4 = 8

 也是沒問題的,既沒有出現多個請求粘連的情況,也沒有出現請求與應答錯位的情況。

 

測試程序

 

 

4.結論

Solaris 上的pipe不僅可以全雙工通訊、不依賴父子進程關係,還可以實現類似tcp那樣分離多個客戶端通訊連接的能力。

雖然Solaris前途未卜,但是希望一些好的東西還是能流傳下來,就比如這個神奇的pipe。

 

看完今天的文章,你是不是對特立獨行的Solaris又加深了一層了解?歡迎留言區說說你認識的Solaris。

 

 

 

 

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理【其他文章推薦】

收購3c,收購IPHONE,收購蘋果電腦-詳細收購流程一覽表

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

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

※公開收購3c價格,不怕被賤賣!

※自行創業 缺乏曝光? 下一步"網站設計"幫您第一時間規劃公司的門面形象