AVt天堂网 手机版,亚洲va久久久噜噜噜久久4399,天天综合亚洲色在线精品,亚洲一级Av无码毛片久久精品

當前位置:首頁 > 科技  > 軟件

您可知道如何通過HTTP2實現TCP的內網穿透?

來源: 責編: 時間:2024-05-07 09:15:57 172觀看
導讀可能有人很疑惑應用層 轉發傳輸層?,為什么會有這樣的需求啊???哈哈技術無所不用其極,由于一些場景下,對于一個服務器存在某一個內部網站中,但是對于這個服務器它沒有訪問外網的權限,雖然也可以申請端口訪問外部指定的ip+端口,但

可能有人很疑惑應用層 轉發傳輸層?,為什么會有這樣的需求啊???哈哈技術無所不用其極,由于一些場景下,對于一個服務器存在某一個內部網站中,但是對于這個服務器它沒有訪問外網的權限,雖然也可以申請端口訪問外部指定的ip+端口,但是對于訪問服務內部的TCP的時候我們就會發現忘記申請了!這個時候我們又要提交申請,又要等審批,然后開通端口,對于這個步驟不是一般的麻煩,所以我在想是否可以直接利用現有的Http網關的端口進行轉發內部的TCP服務?這個時候我詢問了我們的老九大佬,由于我之前也做過通過H2實現HTTP內網穿透,可以利用H2將內部網絡中的服務映射出來,但是由于底層是基于yarp的一些方法實現,所以并沒有考慮過TCP,然后于老九大佬交流深究,決定嘗試驗證可行性,然后我們的Taibai項目就誕生了,為什么叫Taibai?您仔細看看這個拼音,翻譯過來就是太白,確實全稱應該叫太白金星,寓意上天遁地無所不能!下面我們介紹一下具體實現邏輯,確實您仔細看會發現實現是真的超級簡單的!KUr28資訊網——每日最新資訊28at.com

創建Core項目用于共用的核心類庫

創建項目名Taibai.CoreKUr28資訊網——每日最新資訊28at.com

下面幾個方法都是用于操作Stream的類KUr28資訊網——每日最新資訊28at.com

DelegatingStream.csKUr28資訊網——每日最新資訊28at.com

namespace Taibai.Core;/// <summary>/// 委托流/// </summary>public abstract class DelegatingStream : Stream{    /// <summary>    /// 獲取所包裝的流對象    /// </summary>    protected readonly Stream Inner;    /// <summary>    /// 委托流    /// </summary>    /// <param name="inner"></param>    public DelegatingStream(Stream inner)    {        this.Inner = inner;    }    /// <inheritdoc/>    public override bool CanRead => Inner.CanRead;    /// <inheritdoc/>    public override bool CanSeek => Inner.CanSeek;    /// <inheritdoc/>    public override bool CanWrite => Inner.CanWrite;    /// <inheritdoc/>    public override long Length => Inner.Length;    /// <inheritdoc/>    public override bool CanTimeout => Inner.CanTimeout;    /// <inheritdoc/>    public override int ReadTimeout    {        get => Inner.ReadTimeout;        set => Inner.ReadTimeout = value;    }    /// <inheritdoc/>    public override int WriteTimeout    {        get => Inner.WriteTimeout;        set => Inner.WriteTimeout = value;    }    /// <inheritdoc/>    public override long Position    {        get => Inner.Position;        set => Inner.Position = value;    }    /// <inheritdoc/>    public override void Flush()    {        Inner.Flush();    }    /// <inheritdoc/>    public override Task FlushAsync(CancellationToken cancellationToken)    {        return Inner.FlushAsync(cancellationToken);    }    /// <inheritdoc/>    public override int Read(byte[] buffer, int offset, int count)    {        return Inner.Read(buffer, offset, count);    }    /// <inheritdoc/>    public override int Read(Span<byte> destination)    {        return Inner.Read(destination);    }    /// <inheritdoc/>    public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)    {        return Inner.ReadAsync(buffer, offset, count, cancellationToken);    }    /// <inheritdoc/>    public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)    {        return Inner.ReadAsync(destination, cancellationToken);    }    /// <inheritdoc/>    public override long Seek(long offset, SeekOrigin origin)    {        return Inner.Seek(offset, origin);    }    /// <inheritdoc/>    public override void SetLength(long value)    {        Inner.SetLength(value);    }    /// <inheritdoc/>    public override void Write(byte[] buffer, int offset, int count)    {        Inner.Write(buffer, offset, count);    }    /// <inheritdoc/>    public override void Write(ReadOnlySpan<byte> source)    {        Inner.Write(source);    }    /// <inheritdoc/>    public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)    {        return Inner.WriteAsync(buffer, offset, count, cancellationToken);    }    /// <inheritdoc/>    public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)    {        return Inner.WriteAsync(source, cancellationToken);    }    /// <inheritdoc/>    public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)    {        return TaskToAsyncResult.Begin(ReadAsync(buffer, offset, count), callback, state);    }    /// <inheritdoc/>    public override int EndRead(IAsyncResult asyncResult)    {        return TaskToAsyncResult.End<int>(asyncResult);    }    /// <inheritdoc/>    public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback,        object? state)    {        return TaskToAsyncResult.Begin(WriteAsync(buffer, offset, count), callback, state);    }    /// <inheritdoc/>    public override void EndWrite(IAsyncResult asyncResult)    {        TaskToAsyncResult.End(asyncResult);    }    /// <inheritdoc/>    public override int ReadByte()    {        return Inner.ReadByte();    }    /// <inheritdoc/>    public override void WriteByte(byte value)    {        Inner.WriteByte(value);    }    /// <inheritdoc/>    public sealed override void Close()    {        base.Close();    }}

SafeWriteStream.csKUr28資訊網——每日最新資訊28at.com

public class SafeWriteStream(Stream inner) : DelegatingStream(inner){    private readonly SemaphoreSlim semaphoreSlim = new(1, 1);    public override async ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)    {        try        {            await this.semaphoreSlim.WaitAsync(CancellationToken.None);            await base.WriteAsync(source, cancellationToken);            await this.FlushAsync(cancellationToken);        }        finally        {            this.semaphoreSlim.Release();        }    }    public override ValueTask DisposeAsync()    {        this.semaphoreSlim.Dispose();        return this.Inner.DisposeAsync();    }    protected override void Dispose(bool disposing)    {        this.semaphoreSlim.Dispose();        this.Inner.Dispose();    }}

創建服務端

創建一個WebAPI的項目項目名Taibai.Server并且依賴Taibai.Core項目KUr28資訊網——每日最新資訊28at.com

創建ServerService.cs,這個類是用于管理內網的客戶端的,這個一般是部署在內網服務器上,用于將內網的端口映射出來,但是我們的Demo只實現了簡單的管理不做端口的管理。KUr28資訊網——每日最新資訊28at.com

using System.Collections.Concurrent;using Microsoft.AspNetCore.Http.Features;using Microsoft.AspNetCore.Http.Timeouts;using Taibai.Core;namespace Taibai.Server;public static class ServerService{    private static readonly ConcurrentDictionary<string, (CancellationToken, Stream)> ClusterConnections = new();    public static async Task StartAsync(HttpContext context)    {        // 如果不是http2協議,我們不處理, 因為我們只支持http2        if (context.Request.Protocol != HttpProtocol.Http2)        {            return;        }        // 獲取query        var query = context.Request.Query;        // 我們需要強制要求name參數        var name = query["name"];        if (string.IsNullOrEmpty(name))        {            context.Response.StatusCode = 400;            Console.WriteLine("Name is required");            return;        }                Console.WriteLine("Accepted connection from " + name);        // 獲取http2特性        var http2Feature = context.Features.Get<IHttpExtendedConnectFeature>();                // 禁用超時        context.Features.Get<IHttpRequestTimeoutFeature>()?.DisableTimeout();        // 得到雙工流        var stream = new SafeWriteStream(await http2Feature.AcceptAsync());        // 將其添加到集合中,以便我們可以在其他地方使用        CreateConnectionChannel(name, context.RequestAborted, stream);        // 注冊取消連接        context.RequestAborted.Register(() =>        {            // 當取消時,我們需要從集合中刪除            ClusterConnections.TryRemove(name, out _);        });                // 由于我們需要保持連接,所以我們需要等待,直到客戶端主動斷開連接。        await Task.Delay(-1, context.RequestAborted);    }    /// <summary>    /// 通過名稱獲取連接    /// </summary>    /// <param name="host"></param>    /// <returns></returns>    public static (CancellationToken, Stream) GetConnectionChannel(string host)    {        return ClusterConnections[host];    }    /// <summary>    /// 注冊連接    /// </summary>    /// <param name="host"></param>    /// <param name="cancellationToken"></param>    /// <param name="stream"></param>    public static void CreateConnectionChannel(string host, CancellationToken cancellationToken, Stream stream)    {        ClusterConnections.GetOrAdd(host,            _ => (cancellationToken, stream));    }}

然后再創建ClientMiddleware.cs,并且繼承IMiddleware,這個是我們本地使用的客戶端鏈接的時候進入的中間件,再這個中間件會獲取query中攜帶的name去找到指定的Stream,然后會將客戶端的Stream和獲取的server的Stream進行Copy,在這里他們會將讀取的數據寫入到對方的流中,這樣就實現了雙工通信KUr28資訊網——每日最新資訊28at.com

using Microsoft.AspNetCore.Http.Features;using Microsoft.AspNetCore.Http.Timeouts;using Taibai.Core;namespace Taibai.Server;public class ClientMiddleware : IMiddleware{    public async Task InvokeAsync(HttpContext context, RequestDelegate next)    {                // 如果不是http2協議,我們不處理, 因為我們只支持http2        if (context.Request.Protocol != HttpProtocol.Http2)        {            return;        }        var name = context.Request.Query["name"];        if (string.IsNullOrEmpty(name))        {            context.Response.StatusCode = 400;            Console.WriteLine("Name is required");            return;        }                Console.WriteLine("Accepted connection from " + name);        var http2Feature = context.Features.Get<IHttpExtendedConnectFeature>();        context.Features.Get<IHttpRequestTimeoutFeature>()?.DisableTimeout();        // 得到雙工流        var stream = new SafeWriteStream(await http2Feature.AcceptAsync());        // 通過name找到指定的server鏈接,然后進行轉發。        var (cancellationToken, reader) = ServerService.GetConnectionChannel(name);        try        {            // 注冊取消連接            cancellationToken.Register(() =>            {                Console.WriteLine("斷開連接");                stream.Close();            });            // 得到客戶端的流,然后給我們的SafeWriteStream,然后我們就可以進行轉發了            var socketStream = new SafeWriteStream(reader);            // 在這里他們會將讀取的數據寫入到對方的流中,這樣就實現了雙工通信,這個非常簡單并且性能也不錯。            await Task.WhenAll(                stream.CopyToAsync(socketStream, context.RequestAborted),                socketStream.CopyToAsync(stream, context.RequestAborted)            );        }        catch (Exception e)        {            Console.WriteLine("斷開連接" + e.Message);            throw;        }    }}

打開Program.csKUr28資訊網——每日最新資訊28at.com

using Taibai.Server;var builder = WebApplication.CreateBuilder(new WebApplicationOptions());builder.Host.ConfigureHostOptions(host => { host.ShutdownTimeout = TimeSpan.FromSeconds(1d); });builder.Services.AddSingleton<ClientMiddleware>();var app = builder.Build();app.Map("/server", app =>{    app.Use(Middleware);    static async Task Middleware(HttpContext context, RequestDelegate _)    {        await ServerService.StartAsync(context);    }});app.Map("/client", app => { app.UseMiddleware<ClientMiddleware>(); });app.Run();

在這里我們將server的所有路由都交過ServerService.StartAsync接管,再server會請求這個地址,KUr28資訊網——每日最新資訊28at.com

而/client則給了ClientMiddleware中間件。KUr28資訊網——每日最新資訊28at.com

創建客戶端

上面我們實現了服務端,其實服務端可以完全放置到現有的WebApi項目當中的,而且代碼也不是很多。KUr28資訊網——每日最新資訊28at.com

客戶端我們創建一個控制臺項目名:Taibai.Client,并且依賴Taibai.Core項目KUr28資訊網——每日最新資訊28at.com

由于我們的客戶端有些特殊,再server中部署的它不需要監聽端口,它只需要將服務器的數據轉發到指定的一個地址即可,所以我們需要將客戶端的server部署的和本地部署的分開實現,再服務器部署的客戶端我們命名為MonitorClient.csKUr28資訊網——每日最新資訊28at.com

ClientOption.cs用于傳遞我們的客戶端地址配置KUr28資訊網——每日最新資訊28at.com

public class ClientOption{    /// <summary>    /// 服務地址    /// </summary>    public string ServiceUri { get; set; }    }

MonitorClient.cs,作為服務器的轉發客戶端。KUr28資訊網——每日最新資訊28at.com

using System.Net;using System.Net.Security;using System.Net.Sockets;using Taibai.Core;namespace Taibai.Client;public class MonitorClient(ClientOption option){    private string Protocol = "taibai";    private readonly HttpMessageInvoker httpClient = new(CreateDefaultHttpHandler(), true);    private readonly Socket socket = new(SocketType.Stream, ProtocolType.Tcp);    private static SocketsHttpHandler CreateDefaultHttpHandler()    {        return new SocketsHttpHandler        {            // 允許多個http2連接            EnableMultipleHttp2Connections = true,            // 設置連接超時時間            ConnectTimeout = TimeSpan.FromSeconds(60),            SslOptions = new SslClientAuthenticationOptions            {                // 由于我們沒有證書,所以我們需要設置為true                RemoteCertificateValidationCallback = (_, _, _, _) => true,            },        };    }    public async Task TransportAsync(CancellationToken cancellationToken)    {        Console.WriteLine("鏈接中!");        // 由于是測試,我們就目前先寫死遠程地址        await socket.ConnectAsync(new IPEndPoint(IPAddress.Parse("192.168.31.250"), 3389), cancellationToken);        Console.WriteLine("連接成功");        // 將Socket轉換為流        var stream = new NetworkStream(socket);        try        {            // 創建服務器的連接,然后返回一個流,這個是H2的流            var serverStream = await this.CreateServerConnectionAsync(cancellationToken);            Console.WriteLine("鏈接服務器成功");            // 將兩個流連接起來,這樣我們就可以進行雙工通信了。它們會自動進行數據的傳輸。            await Task.WhenAll(                stream.CopyToAsync(serverStream, cancellationToken),                serverStream.CopyToAsync(stream, cancellationToken)            );        }        catch (Exception ex)        {            Console.WriteLine(ex.Message);            throw;        }    }    /// <summary>    /// 創建服務器的連接    /// </summary>     /// <param name="cancellationToken"></param>    /// <exception cref="OperationCanceledException"></exception>    /// <returns></returns>    public async Task<SafeWriteStream> CreateServerConnectionAsync(CancellationToken cancellationToken)    {        var stream = await Http20ConnectServerAsync(cancellationToken);        return new SafeWriteStream(stream);    }    /// <summary>    /// 創建http2連接    /// </summary>    /// <param name="cancellationToken"></param>    /// <returns></returns>    private async Task<Stream> Http20ConnectServerAsync(CancellationToken cancellationToken)    {        var serverUri = new Uri(option.ServiceUri);        // 這里我們使用Connect方法,因為我們需要建立一個雙工流, 這樣我們就可以進行雙工通信了。        var request = new HttpRequestMessage(HttpMethod.Connect, serverUri);        // 如果設置了Connect,那么我們需要設置Protocol        request.Headers.Protocol = Protocol;        // 我們需要設置http2的版本        request.Version = HttpVersion.Version20;                // 我們需要確保我們的請求是http2的        request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;        // 設置一下超時時間,這樣我們就可以在超時的時候取消連接了。        using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60));        using var linkedTokenSource =            CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, cancellationToken);        // 發送請求,然后等待響應        var httpResponse = await this.httpClient.SendAsync(request, linkedTokenSource.Token);        // 返回h2的流,用于傳輸數據        return await httpResponse.Content.ReadAsStreamAsync(linkedTokenSource.Token);    }}

創建我們的本地客戶端實現類。KUr28資訊網——每日最新資訊28at.com

Client.cs這個就是在我們本地部署的服務,然后會監聽本地的60112的端口,然后會吧這個端口的數據轉發到我們的服務器,然后服務器會根據我們使用的name去找到指定的客戶端進行交互傳輸。KUr28資訊網——每日最新資訊28at.com

using System.Net;using System.Net.Security;using System.Net.Sockets;using Taibai.Core;using HttpMethod = System.Net.Http.HttpMethod;namespace Taibai.Client;public class Client{    private readonly ClientOption option;    private string Protocol = "taibai";    private readonly HttpMessageInvoker httpClient;    private readonly Socket socket;    public Client(ClientOption option)    {        this.option = option;        this.httpClient = new HttpMessageInvoker(CreateDefaultHttpHandler(), true);        this.socket = new Socket(SocketType.Stream, ProtocolType.Tcp);        // 監聽本地端口        this.socket.Bind(new IPEndPoint(IPAddress.Loopback, 60112));        this.socket.Listen(10);    }    private static SocketsHttpHandler CreateDefaultHttpHandler()    {        return new SocketsHttpHandler        {            // 允許多個http2連接            EnableMultipleHttp2Connections = true,            ConnectTimeout = TimeSpan.FromSeconds(60),            ResponseDrainTimeout = TimeSpan.FromSeconds(60),              SslOptions = new SslClientAuthenticationOptions            {                // 由于我們沒有證書,所以我們需要設置為true                RemoteCertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => true,            },        };    }    public async Task TransportAsync(CancellationToken cancellationToken)    {        Console.WriteLine("Listening on 60112");        // 等待客戶端連接        var client = await this.socket.AcceptAsync(cancellationToken);        Console.WriteLine("Accepted connection from " + client.RemoteEndPoint);        try        {            // 將Socket轉換為流            var stream = new NetworkStream(client);            // 創建服務器的連接,然后返回一個流, 這個是H2的流            var serverStream = await this.CreateServerConnectionAsync(cancellationToken);            Console.WriteLine("Connected to server");            // 將兩個流連接起來, 這樣我們就可以進行雙工通信了. 它們會自動進行數據的傳輸.            await Task.WhenAll(                stream.CopyToAsync(serverStream, cancellationToken),                serverStream.CopyToAsync(stream, cancellationToken)            );        }        catch (Exception e)        {            Console.WriteLine(e);            throw;        }    }    /// <summary>    /// 創建與服務器的連接    /// </summary>     /// <param name="cancellationToken"></param>    /// <exception cref="OperationCanceledException"></exception>    /// <returns></returns>    public async Task<SafeWriteStream> CreateServerConnectionAsync(CancellationToken cancellationToken)    {        var stream = await this.Http20ConnectServerAsync(cancellationToken);        return new SafeWriteStream(stream);    }    private async Task<Stream> Http20ConnectServerAsync(CancellationToken cancellationToken)    {        var serverUri = new Uri(option.ServiceUri);        // 這里我們使用Connect方法, 因為我們需要建立一個雙工流        var request = new HttpRequestMessage(HttpMethod.Connect, serverUri);        // 由于我們設置了Connect方法, 所以我們需要設置協議,這樣服務器才能識別        request.Headers.Protocol = Protocol;        // 設置http2版本        request.Version = HttpVersion.Version20;        // 強制使用http2        request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;        using var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(60));        using var linkedTokenSource =            CancellationTokenSource.CreateLinkedTokenSource(timeoutTokenSource.Token, cancellationToken);        // 發送請求,等待服務器驗證。        var httpResponse = await this.httpClient.SendAsync(request, linkedTokenSource.Token);        // 返回一個流        return await httpResponse.Content.ReadAsStreamAsync(linkedTokenSource.Token);    }}

然后再Program.cs中,我們封裝一個簡單的控制臺版本。KUr28資訊網——每日最新資訊28at.com

using Taibai.Client;const string commandTemplate = @"當前是 Taibai 客戶端,輸入以下命令:- `help` 顯示幫助- `monitor` 使用監控模式,監聽本地端口,將流量轉發到服務端的指定地址    - `monitor=https://localhost:7153/server?name=test`  監聽本地端口,將流量轉發到服務端指定的客戶端名稱為 test 的地址- `client` 使用客戶端模式,連接服務端的指定地址,將流量轉發到本地端口    - `client=https://localhost:7153/client?name=test`  連接服務端指定當前客戶端名稱為 test,將流量轉發到本地端口- `exit` 退出輸入命令:";while (true){    Console.WriteLine(commandTemplate);    var command = Console.ReadLine();    if (command?.StartsWith("monitor=") == true)    {        var client = new MonitorClient(new ClientOption()        {            ServiceUri = command[8..]        });        await client.TransportAsync(new CancellationToken());    }    else if (command?.StartsWith("client=") == true)    {        var client = new Client(new ClientOption()        {            ServiceUri = command[7..]        });        await client.TransportAsync(new CancellationToken());    }    else if (command == "help")    {        Console.WriteLine(commandTemplate);    }    else if (command == "exit")    {        Console.WriteLine("Bye!");        break;    }    else    {        Console.WriteLine("未知命令");    }}

我們默認提供了命令去使用指定的一個模式去鏈接客戶端,KUr28資訊網——每日最新資訊28at.com

然后我們發布一下Taibai.Client,發布完成以后我們使用ide啟動我們的Taibai.Server,請注意我們需要使用HTTPS進行啟動的,HTTP是不支持H2的!KUr28資訊網——每日最新資訊28at.com

然后再客戶端中打開倆個控制臺面板,一個作為監聽的monitor,一個作為client進行鏈接到我們的服務器中。KUr28資訊網——每日最新資訊28at.com

圖片圖片KUr28資訊網——每日最新資訊28at.com

圖片圖片KUr28資訊網——每日最新資訊28at.com

然后我們使用遠程桌面訪問我們的127.0.0.1:60112,然后我們發現鏈接成功!如果您跟著寫代碼您會您發您也成功了,哦耶您獲得了一個牛逼的技能,來源于微軟MVP token的雙休大法的傳授!KUr28資訊網——每日最新資訊28at.com

圖片 圖片 KUr28資訊網——每日最新資訊28at.com

本文鏈接:http://www.tebozhan.com/showinfo-26-87050-0.html您可知道如何通過HTTP2實現TCP的內網穿透?

聲明:本網頁內容旨在傳播知識,若有侵權等問題請及時與本網聯系,我們將在第一時間刪除處理。郵件:2376512515@qq.com

上一篇: Python 網絡爬蟲利器:執行 JavaScript 實現數據抓取

下一篇: DevSecOps 是什么?你知道嗎?

標簽:
  • 熱門焦點
  • K60至尊版剛預熱 一加Ace2 Pro正面硬剛

    Redmi這邊剛如火如荼的宣傳了K60 Ultra的各種技術和硬件配置,作為競品的一加也坐不住了。一加中國區總裁李杰發布了兩條微博,表示在自家的一加Ace2上早就已經采用了和PixelWo
  • 6月安卓手機好評榜:魅族20 Pro蟬聯冠軍

    性能榜和性價比榜之后,我們來看最后的安卓手機好評榜,數據來源安兔兔評測,收集時間2023年6月1日至6月30日,僅限國內市場。第一名:魅族20 Pro好評率:95%5月份的時候魅族20 Pro就是
  • 跑分安卓第一!Redmi K60至尊版8月發布!盧偉冰:目標年度性能之王

    8月5日消息,Redmi K60至尊版將于8月發布,在此前舉行的戰略發布會上,官方該機將搭載搭載天璣9200+處理器,安兔兔V10跑分超177萬分,是目前安卓陣營最高的分數
  • 0糖0卡0脂 旭日森林仙草烏龍茶優惠:15瓶到手29元

    旭日森林無糖仙草烏龍茶510ml*15瓶平時要賣為79.9元,今日下單領取50元優惠券,到手價為29.9元。產品規格:0糖0卡0脂,添加草本仙草汁,清涼爽口,富含茶多酚,保留
  • SpringBoot中使用Cache提升接口性能詳解

    環境:springboot2.3.12.RELEASE + JSR107 + Ehcache + JPASpring 框架從 3.1 開始,對 Spring 應用程序提供了透明式添加緩存的支持。和事務支持一樣,抽象緩存允許一致地使用各
  • 每天一道面試題-CPU偽共享

    前言:了不起:又到了每天一到面試題的時候了!學弟,最近學習的怎么樣啊 了不起學弟:最近學習的還不錯,每天都在學習,每天都在進步! 了不起:那你最近學習的什么呢? 了不起學弟:最近在學習C
  • 阿里大調整

    來源:產品劉有媒體報道稱,近期淘寶天貓集團啟動了近年來最大的人力制度改革,涉及員工績效、層級體系等多個核心事項,目前已形成一個初步的&ldquo;征求意見版&rdquo;:1、取消P序列
  • OPPO、vivo、小米等國內廠商Q2在印度智能手機市場份額依舊高達55%

    7月20日消息,據外媒報道,研究機構的報告顯示,在全球智能手機出貨量同比仍在下滑的大背景下,印度這一有潛力的市場也未能幸免,出貨量同比也有下滑,多家廠
  • Meta盲目擴張致超萬人被裁,重金押注元宇宙而前景未明

    圖片來源:圖蟲創意日前,Meta創始人兼CEO 馬克&middot;扎克伯發布公開信,宣布Meta計劃裁員超11000人,占其員工總數13%。他公開承認了自己的預判失誤:&ldquo;不僅
Top