YoyoGo基於ASP.NET Core設計的Golang實現

YoyoGo

YoyoGo 是一個用 Go 編寫的簡單,輕便,快速的 微服務框架,目前已實現了Web框架的能力,但是底層設計已支持多種服務架構。

Github

https://github.com/yoyofx/yoyogo

特色

  • 漂亮又快速的路由器
  • 中間件支持 (handler func & custom middleware)
  • 對 REST API 友好
  • 支持 MVC 模式
  • 受到許多出色的 Go Web 框架的啟發

框架安裝

go get github.com/yoyofx/yoyogo

安裝依賴 (由於某些原因國內下載不了依賴)

go version < 1.13

window 下在 cmd 中執行:
set GO111MODULE=on
set  GOPROXY=https://goproxy.cn

linux  下執行:
export GO111MODULE=on
export GOPROXY=https://goproxy.cn

go version >= 1.13

go env -w GOPROXY=https://goproxy.cn,direct

簡單的例子

package main
import ...

func main() {
    YoyoGo.CreateDefaultBuilder(func(router Router.IRouterBuilder) {
        router.GET("/info",func (ctx *Context.HttpContext) {    // 支持Group方式
            ctx.JSON(200, Context.M{"info": "ok"})
        })
    }).Build().Run()       //默認端口號 :8080
}

實現進度

標準功能

  • [X] 打印Logo和日誌(YoyoGo)
  • [X] 統一程序輸入參數和環境變量 (YoyoGo)
  • [X] 簡單路由器綁定句柄功能
  • [X] HttpContext 上下文封裝(請求,響應)
  • [X] 靜態文件端點(靜態文件服務器)
  • [X] JSON 序列化結構(Context.M)
  • [X] 獲取請求文件並保存
  • [X] 獲取請求數據(form-data,x-www-form-urlencoded,Json ,XML,Protobuf 等)
  • [X] Http 請求的綁定模型(Url, From,JSON,XML,Protobuf)

響應渲染功能

  • [X] Render Interface
  • [X] JSON Render
  • [X] JSONP Render
  • [X] Indented Json Render
  • [X] Secure Json Render
  • [X] Ascii Json Render
  • [X] Pure Json Render
  • [X] Binary Data Render
  • [X] TEXT
  • [X] Protobuf
  • [X] MessagePack
  • [X] XML
  • [X] YAML
  • [X] File
  • [X] Image
  • [X] Template
  • [X] Auto formater Render

中間件

  • [X] Logger
  • [X] StaticFile
  • [X] Router Middleware
  • [ ] Session
  • [ ] CORS
  • [ ] GZip
  • [X] Binding
  • [ ] Binding Valateion

路由

  • [x] GET,POST,HEAD,PUT,DELETE 方法支持
  • [x] 路由解析樹與表達式支持
  • [x] RouteData路由數據 (/api/:version/) 與 Binding的集成
  • [x] 路由組功能
  • [ ] MVC默認模板功能
  • [ ] 路由過濾器 Filter

MVC

  • [x] 路由請求觸發Controller&Action
  • [X] Action方法參數綁定
  • [ ] 內部對象的DI化
  • [ ] 關鍵對象的參數傳遞

Dependency injection

  • [X] 抽象集成第三方DI框架
  • [X] MVC模式集成
  • [X] 框架級的DI支持功能

擴展

  • [ ] 配置
  • [ ] WebSocket
  • [ ] JWT
  • [ ] swagger
  • [ ] GRpc
  • [ ] OAuth2
  • [ ] Prometheus
  • [ ] 安全

進階範例

package main
import ...

func main() {
	webHost := CreateCustomWebHostBuilder().Build()
	webHost.Run()
}

// 自定義HostBuilder並支持 MVC 和 自動參數綁定功能,簡單情況也可以直接使用CreateDefaultBuilder 。
func CreateCustomBuilder() *Abstractions.HostBuilder {
	return YoyoGo.NewWebHostBuilder().
		SetEnvironment(Context.Prod).
		UseFastHttp().
		//UseServer(YoyoGo.DefaultHttps(":8080", "./Certificate/server.pem", "./Certificate/server.key")).
		Configure(func(app *YoyoGo.WebApplicationBuilder) {
			app.UseStatic("Static")
			app.UseEndpoints(registerEndpointRouterConfig)
			app.UseMvc(func(builder *Mvc.ControllerBuilder) {
				builder.AddController(contollers.NewUserController)
			})
		}).
		ConfigureServices(func(serviceCollection *DependencyInjection.ServiceCollection) {
			serviceCollection.AddTransientByImplements(models.NewUserAction, new(models.IUserAction))
		}).
		OnApplicationLifeEvent(getApplicationLifeEvent)
}

//region endpoint 路由綁定函數
func registerEndpoints(router Router.IRouterBuilder) {
	router.GET("/error", func(ctx *Context.HttpContext) {
		panic("http get error")
	})

    //POST 請求: /info/:id ?q1=abc&username=123
	router.POST("/info/:id", func (ctx *Context.HttpContext) {
        qs_q1 := ctx.Query("q1")
        pd_name := ctx.Param("username")

        userInfo := &UserInfo{}
        
        _ = ctx.Bind(userInfo)    // 手動綁定請求對象

        strResult := fmt.Sprintf("Name:%s , Q1:%s , bind: %s", pd_name, qs_q1, userInfo)

        ctx.JSON(200, Std.M{"info": "hello world", "result": strResult})
    })

    // 路由組功能實現綁定 GET 請求:  /v1/api/info
	router.Group("/v1/api", func(router *Router.RouterGroup) {
		router.GET("/info", func (ctx *Context.HttpContext) {
	        ctx.JSON(200, Std.M{"info": "ok"})
        })
	})
    
    // GET 請求: HttpContext.RequiredServices獲取IOC對象
	router.GET("/ioc", func (ctx *Context.HttpContext) {
        var userAction models.IUserAction
        _ = ctx.RequiredServices.GetService(&userAction)
        ctx.JSON(200, Std.M{"info": "ok " + userAction.Login("zhang")})
    })
}

//endregion

//region 請求對象
type UserInfo struct {
	UserName string `param:"username"`
	Number   string `param:"q1"`
	Id       string `param:"id"`
}

// ----------------------------------------- MVC 定義 ------------------------------------------------------

// 定義Controller
type UserController struct {
	*Controller.ApiController
	userAction models.IUserAction    // IOC 對象參數
}

// 構造器依賴注入
func NewUserController(userAction models.IUserAction) *UserController {
	return &UserController{userAction: userAction}
}

// 請求對象的參數化綁定
type RegiserRequest struct {
	Controller.RequestParam
	UserName string `param:"username"`
	Password string `param:"password"`
}

// Register函數自動綁定參數
func (this *UserController) Register(ctx *Context.HttpContext, request *RegiserRequest) ActionResult.IActionResult {
	result := Controller.ApiResult{Success: true, Message: "ok", Data: request}
	return ActionResult.Json{Data: result}
}

// use userAction interface by ioc  
func (this *UserController) GetInfo() Controller.ApiResult {
	return this.OK(this.userAction.Login("zhang"))
}


// Web程序的開始與停止事件
func fireApplicationLifeEvent(life *YoyoGo.ApplicationLife) {
	printDataEvent := func(event YoyoGo.ApplicationEvent) {
		fmt.Printf("[yoyogo] Topic: %s; Event: %v\n", event.Topic, event.Data)
	}
	for {
		select {
		case ev := <-life.ApplicationStarted:
			go printDataEvent(ev)
		case ev := <-life.ApplicationStopped:
			go printDataEvent(ev)
			break
		}
	}
}

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

理解C#中的ValueTask

原文:https://devblogs.microsoft.com/dotnet/understanding-the-whys-whats-and-whens-of-valuetask/
作者:Stephen
翻譯:xiaoxiaotank
備註:本文要求讀者對Task有一定的了解,文章文字描述較多,但內容十分充實,相信你認真閱讀後,一定讓你受益匪淺。

前言

Task類是在.NET Framework 4引入的,位於System.Threading.Tasks命名空間下,它與派生的泛型類Task<TResult>已然成為.NET編程的主力,也是以async/await(C# 5引入的)語法糖為代表的異步編程模型的核心。

隨後,我會向大家介紹.NET Core 2.0中的新成員ValueTask/ValueTask<TResult>,來幫助你在日常開發用例中降低內存分配開銷,提升異步性能。

Task

雖然Task的用法有很多,但其最核心的是“承諾(promise)”,用來表示某個操作最終完成。

當你初始化一個操作后,會獲取一個與該操作相關的Task,當這個操作完成時,Task也同樣會完成。這個操作的完成情況可能有以下幾種:

  • 作為初始化操作的一部分同步完成,例如:訪問一些已被緩存的數據
  • 恰好在你獲取到Task實例的時候異步完成,例如:訪問雖然沒被緩存但是訪問速度非常快的數據
  • 你已經獲取到了Task實例,並等待了一段時間后,才異步完成,例如:訪問一些網絡數據

由於操作可能會異步完成,所以當你想要使用最終結果時,你可以通過阻塞來等待結果返回(不過這違背了異步操作的初衷);或者,使用回調方法,它會在操作完成時被調用,.NET 4通過Task.ContinueWith方法顯式實現了這個回調方法,如:

SomeOperationAsync().ContinueWith(task =>
{
    try
    {
        TResult result = task.Result;
        UseResult(result);
    }
    catch(Exception ex)
    {
        HandleException(ex);
    }
})

而在.NET 4.5中,Task通過結合await,大大簡化了對異步操作結果的使用,它能夠優化上面說的所有情況,無論操作是同步完成、快速異步完成還是已經(隱式地)提供回調之後異步完成,都不在話下,寫法如下:

TResult result = await SomeOperationAsync();
UseResult(result);

Task作為一個類(class),非常靈活,並因此帶來了很多好處。例如:

  • 它可以被任意數量的調用者併發await多次
  • 你可以把它存儲到字典中,以便任意數量的後續使用者對其進行await,進而把這個字典當成異步結果的緩存
  • 如果需要的話,你可以通過阻塞等待操作完成
  • 另外,你還可以對Task使用各種各樣的操作(稱為“組合器”,combinators),例如使用Task.WhenAny異步等待任意一個操作率先完成。

不過,在大多數情況下其實用不到這種靈活性,只需要簡單地調用異步操作並await獲取結果就好了:

TResult result = await SomeOperationAsync();
UseResult(result);

在這種用法中,我們不需要多次await task,不需要處理併發await,不需要處理同步阻塞,也不需要編寫組合器,我們只是異步等待操作的結果。這就是我們編寫同步代碼(例如TResult result = SomeOperation())的方式,它很自然地轉換為了async/await的方式。

此外,Task也確實存在潛在缺陷,特別是在需要創建大量Task實例且要求高吞吐量和高性能的場景下。Task 是一個類(class),作為一個類,這意味着每創建一個操作,都需要分配一個對象,而且分配的對象越多,垃圾回收器(GC)的工作量也會越大,我們花在這個上面的資源也就越多,本來這些資源可以用於做其他事情。慶幸的是,運行時(Runtime)和核心庫在許多情況下都可以緩解這種情況。

例如,你寫了如下方法:

public async Task WriteAsync(byte value)
{
    if (_bufferedCount == _buffer.Length)
    {
        await FlushAsync();
    }
    _buffer[_bufferedCount++] = value;
}

一般來說,緩衝區中會有可用空間,也就無需Flush,這樣操作就會同步完成。這時,不需要Task返回任何特殊信息,因為沒有返回值,返回Task與同步方法返回void沒什麼區別。因此,運行時可以簡單地緩存單個非泛型Task,並將其反覆用作任何同步完成的方法的結果(該單例是通過Task.CompletedTask公開的)。

或者,你的方法是這樣的:

public async Task<bool> MoveNextAsync()
{
    if (_bufferedCount == 0)
    {
        // 緩存數據
        await FillBuffer();
    }
    return _bufferedCount > 0;
}

一般來說,我們想的是會有一些緩存數據,這樣_bufferedCount就不會等於0,直接返回true就可以了;只有當沒有緩存數據(即_bufferedCount == 0)時,才需要執行可能異步完成的操作。而且,由於只有truefalse這兩種可能的結果,所以只需要兩個Task<bool>對象來分別表示truefalse,因此運行時可以將這兩個對象緩存下來,避免內存分配。只有當操作異步完成時,該方法才需要分配新的Task<bool>,因為調用方在知道操作結果之前,就要得到Task<bool>對象,並且要求該對象是唯一的,這樣在操作完成后,就可以將結果存儲到該對象中。

運行時也為其他類型型維護了一個類似的小型緩存,但是想要緩存所有內容是不切實際的。例如下面這個方法:

public async Task<int> ReadNextByteAsync()
{
    if (_bufferedCount == 0)
    {
        await FillBuffer();
    }

    if (_bufferedCount == 0)
    {
        return -1;
    }

    _bufferedCount--;
    return _buffer[_position++];
}

通常情況下,上面的案例也會同步完成。但是與上一個返回Task<bool>的案例不同,該方法返回的Int32的可能值約有40億個結果,如果將它們都緩存下來,大概會消耗數百GB的內存。雖然運行時保留了一個小型緩存,但也只保留了一小部分結果值,因此,如果該方法同步完成(緩衝區中有數據)的返回值是4,它會返回緩存的Task<int>,但是如果它同步完成的返回值是42,那就會分配一個新的Task<int>,相當於調用了Task.FromResult(42)

許多框架庫的實現也嘗試通過維護自己的緩存來進一步緩解這種情況。例如,.NET Framework 4.5中引入的MemoryStream.ReadAsync重載方法總是會同步完成,因為它只從內存中讀取數據。它返回一個Task<int>對象,其中Int32結果表示讀取的字節數。ReadAsync常常用在循環中,並且每次調用時請求的字節數是相同的(僅讀取到數據末尾時才有可能不同)。因此,重複調用通常會返回同步結果,其結果與上一次調用相同。這樣,可以維護單個Task實例的緩存,即緩存最後一次成功返回的Task實例。然後在後續調用中,如果新結果與其緩存的結果相匹配,它還是返回緩存的Task實例;否則,它會創建一個新的Task實例,並把它作為新的緩存Task,然後將其返回。

即使這樣,在許多操作同步完成的情況下,仍需強制分配Task<TResult>實例並返回。

同步完成時的ValueTask

正因如此,在.NET Core 2.0 中引入了一個新類型——ValueTask<TResult>,用來優化性能。之前的.NET版本可以通過引用NuGet包使用:System.Threading.Tasks.Extensions

ValueTask<TResult>是一個結構體(struct),用來包裝TResultTask<TResult>,因此它可以從異步方法中返回。並且,如果方法是同步成功完成的,則不需要分配任何東西:我們可以簡單地使用TResult來初始化ValueTask<TResult>並返回它。只有當方法異步完成時,才需要分配一個Task<TResult>實例,並使用ValueTask<TResult>來包裝該實例。另外,為了使ValueTask<TResult>更加輕量化,併為成功情形進行優化,所以拋出未處理異常的異步方法也會分配一個Task<TResult>實例,以方便ValueTask<TResult>包裝Task<TResult>,而不是增加一個附加字段來存儲異常(Exception)。

這樣,像MemoryStream.ReadAsync這類方法將返回ValueTask<int>而不需要關注緩存,現在可以使用以下代碼:

public override ValueTask<int> ReadAsync(byte[] buffer, int offset, int count)
{
    try
    {
        int bytesRead = Read(buffer, offset, count);
        return new ValueTask<int>(bytesRead);
    }
    catch (Exception e)
    {
        return new ValueTask<int>(Task.FromException<int>(e));
    }
}

異步完成時的ValueTask

能夠編寫出在同步完成時無需為結果類型產生額外內存分配的異步方法是一項很大的突破,.NET Core 2.0引入ValueTask<TResult>的目的,就是將頻繁使用的新方法定義為返回ValueTask<TResult>而不是Task<TResult>

例如,我們在.NET Core 2.1中的Stream類中添加了新的ReadAsync重載方法,以傳遞Memory<byte>來替代byte[],該方法的返回類型就是ValueTask<int>。這樣,Streams(一般都有一種同步完成的ReadAsync方法,如前面的MemoryStream示例中所示)現在可以在使用過程中更少的分配內存。

但是,在處理高吞吐量服務時,我們依舊需要考慮如何盡可能地避免額外內存分配,這就要想辦法減少或消除異步完成時的內存分配。

使用await異步編程模型時,對於任何異步完成的操作,我們都需要返回代表該操作最終完成的對象:調用者需要能夠傳遞在操作完成時調用的回調方法,這就要求在堆上有一個唯一的對象,用作這種特定操作的管道,但是,這並不意味着有關操作完成后能否重用該對象的任何信息。如果對象可以重複使用,則API可以維護一個或多個此類對象的緩存,並將其復用於序列化操作,也就是說,它不能將同一對象用於多個同時進行中的異步操作,但可以復用於非并行訪問下的對象。

在.NET Core 2.1中,為了支持這種池化和復用,ValueTask<TResult>進行了增強,不僅可以包裝TResultTask<TResult>,還可以包裝新引入的接口IValueTaskSource<TResult>。類似於Task<TResult>IValueTaskSource<TResult>提供表示異步操作所需的核心支持;

public interface IValueTaskSource<out TResult>
{
    ValueTaskSourceStatus GetStatus(short token);
    void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags);
    TResult GetResult(short token);
}
  • GetStatus用於實現諸如ValueTask<TResult>.IsCompleted之類的屬性,返回指示異步操作是否仍在掛起或是否已完成以及完成情況(成功或失敗)的指示。
  • OnCompleted用於ValueTask<TResult>的等待者(awaiter),它與調用者提供的回調方法掛鈎,當異步操作完成時,等待者繼續執行回調方法。
  • GetResult用於檢索操作的結果,以便在操作完成后,等待者可以獲取TResult或傳播可能發生的任何異常。

大多數開發人員永遠都不需要用到此接口(指IValueTaskSource<TResult>):方法只是簡單地將包裝該接口實例的ValueTask<TResult>實例返回給調用者,而調用者並不需要知道內部細節。該接口的主要作用是為了讓開發人員在編寫性能敏感的API時可以盡可能地避免額外內存分配。

.NET Core 2.1中有幾個類似的API。最值得關注的是Socket.ReceiveAsyncSocket.SendAsync,添加了新的重載,例如:

public ValueTask<int> ReceiveAsync(Memory<byte> buffer, SocketFlags socketFlags, CancellationToken cancellationToken = default);

此重載返回ValueTask<int>

如果操作同步完成,則可以簡單地構造具有正確結果的ValueTask<int>,例如:

int result = …;
return new ValueTask<int>(result);

如果它異步完成,則可以使用實現此接口的池對象:

IValueTaskSource<int> vts = …;
return new ValueTask<int>(vts);

Socket實現維護了兩個這樣的池對象,一個用於Receive,一個用於Send,這樣,每次未完成的對象只要不超過一個,即使這些重載是異步完成的,它們最終也不會額外分配內存。NetworkStream也因此受益。

例如,在.NET Core 2.1中,Stream公開了一個方法:

public virtual ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default);

NetworkStream的重載方法NetworkStream.ReadAsync,內部實際邏輯只是交給了Socket.ReceiveAsync去處理,所以將優勢從Socket帶到了NetworkStream中,使得NetworkStream.ReadAsync也有效地不進行額外內存分配了。

非泛型的ValueTask

當在.NET Core 2.0中引入ValueTask<TResult>時,它純粹是為了優化異步方法同步完成的情況——避免必須分配一個Task<TResult>實例用於存儲TResult。這也意味着非泛型的ValueTask是不必要的(因為沒有TResult):對於同步完成的情況,返回值為Task的方法可以返回Task.CompletedTask單例,此單例由async Task方法的運行時隱式返回。

然而,隨着即使異步完成也要避免額外內存分配需求的出現,非泛型的ValueTask又變得必不可少。因此,在.NET Core 2.1中,我們還引入了非泛型的ValueTaskIValueTaskSource。它們提供泛型版本對應的非泛型版本,使用方式類似,只是GetResult返回void

實現IValueTaskSource / IValueTaskSource

大多數開發人員都不需要實現這兩個接口,它們也不是特別容易實現。如果您需要的話,.NET Core 2.1的內部有幾種實現可以用作參考,例如

  • AwaitableSocketAsyncEventArgs
  • AsyncOperation
  • DefaultPipeReader

為了使想要這樣做的開發人員更輕鬆地進行開發,將在.NET Core 3.0中計劃引入ManualResetValueTaskSourceCore<TResult>結構體(譯註:目前已引入),用於實現接口的所有邏輯,並可以被包裝到其他實現了IValueTaskSourceIValueTaskSource<TResult>的包裝器對象中,這個包裝器對象只需要單純地將大部分實現交給該結構體就可以了。

ValueTask的有效消費模式

從表面上看,ValueTaskValueTask<TResult>的使用限制要比TaskTask<TResult>大得多 。不過沒關係,這甚至就是我們想要的,因為主要的消費方式就是簡單地await它們。

但是,由於ValueTaskValueTask<TResult>可能包裝可復用的對象,因此,與TaskTask<TResult>相比,如果調用者偏離了僅await它們的設計目的,則它們在使用上實際回受到很大的限制。通常,以下操作絕對不能用在ValueTask/ValueTask<TResult>上:

  • await ValueTask/ValueTask<TResult>多次。

    因為底層對象可能已經被回收了,並已由其他操作使用。而Task/Task<TResult>永遠不會從完成狀態轉換為未完成狀態,因此您可以根據需要等待多次,並且每次都會得到相同的結果。

  • 併發await ValueTask/ValueTask<TResult>

    底層對象期望一次只有單個調用者的單個回調來使用,並且嘗試同時等待它可能很容易引入競爭條件和細微的程序錯誤。這也是第一個錯誤操作的一個更具體的情況——await ValueTask/ValueTask<TResult>多次。相反,Task/Task<TResult>支持任意數量的併發等待

  • 使用.GetAwaiter().GetResult()時操作尚未完成。

    IValueTaskSource / IValueTaskSource<TResult>接口的實現中,在操作完成前是沒有強制要求支持阻塞的,並且很可能不會支持,所以這種操作本質上是一種競爭狀態,也不可能按照調用方的意願去執行。相反,Task/Task<TResult>支持此功能,可以阻塞調用者,直到任務完成。

如果您使用ValueTask/ValueTask<TResult>,並且您確實需要執行上述任一操作,則應使用.AsTask()獲取Task/Task<TResult>實例,然後對該實例進行操作。並且,在之後的代碼中您再也不應該與該ValueTask/ValueTask<TResult>進行交互。
簡單說就是使用ValueTask/ValueTask<TResult>時,您應該直接await它(可以有選擇地加上.ConfigureAwait(false)),或直接調用AsTask()且再也不要使用它,例如:

// 以這個方法為例
public ValueTask<int> SomeValueTaskReturningMethodAsync();
…
// GOOD
int result = await SomeValueTaskReturningMethodAsync();

// GOOD
int result = await SomeValueTaskReturningMethodAsync().ConfigureAwait(false);

// GOOD
Task<int> t = SomeValueTaskReturningMethodAsync().AsTask();

// WARNING
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
... // 將實例存儲到本地會使它被濫用的可能性更大,
    // 不過這還好,適當使用沒啥問題

// BAD: await 多次
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
int result = await vt;
int result2 = await vt;

// BAD: 併發 await (and, by definition then, multiple times)
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
Task.Run(async () => await vt);
Task.Run(async () => await vt);

// BAD: 在不清楚操作是否完成的情況下使用 GetAwaiter().GetResult()
ValueTask<int> vt = SomeValueTaskReturningMethodAsync();
int result = vt.GetAwaiter().GetResult();

另外,開發人員可以選擇使用另一種高級模式,最好你在衡量后確定它可以帶來好處之後再使用。具體來說,ValueTask/ValueTask<TResult>確實公開了一些與操作的當前狀態有關的屬性,例如:

  • IsCompleted,如果操作尚未完成,則返回false;如果操作已完成,則返回true(這意味着該操作不再運行,並且可能已經成功完成或以其他方式完成)
  • IsCompletedSuccessfully,當且僅當它已完成並成功完成才返回true(意味着嘗試等待它或訪問其結果不會導致引發異常)

舉個例子,對於一些執行非常頻繁的代碼,想要避免在異步執行時進行額外的性能損耗,並在某個本質上會使ValueTask/ValueTask<TResult>不再使用的操作(如await.AsTask())時,可以先檢查這些屬性。例如,在 .NET Core 2.1的SocketsHttpHandler實現中,代碼在連接上發出讀操作,並返回一個ValueTask<int>實例。如果該操作同步完成,那麼我們不用關注能否取消該操作。但是,如果它異步完成,在運行時就要發出取消請求,這樣取消請求會將連接斷開。由於這是一個非常常用的代碼,並且通過分析表明這樣做的確有細微差別,因此代碼的結構基本上如下:

int bytesRead;
{
    ValueTask<int> readTask = _connection.ReadAsync(buffer);
    if (readTask.IsCompletedSuccessfully)
    {
        bytesRead = readTask.Result;
    }
    else
    {
        using (_connection.RegisterCancellation())
        {
            bytesRead = await readTask;
        }
    }
}

這種模式是可以接受的,因為在ValueTask<int>Result被訪問或自身被await之後,不會再被使用了。

新異步API都應返回ValueTask / ValueTask 嗎?

當然不是,Task/Task<TResult>仍然是默認選擇

正如上文所強調的那樣,Task/Task<TResult>ValueTask/ValueTask<TResult>更加容易正確使用,所以除非對性能的影響大於可用性的影響,否則Task/Task<TResult>仍然是最優的。

此外,返回ValueTask<TResult>會比返回Task<TResult>多一些小開銷,例如,await Task<TResult>await ValueTask<TResult>會更快一些,所以如果你可以使用緩存的Task實例(例如,你的API返回TaskTask<bool>),你或許應該為了更好地性能而仍使用TaskTask<bool>。而且,ValueTask/ValueTask<TResult>相比Task/Task<TResult>有更多的字段,所以當它們被await、並將它們的字段存儲在調用異步方法的狀態機中時,它們會在該狀態機對象中佔用更多的空間。

但是,如果是以下情況,那你應該使用ValueTask/ValueTask<TResult>

  1. 你希望API的調用者只能直接await
  2. 避免額外的內存分配的開銷對API很重要
  3. 你預期該API常常是同步完成的,或者在異步完成時你可以有效地池化對象。

在添加抽象、虛擬或接口方法時,您還需要考慮這些方法的重載/實現是否存在這些情況。

ValueTask和ValueTask 的下一步是什麼?

對於.NET Core庫,我們將依然會看到新的API被添加進來,其返回值是Task/Task<TResult>,但在適當的地方,我們也將看到添加了新的以ValueTask/ValueTask<TResult>為返回值的API。

ValueTask/ValueTask<TResult>的一個關鍵例子就是在.NET Core 3.0添加新的IAsyncEnumerator<T>支持。IEnumerator<T>公開了一個返回boolMoveNext方法,異步IAsyncEnumerator<T>則會公開一個MoveNextAsync方法。剛開始設計此功能時,我們認為MoveNextAsync 應返回Task<bool>,一般情況下,通過緩存的Task<bool>在同步完成時可以非常高效地執行此操作。但是,考慮到我們期望的異步枚舉的廣泛性,並且考慮到它們基於是基於接口的,其可能有許多不同的實現方式(其中一些可能會非常關注性能和內存分配),並且鑒於絕大多數的消費者將通過await foreach來使用,我們決定MoveNextAsync返回ValueTask<bool>。這樣既可以使同步完成案例變得很快,又可以使用可重用的對象來使異步完成案例的內存分配也減少。實際上,在實現異步迭代器時,C#編譯器會利用此優勢,以使異步迭代器盡可能免於額外內存分配。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

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

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

東京奧運聖火傳遞福島起點 被查出輻射量異常

摘錄自2019年12月4日自由時報報導

日本東京奧運聖火明年3月底從福島縣的足球國練中心「J-VILLAGE」出發,但日本環境省今(4日)透露,「J-VILLAGE」相鄰的停車場有部分區域空間輻射量較高,當局已要求東京電力公司再次去污。

J-VILLAGE位於福島縣濱通南部,2011年福島核災發生後,由於此處距離福島核一廠僅有20公里,被政府借用為核災事故處理的對應據點,該中心今年4月下旬才全面恢復營運,因具有災區重建的重大象徵意義,所以被選為聖火傳遞的起點。

根據《共同社》報導,環保團體綠色和平組織10月對J-VILLAGE周圍展開調查,發現異常的輻射量,隨後將結果送交環境省。

環境省表示,空間輻射量較高的是與J-VILLAGE相鄰的楢葉町營停車場部分區域,已要求東電對該地區未經鋪設的地面再次去污。東電3日去除了周圍約0.03立方米的土和草,調查放射性物質的種類等。

根據東電的調查,在去污地區1公尺高的位置測得每小時1.79微西弗的輻射量,超過日本政府訂定的0.23微西弗的去污標準。地表輻射量為70.2微西弗。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

北極熊身上遭噴黑字「T-34」 專家震驚:失去保護色恐難覓食

摘錄自2019年12月4日上報報導

隨著全球暖化加劇,近幾個月以來常見北極熊出現在俄羅斯北方。但是近期在社群媒流傳的影片可見,有隻北極熊身體側邊被噴上黑色字母和數字「T-34」,目前專家正在調查這部影片的確切拍攝地點是否位於俄羅斯,並且警告這樣的行為可能會影響北極熊的生活技能,使牠們失去原有的保護色,難以在白雪覆蓋的地區捕獵食物。

《英國廣播公司》(BBC)報導,這部影片被世界自然基金會(World Wildlife Fund,WWF)成員卡夫里(Sergey Kavry)發布在Facebook,之後又被當地媒體廣泛分享。卡夫里說,這部影片被分享在通訊軟體WhatsApp的一個俄羅斯東部楚科奇自治區(Chukotka)當地居民的群組裡,而負責監控野生動物的科學家也不會以噴漆的方式將北極熊進行編號。

由於「T-34」戰車是蘇聯在第二次世界大戰(World War Two)戰勝德國納粹的重要關鍵,卡夫里說:「我不知道這段影片究竟是在哪個區域、地區,或哪裡的附近拍攝。如果這是個傳遞軍事訊息的舉動……某種程度來說是對歷史不尊重。」

俄羅斯北方生態問題研究所(Institute Of The Biological Problems Of The North)科學家科赫涅夫(Anatoly Kochnev)指出,北極熊不太可能在未被麻醉的情況下,任人在牠身上噴字。

科赫涅夫提到,這隻北極熊被噴漆的當下可能無法活動,或是可能靜止不動,因為牠被噴上的字樣均勻,而且大小相同。此外,他認為這可能發生在俄羅斯北部地區新地島(Novaya Zemlya),因為先前有專家團隊將北極熊進行麻醉,阻止牠們在人類居住地遊蕩。

科赫涅夫說,如今要將這隻北極熊的噴漆洗掉可能要花上好幾週的時間,而且這會對牠的白色保護色造成影響,使牠難以在冰雪覆蓋的地方順利捕食獵物。

俄羅斯媒體推測,在北極熊身上噴漆的舉動,可能是基於附近居民對於許多北極熊屢次闖入人類居住地的恐懼與不滿。隨著全球暖化日漸加劇,新地島2月宣布進入緊急狀態,因為有超過50隻北極熊出現在該地區,牠們在人口密集居住的建築物周遭漫步、尋找食物。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

北美鳥兒越變越小隻 專家:都是暖化惹的禍

摘錄自2019年12月5日中央通訊社報導

每年春秋遷徙季都有鳥兒撞上美國第3大城芝加哥的高樓大廈而死,研究人員1978年起年年撿拾鳥屍並逐年測量記錄,意外發現鳥兒體型越變越小。

他們研究7萬716隻在1978年至2016年間撞死的鳥兒,發現鳥兒平均體型逐年縮水,翼展卻變長了。

研究人員說,這顯示氣候暖化造成北美特定鳥種尺寸縮小,而全世界各地情況或許都是這樣。他們引述物種在較暖地區體型通常較小而在較冷地區體型通常較大的所謂「伯格曼法則」(Bergmann’s rule),來解釋物種或許會隨氣溫升高逐漸變小的現象。

這份研究著重在52種鳥兒,大部分是鳴禽,包括麻雀、黃鶯、畫眉等在北美成長,在芝加哥南部過冬的鳥種。根據研究人員的測量與秤重,過去這40年間,所有52種鳥兒都越來越小隻,平均身體質量減少2.6%,腿骨長減少2.4%,但翼展增加了1.3%,或許就是這些鳥兒體型變小仍可長途遷徙的原因。

密西根大學(University of Michigan)環境與永續學院(School for Environment and Sustainability)生物學家威克斯(Brian Weeks)說:「換言之,氣候變遷似乎改變了這些物種的大小和外型。」

負責測量鳥兒的芝加哥菲爾德自然史博物館(Field Museum)榮譽館藏管理者鮑以爾(Dave Willard)也說:「大家幾乎都同意氣候越來越暖,但自然界受影響的實例現在才要漸漸浮現。」研究刊登在科學期刊「生態學報告」(Ecology Letters),威克斯是報告主要執筆人。

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

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

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

奧迪2018年量產首款純電動SUV汽車

奧迪準備2018年在布魯塞爾量產首款純電動SUV汽車。布魯塞爾的奧迪工廠會同時製造汽車和電池,它還要向大眾其它汽車提供電池。

在2015年舉行的法蘭克福汽車展上,奧迪展示了e-tron概念車,它預示著奧迪即將推出量產版本的SUV,汽車取名為“Q6 e-tron”。

Q6 e-tron配有3個電動機,一個位於前軸,兩個放在後面。大型電池組安裝在前後軸之間,位於乘客座位的下方,這樣可以降低重心,提供更好的平衡性。

Q6 e-tron安裝的電池來自韓國LG化學和三星SDI,充電一次可以行駛約500千米。資料是以歐洲測試週期作為標準的,如果用美國環境保護署(EPA)的標準測試里程會短一些。因此,Q6 e-tron充電一次的行駛距離估計為390千米,這個資料更為合理一些。

目前布魯塞爾的奧迪工廠主要負責生產A1,奧迪會將A1生產線轉移到西班牙工廠,讓布魯塞爾負責生產Q6 e-tron。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

英國計畫開發可以同時照明和給電動汽車充電的街燈

英國政府日前給倫敦市劃撥了4000萬英鎊資金,供其開發可以同時照明和給電動汽車充電的街燈。

現在尚不清楚這種街燈將會是什麼樣子的,也不清楚它們將於何時推出。

寶馬公司也在努力競標成為這個項目的贊助商。但是,倫敦的這個街燈項目尚未宣佈任何具體計畫。

倫敦還斥資1300萬英鎊啟動了“未來鄉鄰”(neighborhoodsofthefuture)計畫。該計畫旨在給電動汽車司機提供免費的停車位以及交通優先權,例如可以使用公共汽車道,以鼓勵更多的人購買這種更環保的汽車。

此外,倫敦當局證實,到2020年,該市將會出現3000輛混合動力巴士和300輛零排放單層巴士;而且,該市將會撥款60萬英鎊,讓倫敦的消防隊全部配備電動汽車。

英國總共挑選了4個城市做試點,給它們分別撥款數百萬英鎊讓其推行電動汽車。其中布里斯托爾市還推出了一項為期四周的先試駕再購買的活動,以促使對電動汽車有興趣的人進行購買。

英國還劃撥了500萬英鎊研發資金,用於開發其他相關專案。例如,約克市的蓋有太陽頂棚的“停放小轎車換乘公共汽車進城”(parkandride)中心。它的設計就是為了減少空氣污染。

英國還有一個6000萬英鎊的專案,該專案包括有一個名為goultralowcities的基金。該基金旨在支持電動汽車、投資低排放公共交通以及研發超長續航能力的電池。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

FastDFS圖片服務器單機安裝步驟(修訂版)

前面已經講 ,通過此文章可以了解FastDFS組件中單機安裝流程。

單機版架構圖

以下為單機FastDFS安裝步驟

一、環境準備

CentOS 7.X

使用的版本: libfastcommon-1.0.41.tar.gz

使用的版本: fastdfs-6.01.tar.gz

使用的版本:fastdfs-nginx-module-1.21.tar.gz

使用的版本: nginx-1.16.1.tar.gz

二、安裝過程

1、安裝 libfastcommon-1.0.41.tar.gz

tar -zxvf libfastcommon-1.0.41.tar.gz
cd libfastcommon-1.0.41/
./make.sh
./make.sh install

2、安裝 FastDFS

tar -zxvf  fastdfs-6.01.tar.gz
cd fastdfs-6.01/
./make.sh
./make.sh install

準備配置文件

cp /etc/fdfs/tracker.conf.sample /etc/fdfs/tracker.conf
cp /etc/fdfs/storage.conf.sample /etc/fdfs/storage.conf
cp /etc/fdfs/client.conf.sample /etc/fdfs/client.conf
cd /opt/apps/fastdfs-6.01/conf
cp http.conf mime.types /etc/fdfs/

Tracker Server 配置

vim /etc/fdfs/tracker.conf
修改配置如下:
#tracker server端口號
port=22122
#存儲日誌和數據的根目錄
base_path=/opt/fastdfs/tracker
#HTTP服務端口
http.server_port=80
開放防火牆端口

1、打開跟蹤端口

vim /etc/sysconfig/iptables

2、添加以下端口行:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 22122 -j ACCEPT

3、重啟防火牆

service iptables restart
啟動Tracker
/etc/init.d/fdfs_trackerd start

Storage Server 配置

vim /etc/fdfs/storage.conf
修改配置如下:
#storage server端口號
port=23000
#數據和日誌文件存儲根目錄
base_path=/opt/fastdfs/storage
#第一個存儲目錄
store_path0=/opt/fastdfs/storepath0
#tracker服務器IP和端口
tracker_server=192.168.0.1:22122
#http訪問文件的端口(默認8888,看情況修改,和nginx中保持一致)
http.server_port=8888
開放防火牆端口

1、打開跟蹤端口

vim /etc/sysconfig/iptables

2、添加以下端口行:

-A INPUT -m state --state NEW -m tcp -p tcp --dport 23000 -j ACCEPT

3、重啟防火牆

service iptables restart
啟動Storage
/etc/init.d/fdfs_storaged start
查看集群狀態
 fdfs_monitor /etc/fdfs/storage.conf list

查看狀態是否正常

Storage 1:
id = 6.0.36.243
ip_addr = 6.0.36.243 (anantes-651-1-49-net.w2-0.abo.wanadoo.fr) ACTIVE

Client配置

vim /etc/fdfs/client.conf
修改配置如下:
#
base_path=/opt/apps/fastdfs/client
#tracker服務器IP和端口
tracker_server=192.168.0.1:22122 
上傳一個圖片測試是否能上傳成功
 fdfs_upload_file /etc/fdfs/client.conf test.jpg

test.jpg 是測試本地上傳的圖片,路徑請填寫正確

3、安裝Nginx和 fastdfs-nginx-module

#解壓fastdfs-nginx-module
tar -zxvf fastdfs-nginx-module-1.21.tar.gz
cd fastdfs-nginx-module-1.21/
cp ./src/mod_fastdfs.conf /etc/fdfs
#解壓nginx
tar -zxvf nginx-1.16.1.tar.gz
cd nginx-1.16.1/
#安裝nginx_http_image_filter_module
yum -y install gd-devel
yum -y install zlib zlib-devel openssl openssl--devel pcre pcre-devel
#添加模塊
./configure --add-module=../fastdfs-nginx-module-1.21/src --prefix=/usr/local/nginx --with-http_image_filter_module 
#編譯nginx
make
#安裝nginx
make install

查看是否安裝成功

/usr/local/nginx/sbin/nginx -V

查看插件是否安裝成功

[root@FastDFS nginx-1.16.1]# /usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.16.1
built by gcc 4.8.5 20150623 (Red Hat 4.8.5-11) (GCC) 
configure arguments: --add-module=../fastdfs-nginx-module-1.21/src --prefix=/usr/local/nginx --with-http_image_filter_module
[root@FastDFS nginx-1.16.1]# 

修改Nginx訪問

vim /etc/fdfs/mod_fastdfs.conf

修改配置如下:

#
connect_timeout=10
#tracker服務器IP和端口
tracker_server=192.168.0.1:22122
#是否啟用group組名
url_have_group_name=true
#
store_path0=/opt/fastdfs/storepath0

修改Nginx配置:

vim /usr/local/nginx/conf/nginx.conf

修改配置如下:

server {
    listen       80;
    server_name  localhost;

    #charset koi8-r;

    #access_log  logs/host.access.log  main;

    location / {
        root   html;
        index  index.html index.htm;
    }
    #圖片帶壓縮訪問
    location ~ /group1/M00/(.*)\.(jpg|gif|png)!format=([0-9]+)_([0-9]+) {
        alias  /home/fastdfs/storage/data/;
        ngx_fastdfs_module;
        set $w $3;
        set $h $4;

        rewrite group1/M00(.+)\.(jpg|gif|png)!format=([0-9]+)_([0-9]+)$ group1/M00$1.$2 break;

        image_filter resize $w $h;
        image_filter_buffer 5M;
    }
    #主圖訪問
    location ~ /group([0-9])/M00/(.+)\.?(.+) {
        alias /home/fastdfs/storage/data/;
        ngx_fastdfs_module;
    }
...
}

啟動Nginx

#啟動
/usr/local/nginx/sbin/nginx
#停止
/usr/local/nginx/sbin/nginx -s stop
#重啟
/usr/local/nginx/sbin/nginx -s reload

通過以上配置完成FastDFS的搭建。

測試圖片訪問

圖片訪問示例:

主圖訪問

http://218.2.204.124:30308/group1/M00/00/03/BgAk813IvTCAIxxxAAD44NFKFPc908.png

壓縮圖片 (主圖后加 !format=寬度_高度)訪問

http://218.2.204.124:30308/group1/M00/00/03/BgAk813IvTCAIxxxAAD44NFKFPc908.png!format=400_10

未解決的問題

壓縮圖片使用主圖后?format=寬度_高度

本文由博客一文多發平台 發布!

再次感謝!!! 您已看完全文,歡迎關注微信公眾號猿碼 ,你的支持是我持續更新文章的動力!

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

基於 HTML5 + WebGL 實現 3D 挖掘機系統

前言

在工業互聯網以及物聯網的影響下,人們對於机械的管理,机械的可視化,机械的操作可視化提出了更高的要求。如何在一個系統中完整的显示机械的運行情況,机械的運行軌跡,或者机械的机械動作顯得尤為的重要,因為這會幫助一個不了解這個机械的小白可以直觀的了解机械的運行情況,以及机械的所有可能發生的動作,對於三一或者其它國內國外重工机械的公司能夠有一個更好的展示或者推廣。
挖掘機,又稱挖掘机械(excavating machinery),從近幾年工程机械的發展來看,挖掘機的發展相對較快,挖掘機已經成為工程建設中最主要的工程机械之一。所以該系統實現了對挖掘機的 3D 可視化,在傳統行業一般都是基於 Web SCADA 的前端技術來實現 2D 可視化監控,而且都是 2D 面板部分數據的監控,從後台獲取數據前台显示數據,但是對於挖掘機本身來說,挖掘機的模型,挖掘機的動作,挖掘機的運行可視化卻是更讓人眼前一亮的,所以該系統對於挖機的 3D 模型做出了動作的可視化,大體包括以下幾個方面:

  • 前進後退 — 用戶可以通過鍵盤 wasd 實現前後左右,或者點擊 2D 界面 WASD 來實現挖機的前進後退。
  • 機身旋轉 — 用戶可以通過鍵盤左右鍵實現機身的旋轉,或者點擊 2D 界面 < > 來實現挖機機身的旋轉。
  • 大臂旋轉 — 用戶可點擊 2D 界面第一個滑塊部分實現大臂的旋轉。
  • 小臂旋轉 — 用戶可點擊 2D 界面第二個滑塊部分實現小臂的旋轉。
  • 挖斗挖掘 — 用戶可點擊 2D 界面第三個滑塊部分實現挖斗部分的旋轉挖掘。
  • 挖機動畫 — 用戶可點擊 2D 界面鏟子圖標,點擊之後系統會把挖機本身幾個動畫做一個串聯展示。

本篇文章通過對挖掘機可視化場景的搭建,挖機机械動作代碼的實現進行闡述,幫助我們了解如何使用  實現一個挖掘機的可視化。

預覽地址: 

界面效果預覽

挖機机械運動效果

通過上面 gif 圖片可以看出挖掘機的幾個主要動作。

挖機挖斗運動效果

滑動頁面的第三個滑桿控制挖斗的旋轉挖掘。

挖機機身運動

通過上面 gif 圖片可以看出挖掘機的前進後退以及機身旋轉幾個運動。

場景搭建

該 3D 場景中所有形狀都是用 HT 內部的牆面工具進行構建,通過設置牆面透明屬性 shape3d.transparent 為 true 以及對構建出的牆面進行貼圖來構造出場景中的類似建築的显示效果,具體的樣式可以參考 HT 的 ,場景效果:

通過上圖我們可以看到場景中有許許多多的牆面建築,所以它們有許多相同的地方,例如樣式以及貼圖都是一樣的,所以在 HT 中可以通過批量的操作對這些牆面進行處理,批量的意思指的是在當前未處理的情況下的牆面圖元是一個個獨立繪製的模型,所以性能會比較差,而當一批圖元聚合成一個大模型進行一次性的繪製時,則會極大提高 WebGL 刷新性能,這就是批量所以要做的事情,具體可以參考 HT 的 

該系統 2d 面板部分則也是通過 HT 的矢量進行繪製,面板部分主要包括當前挖機的作業情況,工作時間,保修信息,故障信息等,通過二維的方式展示這些數據信息,面板截圖:

机械運動代碼分析

該系統中挖機的動作是十分的重要和關鍵的,大小臂運動時液壓杠該如何運動,挖斗運動時液壓桿,旋轉點零件,以及連接到挖鬥上的零部件如何聯動起來是關鍵點,机械動畫中用到大部分數學知識進行點面位置的計算,以下是幾個關鍵的數學知識點作為基礎:

在數學中,向量(也稱為幾何向量、矢量),指具有大小和方向的量。它可以形象化地表示為帶箭頭的線段。系統中會通過向量的叉乘算出與某個面垂直的向量即法向量,在計算挖斗旋轉時需要計算出與挖斗面垂直的法向量來進行點的計算,HT 中封裝了 ht.Math 的數學函數,裏面的 ht.Math.Vector2 指的即為二維向量,ht.Math.Vector3 則為三維的向量,可以傳入三個數值進行初始化向量,向量的原型中有 cross 方法用來計算兩個向量的法向量,例如以下偽代碼:

1 var Vector3 = ht.Math.Vector3;
2 var a = new Vector3([10, 10, 0]);
3 var b = new Vector3([10, 0, 0]);
4 var ab = a.clone().cross(b);

以上代碼中 ab 即為計算法向量,a.clone 是為了避免 cross 運算會修改原本的 a 內容,所以克隆出一個新的向量進行叉乘,以下為示意圖:

挖斗机械運動分析

在進行挖斗部分的机械代碼時會將挖斗的位置以及挖斗所有連接點的設備轉化為相對於某個節點的相對位置,例如節點 A 在世界中的坐標為 [100, 100, 100],世界中還有一個節點 B,而且節點 B 的坐標為 [10, 10, 10] 則節點 A 相對於節點 B 的相對位置即為 [90, 90, 90],因為在計算挖斗的位置時,挖機可能此時已經運動到某一點或者旋轉到某一個軸,所以此時不能使用相對世界的坐標,需要使用相對挖機機身的相對坐標來進行計算,代碼中提供了 toLocalPostion(node, worldPosition) 用來將世界的坐標 worldPosition 轉化為相對 node 的相對坐標,以下為代碼實現:

1 var Matrix4 = ht.Math.Matrix4,
2 Vector3 = ht.Math.Vector3;
3 var mat = new Matrix4().fromArray(this.getNodeMat(g3d, node)),
4 matInverse = new Matrix4().getInverse(mat),
5 position = new Vector3(worldPosition).applyMatrix4(matInverse);
6 return position.toArray();

該函數的返回值即為相對坐標,挖機中需要轉化的坐標為連接着挖斗以及小臂的兩個零部件,系統中用 armHinge 以及 bucketHinge 來分別表示小臂樞紐以及挖斗樞紐這兩個零部件,可以從側面來看挖斗的動作,從下圖可以看出,關鍵點是算出交點 P 的坐標,交點 P 的坐標則是以 armHinge 與 bucketHinge位置為圓心,armHinge 與 bucketHinge 的長度為半徑的兩個圓的交點,而且這兩個圓的圓心在挖斗旋轉的過程中是不斷變化的,所以需要通過數學計算不斷算出交點的位置,以下為示意圖:

通過上圖可以知道交點的位置有兩個 p1 以及 p2,程序中通過計算圓心 1 與圓心 2 構成的向量 c2ToC1,以下為偽代碼:

1 var Vector2 = ht.Math.Vector2;
2 var c2ToC1 = new Vector2({ x: c1.x, y: c1.y }).sub(new Vector2({ x: c2.x, y: c2.y }));

c1 和 c2 為 armHinge 以及 bucketHinge 的圓心坐標,接下來是計算圓心 2 與點 p1 以及 p2 構成的向量 c2ToP1 以及 c2ToP2,以下為偽代碼:

1 var Vector2 = ht.Math.Vector2;
2 var c2ToP1 = new Vector2({ x: p1.x, y: p1.y }).sub(new Vector2({ x: c2.x, y: c2.y }));
3 var c2ToP2 = new Vector2({ x: p2.x, y: p2.y }).sub(new Vector2({ x: c2.x, y: c2.y }));

通過上述操作我們可以獲得三個向量 c2ToC1c2ToP1c2ToP2 所以我們可以用到我上述講的向量叉乘的概念進行 p1 與 p2 點的選取,通過向量 c2ToC1 與 c2ToP1,以及向量 c2ToC1 與 c2ToP2 分別進行叉乘得到的結果肯定一個是大於 0 一個小於 0,二維向量的叉乘可以直接把它們視為 3d 向量,z軸補 0 的三維向量,不過二維向量叉乘的結果 result 不是向量而是數值,如果 result > 0 時,那麼 a 正旋轉到 b 的角度為 <180°,如果 k < 0,那麼 a 正旋轉到 b 的角度為 >180°,如果 k = 0 那麼a,b向量平行,所以通過上面的理論知識我們可以知道結果肯定是一個大於 0 一個小於 0,我們可以在程序中測下可以知道我們需要獲取的是大於 0 的那個點 P1,所以每次可以通過上述的方法進行兩個交點的選擇。

以下為挖斗部分動畫的執行流程圖:

通過上述運算之後我們可以獲取到最終需要的點 P 坐標,點 P 坐標即為挖斗與小臂連接部分的一個重要點,獲取該點之後我們可以通過 HT 中提供的 lookAtX 函數來實現接下來的操作,lookAtX 函數的作用為讓某個物體看向某一點,使用方式如下:

1 node.lookAtX(position, 'bottom');

node 即為需要看向某一個點的節點,position 為看向的點的坐標,第二個參數有六個枚舉值可以選擇,分別為 ‘bottom’,’back’,’front’,’top’,’right’,’left’,第二個參數的作用是當我們需要把某個物體看向某一個點的時候我們也要指定該物體的哪一個面看向該點,所以需要提供第二個參數來明確,獲取到該函數之後我們可以通過將 bucketHinge 看向點 P,armHinge 看向點 P,就可以保持這兩個連接的設備永遠朝向該點,以下為部分偽代碼:

1 bucketHinge.lookAtX(P, 'front');
2 armHinge.lookAtX(P, 'bottom');

所以通過上述操作之後我們已經把挖斗部分的兩個關鍵零件的位置已經擺放正確,接下來是要正確的擺放與挖斗連接的小臂上液壓部分的位置,下一部分為介紹該節點如何進行擺放。

液壓聯動分析

在場景中我們可以看到液壓主要分為兩個部分,一部分為白色的較細的液壓桿,一部分為黑色的較厚的液壓桿,白色的液壓桿插在黑色的液壓桿中,所以在小臂或者挖斗旋轉的過程中我們要保持兩個節點始終保持相對的位置,通過上一步驟中我們可以知道 lookAtX 這個函數的作用,所以在液壓桿部分我們也是照樣用該函數來實現。

在上一步我們獲取到了挖斗旋轉過程中的關鍵點 P,所以在挖斗旋轉的過程我們小臂上的液壓桿也要相應的進行變化,具體的操作就是將小臂的白色液壓桿的位置設置為上步中計算出來的點 P 的位置,當然需要把白色液壓桿的錨點進行相應的設置,之後讓白色液壓桿 lookAt 黑色液壓桿,同時讓黑色液壓桿 lookAt 白色液壓桿,這樣下來兩個液壓桿都在互相看着對方,所以它們呈現出來的效果就是白色液壓桿在黑色液壓桿中進行伸縮,以下為偽代碼:

1 bucketWhite.p3(P);
2 bucketWhite.lookAtX(bucketBlack.p3(), 'top');
3 bucketBlack.lookAtX(P, 'bottom');

代碼中 bucketWhite 節點即為小臂上白色液壓桿,bucketBlack 節點為小臂上黑色液壓桿,通過以上的設置就可以實現伸縮的動畫效果,以下為液壓的運行圖:

同理挖機身上的大臂的液壓動作以及機身與大臂連接部分的液壓動作都是使用上面的方法來實現,以下為這兩部分的代碼:


 1 rotateBoom: (rotateVal) = >{
 2     excavatorBoomNode.setRotationX(dr * rotateVal);
 3     let archorVector = [0.5 - 0.5, 0.56 - 0.5, 0.22 - 0.5];
 4     let pos = projectUtil.toWorldPosition(g3d, excavatorBoomNode, archorVector);
 5     boomWhite.lookAtX(boomBlack.p3(), 'bottom');
 6     boomBlack.lookAtX(pos, 'top');
 7 },
 8 rotateArm: (rotateVal) = >{
 9     projectUtil.applyRelativeRotation(excavatorArmNode, excavatorBoomNode, -rotateVal);
10     let archorVector = [0.585 - 0.5, 0.985 - 0.5, 0.17 - 0.5];
11     let pos = projectUtil.toWorldPosition(g3d, excavatorArmNode, archorVector);
12     armWhite.lookAtX(armBlack.p3(), 'bottom');
13     armBlack.lookAtX(pos, 'top');
14 }


我將兩部分的運動封裝為兩個函數 rotateBoom 以及 rotateArm 分別是大臂與機身連接處的液壓運動與大臂上的液壓運動,在該部分中為了精確的獲取看向的點,我通過 toWorldPosition 方法將相對坐標轉化為世界坐標,相對坐標為黑白液壓桿的錨點坐標,轉化為相對大臂或者機身的世界坐標。

基本運動分析

挖機的基本運動包括前進後退,機身旋轉,這一部分會相對上面的運動簡單許多,在 HT 的三維坐標系中,不斷修改挖機機身的 x,y,z 的坐標值就可以實現挖機的前進後退,通過修改機身的 y 軸旋轉角度則可以控制機身的旋轉,當然挖機身體上的所有其它零部件需要吸附在機身身上,當機身進行旋轉時其它零部件則會進行相應的旋轉,在進行前進的時候挖機底部的履帶會進行對應的滾動,當然履帶我們這邊是用了一個履帶的貼圖貼在上面,當挖機前進的時候修改貼圖的偏移值就可以實現履帶的滾動,修改偏移值的偽代碼如下:

1 node.s('shape3d.uv.offset', [x, y]);

上面的 x,y 分別為 x 軸與 y 軸方向的偏移值,在挖機前進後退的過程中不斷修改 y 的值可以實現履帶的滾動效果,具體的文檔說明可以查看 

在挖機前進後退的過程中我們可以 wasd 四個鍵同時按下,並且可以對按鍵進行一直的響應,在 js 中可以通過 document.addEventListener(‘keydown’, (e) => {}) 以及 document.addEventListener(‘keyup’, (e) => {}) 進行監聽,但是這隻能每次執行一次需要執行的動作,所以我們可以在外部起一個定時器,來執行 keydown 時候需要不斷執行的動作,可以用一個 keyMap 來記錄當前已經點擊的按鍵,在 keydown 的時候紀錄為 true 在 keyup 的時候記錄為 false,所以我們可以在定時器中判斷這個 bool 值,當為 true 的時候則執行相應的動作,否則不執行,以下為對應的部分關鍵代碼:


 1 let key_pressed = {
 2     65 : {
 3         status: false,
 4         action: turnLeft
 5     },
 6     87 : {
 7         status: false,
 8         action: goAhead
 9     },
10     68 : {
11         status: false,
12         action: turnRight
13     },
14     83 : {
15         status: false,
16         action: back
17     },
18     37 : {
19         status: false,
20         action: bodyTurnLeft
21     },
22     39 : {
23         status: false,
24         action: bodyTurnRight
25     }
26 };
27 setInterval(() = >{
28     for (let key in key_pressed) {
29         let {
30             status,
31             action
32         } = key_pressed[key];
33         if (status) {
34             action();
35         }
36     }
37 },
38 50);
39 document.addEventListener('keydown', (event) = >{
40     let keyCode = event.keyCode;
41     key_pressed[keyCode] && (key_pressed[keyCode].status = true);
42     event.stopPropagation();
43 },
44 true);
45 document.addEventListener('keyup', (event) = >{
46     let keyCode = event.keyCode;
47     key_pressed[keyCode] && (key_pressed[keyCode].status = false);
48     event.stopPropagation();
49 },
50 true);


從上面代碼可以看出我在 key_pressed 變量中記錄對應按鍵以及按鍵對應的 action 動作,在 keydown 與 keyup 的時候對應修改當前 key 的 status 的狀態值,所以可以在 Interval 中根據 key_pressed 這個變量的 status 值執行對應的 action 動作,以下為執行流程圖:

HT 的輕量化,自適應讓當前系統在手機端也能流暢的運行,當然目前移動端與電腦端的 2D 圖紙部分是加載不同的圖紙,在移動端的 2D 部分只留下操作挖機的操作部分,其它部分進行了相應的捨棄,不然在移動端小屏幕下無法展示如此多的數據,在 3D 場景部分都是共用同一個場景,通過場景搭建部分的批量操作使得 3D 在手機端也十分流暢的運行,以下為手機端運行截圖:

總結

物聯網已經融入了現代生活,通過內嵌到机械設備中的电子設備,我們能夠完成對机械設備的運轉、性能的監控,以及對机械設備出現的問題進行及時的預警。在該系統 2D 面板監控部分就是對採集過來的數據進行可視化的展示,而且我們可以藉助大數據和物聯網技術,將一台台机械通過機載控制器、傳感器和無線通訊模塊,與一個龐大的網絡連接,每揮動一鏟、行動一步,都形成數據痕迹。大數據精準描繪出基礎建設開工率等情況,成為觀察固定資產投資等經濟變化的風向標。所以在實現上述挖機動作之後,通過與挖機傳感器進行連接之後,可以將挖掘機此時的真實動作通過數據傳遞到系統,系統則會根據動作進行相應的真實操作,真正實現了挖機與網絡的互聯互通。

程序運行截圖:

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

新北清潔公司,居家、辦公、裝潢細清專業服務

手寫jwt驗證,實現java和node無縫切換

前言

前端時間和我寫了一個簡易用戶管理後台,功能其實很簡單,涉及到的技術棧有:vue+elementUI,java+spring MVC以及node+egg,數據庫用的mysql,簡單方便。

一開始是我是只負責前端,但是前端開發的的速度太快,老是沒事,加上他小子並沒有接觸過實戰的項目,又怕他出亂子,所以考慮我也寫一個後端,

開始考慮的是用python+django,但是還是在半途放棄了,因為總感覺Django不過靈活,使用起來非常彆扭,也可能是用scrapy寫爬蟲寫多了,難以理解django的框架設計,總是會把他想象成一個scrapy架構,也導致代碼寫的很亂,下面上一張scrapy架構圖。

所以最後考慮的用node,Express和Koa框架學習過node應該都知道,我以前也用過express(寫過一個小demo),但是給我的感覺這到像是一個只能有5年以上node開發經驗才能玩的轉的,因為express是非常的精簡的,安裝它后,會發現它幾乎不會為你提供任何編碼規範,也沒有約束你的框架應該怎麼設計,以至於新手下載完成后完全不知道自己應該幹嘛,甚至不知道直接的文件應該寫在哪,沒有框架本身的約定,標準的MVC模型有着千奇百怪的寫法,所以我覺得沒有一定的架構思想和經驗是很難駕馭的。

koa我並沒有直接的使用過,只是聽說是express的原班人馬打造的,中間件是基於洋蔥圈模型實現的。下面上一張圖洋蔥圈模型圖。

而是後來直接就接觸的egg,egg標語是“為企業級框架和應用而生”,

廢話說的有點多了,重點是通一套前端代碼,開發了兩套後端代碼,功能是完全一致的,後端都實現了相應的jwt驗證功能,–>,密鑰都是我們約定好的固定值,但是最後發現一樣的密鑰,相同的數據,使用的jwt包不同產生的結果也不同,這也就導致兩端之間無法相互切換,每次切換必須從登陸重新開始,這不太符合邏輯,而且重要的是後面的安排有需要這樣的功能,無法做到無縫切換就直接導致實現不了下面的功能。

提綱內容

  • jwt實現原理
  • jwt未能解決的疑惑
  • base64和base64url

jwt實現原理

其實作為一個前端開發人員,jwt實現原理我是沒必要懂得的,但是如果你不限於此,這算是個必不可少的內容了吧。

先上一張圖。

圖中数字1是後端使用jwt工具包生成的token,通常是由三部分組成,也就是token中間的兩個“.”將其分為三部分,第一部分對應的是右邊的数字2部分,然後依次對應。

這三部分分別是頭部、有效載荷、簽名。

頭部:alg是指說用到的算法,type當然是令牌類型

有效荷載:sub所簽發的用戶,name是簽發者的姓名,lat是這簽發時間,exp是指到期時間,當然還有一些其他的,這些數據都是非必要數據,實則只有exp可能有用,因為有效數據實際都是在data裏面,當然你也可以不這麼做。

簽名:前兩者都是通過base64url編碼過的,而非是算法加密的,所以幾乎是透明的。但是簽名是默認是通過hsa256算法加密的 ,加密的規則是:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  your-256-bit-secret
)

如何驗證token呢?那更加簡單了,就是用前端傳回來的token前兩部分和服務器存的秘鑰再次加密一次,然後做base64url處理和第三部分比較,一樣代表這個token是自己簽發的,不一樣代表是偽造的,完了過後再將有效載荷部分進行base64url反編碼,就得到exp,然後和當前時間比較是否過期。

jwt未能解決的疑惑

經過測試,發現三個問題。

1、 node和java使用對應的jwt工具包生成的token不相同,這裡是指在一系列參數完全相同的情況下。

2、 還發現用java生成的token在jwt官網解析時候輸入秘鑰結果的到的簽名和自己的簽名不一樣,但是node是一樣的,於是就產生了java工具包在輸入簽名的時候對密鑰進行了其他處理的想法,但是他並沒有找出做出來怎麼樣的處理,也就是說我們在生成簽名的時候根本就連密鑰都是不一樣的。

3、 不管是node還是java生成的token,我們用原來一模一樣的數據和一樣的算法公式都產生不了和工具包生成一樣的簽名,也就是可以懷疑在進行hsa256算法加密前確實對秘鑰進行處理了。

base64和base64url

base64就是一種二進制編碼方式,原理很簡單,先準備一個包含64個字符的數組:

['A','B','C',...'a','b','c',...'0','1','2',...'+','/']

然後對二進制數據進行處理,每三個字節一組,一共24bit,一個字節8bit嘛,然後再將24分為4組,每組正好6bit。6bit的話就剛好能表示64個字符。如果編碼字符不是3的倍數,就會剩下一個字節或者兩個字節,這個時候就在後面填充\x00,再在編碼末尾加上一個或者兩個=號。解碼的時候制動力去掉就好了。

base64url編碼就是將字符編碼成url能傳遞的參數,因為base64編碼會出現+號和/號,然後就會在url中出現問題,所以base64url其實就是將+和/分別變成-和_

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

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

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

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