天天看點

ASP.NET Core 快速入門(實戰篇)NO1 留言闆(mysql的使用)NO2 聊天室(WebSocket的使用)NO3 找工作(AngleSharp的使用)部署多個站點一些其它的細節

上篇講了《asp.net core在linux上的環境部署》。今天我們将做幾個小玩意實戰一下。用到的技術和工具有mysql、websocket、AngleSharp(爬蟲html解析)、nginx多站點部署。

NO1 留言闆(mysql的使用)

示範:http://haojima.net

這個功能很簡單。就是對資料庫的寫入和展示。如果在Windows下,相信大家分分鐘都可以搞定。而初次接觸.net core + mysql可能需要注意些細節。

首先打開vs2017建立一個asp.net core項目(選Web應用程式),然後nuget 導入

Microsoft.EntityFrameworkCore.Tools 1.1.1

MySql.Data.EntityFrameworkCore 8.0.8-dmr

然後建立一個DbContext類。

public class DataContext : DbContext
{
    //【注意】連接配接字元串一定要加 sslmode=none 
    string str = @"Data Source=;Database=;User ID=;Password=;pooling=true;CharSet=utf8;port=3306;sslmode=none";
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
        optionsBuilder.UseMySQL(str);

    //下面就可以添加要加入資料庫的實體了
    //public DbSet<Message> Messages { get; set; }
}
           

到此為止,我們已經可以利用EF Core直接連接配接mysql進行增删改查操作了。注意:需要導入命名空間

using Microsoft.EntityFrameworkCore; using MySQL.Data.EntityFrameworkCore.Extensions;

當然。你會說,連接配接字元串不能寫死到代碼裡面。我們也可以放配置檔案。

appsettings.json

{
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Warning"
    }
  },
  "ConnectionStrings": { "SqlServerConnection": "Data Source=;Database=;User ID=;Password=;pooling=true;CharSet=utf8;port=3306;sslmode=none" }
}
           

然後把上面的寫死注釋掉。在

Startup.cs

檔案的

ConfigureServices

方法添加

var connection = Configuration.GetConnectionString("SqlServerConnection");
services.AddDbContext<DataContext>(options => options.UseMySQL(connection));
           

【注意】項目名稱和路徑最好不要有中文,不然會出現些亂七八糟的問題。

【完整代碼】:https://github.com/zhaopeiym/BlogDemoCode/tree/master/MessageBoard

NO2 聊天室(WebSocket的使用)

示範:http://socket.haojima.net

WebSocket是Html5新增的一個很酷的技術。下面我們簡單講解下這個很酷的技術

var Socket = new WebSocket(url);//建立 WebSocket 對象
           

建立了一個WebSocket對象後會觸發打開連接配接事件:

Socket.onopen = function(){  }
           

除了onopen事件,還有其他三個事件:

Socket.onmessage  //用戶端接收服務端資料時觸發
Socket.onerror    //通信發生錯誤時觸發
Socket.onclose    //連接配接關閉時觸發
           

另外還有兩個方法:

Socket.send()   //使用連接配接發送資料
Socket.close()  //關閉連接配接
           

最後還有四個連接配接狀态屬性:

Socket.readyState
0 - 表示連接配接尚未建立。
1 - 表示連接配接已建立,可以進行通信。
2 - 表示連接配接正在進行關閉。
3 - 表示連接配接已經關閉或者連接配接不能打開。
           

整個WebSocket常用功能知識點就

四個事件、兩個方法、四種狀态

。簡單吧,下面我們看看asp.net core背景的配合:

背景添加一個SocketHandler類,并添加一個靜态方法Map:

/// <summary>
/// 請求
/// </summary>
/// <param name="app"></param>
public static void Map(IApplicationBuilder app)
{
    app.UseWebSockets(); //【注意】需要 nuget   導入 Microsoft.AspNetCore.WebSockets.Server
    app.Use(Acceptor);
}
           

然後新增對應的Acceptor方法:

/// <summary>
/// 接收請求
/// </summary>
/// <param name="httpContext"></param>
/// <param name="n"></param>
/// <returns></returns>
static async Task Acceptor(HttpContext httpContext, Func<Task> n)
{
           

需要在

Startup.cs

類裡面的

Configure

方法裡面加入

app.Map("/ws", SocketHandler.Map);   //傳入我們剛才建立的靜态方法Map
           

現在為止,基本的類和配置已經完成。

我們主要操作,是在Acceptor方法裡面接收和發送消息。

//建立連接配接
var socket = await httpContext.WebSockets.AcceptWebSocketAsync();
//等待接收資料
await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
//發送消息
await socket.SendAsync(arraySegment, WebSocketMessageType.Text, true, CancellationToken.None);
           

背景關鍵代碼也就這三句,建立連接配接、等待接收、發送消息。

不過這裡有一點需要了解。建立連接配接後,可以接收任意多次用戶端消息。是以ReceiveAsync等待接收這裡需要死循環接收消息,直到連接配接斷開。(不用擔心真的死循環,沒有消息發送的時候,代碼會阻塞在那裡等待消息)

【完整實作】:https://github.com/zhaopeiym/ChatRoom

NO3 找工作(AngleSharp的使用)

示範:http://job.haojima.net

對于爬蟲抓包,我相信大家初次接觸都非常的熱衷于此。我也不例外。

那麼在asp.net core下面是否也有這樣的插件呢?答案是肯定的。

http://www.cnblogs.com/linezero/p/5599611.html HtmlAgilityPack HTML解析(感謝部落客對.net core的貢獻)。不過xpath用起來超級惡心。

之前在.net下面有一款

Jumony

http://www.cnblogs.com/Ivony/p/3447536.html(部落格園大牛寫的)。支援CSS選擇和linq查詢。簡直不要太爽。可是不支援.net core。(本人試了下遷移.net core,發現很多類在.net core沒有實作)

最後還是到了一款支援.net core的解析元件。并可以媲美Jumony,同樣支援css選擇和linq查詢。那就是AngleSharp。

建立項目,nuget 安裝 AngleSharp。然後以下簡單使用:

using (HttpClient http = new HttpClient())
{
    var htmlString = await http.GetStringAsync(url);
    HtmlParser htmlParser = new HtmlParser();
    var jobInfos = htmlParser.Parse(htmlString)
        .QuerySelectorAll(".newlist_list_content table")
        .Where(t => t.QuerySelectorAll(".zwmc a").FirstOrDefault() != null)
        .Select(t => new JobInfo()
        {
            PositionName = t.QuerySelectorAll(".zwmc a").FirstOrDefault().TextContent,
            CorporateName = t.QuerySelectorAll(".gsmc a").FirstOrDefault().TextContent,
            Salary = t.QuerySelectorAll(".zwyx").FirstOrDefault().TextContent,
            WorkingPlace = t.QuerySelectorAll(".gzdd").FirstOrDefault().TextContent,
        .ToList();
    return jobInfos;
}
           

看到沒有,就像jq一樣解析html。如果你說不爽我都不信。

【完整實作】:https://github.com/zhaopeiym/JobWanted

部署多個站點

以上,這些項目都比較簡單。關鍵技術點和難點都進行的分析。我相信大家都可以動起手練習起來了。

不過有個問題,前面我們隻說了部署一個應用程式。如果是多個該怎麼部署呢?

首先我們把多個程式釋出包放到伺服器上。

然後修改nginx的配置檔案

/etc/nginx/conf.d/default.conf

server {
    listen 80;
    server_name www.haojima.net;           #對應的域名
    root /home/projects/messagBoard;       #程式路徑
    location / {
        proxy_pass http://localhost:5000;  #内網端口
        proxy_http_version 1.1; 
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-real-ip $remote_addr;
        
        proxy_set_header Upgrade $http_upgrade;   
    }
}
           

有幾個程式就添加幾個server,不過需要修改你解析到的域名、程式路徑和内網對應的端口(看配置裡的注釋) 。

然後修改supervisor的配置檔案

/etc/supervisor/conf.d/supervisord.conf

[program:MessageBoard]
command=dotnet MessageBoard.dll        ; 運作程式的指令
directory= /home/projects/messagBoard/ ; 指令執行的目錄
autorestart=true                    ; 程式意外退出是否自動重新開機
stderr_logfile=/var/log/WebApplication1.err.log ; 錯誤日志檔案
stdout_logfile=/var/log/WebApplication1.out.log ; 輸出日志檔案
environment=ASPNETCORE_ENVIRONMENT=Production ; 程序環境變量
user=root ; 程序執行的使用者身份
stopsignal=INT
           

有幾個程式就往下複制幾份program。需要修改program名稱,隻要名稱不重複就可以。然後修改 運作程式的指令 對應的dll和指令執行的目錄(看配置檔案的注釋)。

如此就可以部署多個程式了。

開始我還以為是在域名解析的時候,解析IP + 端口。原來是多個域名解析到同一個IP,然後nginx在内部做域名和内網端口分發。

一些其它的細節

部署阿裡雲

我們在linux的防火牆開放了端口,發現在外面還是通路不了(可以telnet IP 端口 來測試)。有可能是阿裡雲攔截了。https://help.aliyun.com/document_detail/25471.html 在安全組添加某端口哪些IP可以通路。

mysql的用戶端

對于mysql,我們安裝好之後總不能每次指令操作吧。在Windows下面有個用戶端Navicat可以友善管理mysql。Navicat

擷取ip

用了nginx後發現取不到浏覽器IP了。那是因為我們程式都是浏覽器通路nginx,然後nginx轉發内網程式端口。是以取到的IP都是内網本機IP。如果需要取浏覽器IP需要在nginx配置

server {
    listen 80;
    server_name www.haojima.net;
    root /home/projects/messagBoard;
    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1; 
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-real-ip $remote_addr;     # 新添加
    }
}
           

然後代碼裡面取IP:

var ip = HttpContext.Request.Headers["X-real-ip"].FirstOrDefault();
           

WebSocket在nginx的配置

上面我們寫的WebSocket直接運作發現沒有任何問題,可是部署在nginx去跑不起來了。那是因為需要nginx支援WebSocket,需要配置。http://nginx.org/en/docs/http/websocket.html

server {
    listen 80;
    server_name job.haojima.net;
    root /home/projects/jobWanted;
    location / {
        proxy_pass http://localhost:5002;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header X-real-ip $remote_addr;
        proxy_set_header Upgrade $http_upgrade;     # 新增
        proxy_set_header Connection "upgrade";      # 新增 
    }
}
           

WebSocket心跳

經過上面的配置,我們的WebSocket在nginx上跑起來了。萬分歡喜的我們,發現一分鐘不發消息就自動掉線了。郁悶至極到頭大。細心的同學通過上面的連結資料其實已經有說明:

By default, the connection will be closed if the proxied server does not transmit any data within 60 seconds. This timeout can be increased with the proxy_read_timeout directive. Alternatively, the proxied server can be configured to periodically send WebSocket ping frames to reset the timeout and check if the connection is still alive.
           

靠,英文實在太爛了。

預設情況下,如果代理的伺服器在60秒内沒有傳輸任何資料,則連接配接将被關閉。可以使用proxy_read_timeout指令增加此逾時 。或者,代理伺服器可以配置為定期發送WebSocket ping幀以重置逾時并檢查連接配接是否仍然存在。
           

nginx給出了兩種解決方案。第一種,修改proxy_read_timeout (預設60秒)。第二種,浏覽器用戶端定時發送心跳包(時間要短于proxy_read_timeout)。

我使用的是第二種方式。

第一種雖然簡單粗暴,但是時間再長也是一個值,還是會有逾時的可能。再者,誰能保證浏覽器端不會new 很多個WebSocket出來搗蛋。

第二種方式,浏覽器定時發送一條消息,内容和背景約定下。如發送“心跳”,然後背景接收消息是,判斷如果是“心跳”則不做任何處理。

中文編碼

在做“找工作”爬前程無憂的資料時,發現他們使用的GBK編碼。而在.net core中預設不支援這種格式,導緻取到的資料都是亂碼。我們需要nuget安裝

System.Text.Encoding.CodePages

。然後在Startup.cs的Configure裡面注冊:

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);//注冊編碼提供程式
           

使用:

var htmlBytes = await http.GetByteArrayAsync(url);
var htmlString = Encoding.GetEncoding("GBK").GetString(htmlBytes);
           

asp.net core 端口配置設定

asp.net core 預設端口都是5000。那麼我們運作第二個程式的時候就會提示5000端口被占用。這個時候,我們就需要為每個程式配置設定不同的端口了。

在根目錄建立一個json檔案

hosting.json

{
  "server.urls": "http://*:5002"
}
           

Program.cs

檔案修改

public static void Main(string[] args)
{
    var config = new ConfigurationBuilder()
          .SetBasePath(Directory.GetCurrentDirectory())
          .AddJsonFile("hosting.json", optional: true)
          .Build();

    var host = new WebHostBuilder()
        .UseKestrel()
        .UseConfiguration(config)
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseIISIntegration()
        .UseStartup<Startup>()
        .UseApplicationInsights()
        .Build();

    host.Run();
}
           

爬拉勾資料

在爬拉勾網的時候沒有搞定,不知道是不是因為https的原因。

using (HttpClient http = new HttpClient())
{
    var url = "https://www.lagou.com/zhaopin/Java/?labelWords=label";
    var htmlString = await http.GetStringAsync(url);
}
           

在.net core中報錯:An unhandled exception occurred while processing the request.

在.net 4.5 中抓到的資料是“頁面加載中...”。和浏覽器通路的結果不一樣。

原因未知。如果有大佬解惑,感激不盡!

參考

http://www.runoob.com/html/html5-websocket.html

http://www.cnblogs.com/liguobao/p/6130121.html

http://www.cnblogs.com/linezero/p/5806814.html

示範

http://haojima.net

http://socket.haojima.net

http://job.haojima.net

源碼

https://github.com/zhaopeiym/JobWanted

https://github.com/zhaopeiym/ChatRoom

https://github.com/zhaopeiym/BlogDemoCode

繼續閱讀