20170814-20170820C#工作學習周總結

20170807-20170813C#工作學習周總結

Log4Net框架概述

記錄日誌的必要性

程序一旦部署後,就不大可能再利用專門的調試工具了。日誌記錄往往是軟件開發週期中重要的組成部分,可供開發人員儘快找到應用程序中的bug,一旦在程序中加入了Log輸出,程序在運行過程中就能生成並輸出日誌信息而無需人工干預。

結構

Log4Net四種主要的組件,分別是:

Logger(記錄器)

Repository(庫)

Appender(附着器)

Layout(佈局)

日誌的級別

級別 允許的方法 Boolean**屬性** 優先級別
OFF Highest
FATAL void Fatal(…); bool IsFatalEnabled;
RROR void Error(…); bool IsErrorEnabled;
WARN void Warn(…); bool IsWarnEnabled;
INFO void Info(…); bool IsInfoEnabled;
DEBUG void Debug(…); bool IsDebugEnabled;
ALL Lowest

在log4net框架裏,通過設置配置文件,每個日誌對象都被分配了一個日誌優先級別。如果沒有給一個日誌對象顯式地分配一個級別,那麼該對象會試圖從他的祖先繼承一個級別值。

舉例說明,當你創建了一個日誌對象,並且把他的級別設置爲INFO。於是框架會設置日誌的每個Boolean屬性。當你調用相應的日誌方法時,框架會檢查相應的Boolean屬性,以決定該方法能不能執行。如下的代碼:

Logger.Info("message");
Logger.Debug("message");
Logger.Warn("message");

對於第一種方法,Info()的級別等與日誌的級別(INFO),因此日誌請求會被傳遞,我們可以得到輸出結果”message”。

對於第二種方法,Debug()的級別低於日誌對象logger的日誌級別(INFO),因此,日誌請求被拒絕了,我們得不到任何輸出。同樣的,針對第三行語句,我們可以很容易得出結論。

在上表中有兩個特殊的級別:ALL和OFF。ALL表示允許所有的日誌請求。OFF是拒絕所有的請求。

Repository

Repository主要用於負責日誌對象組織結構的維護。如果你是個log4net框架的使用者,而非擴展者,那麼你幾乎不會在你的代碼裏用到Repository的類。相反的,你需要用到LogManager類來自動管理庫和日誌對象。

Appender

一個好的日誌框架應該能夠產生多目的地的輸出。比如說輸出到控制檯或保存到一個日誌文件log4net 能夠很好的滿足這些要求。它使用一個叫做Appender的組件來定義輸出介質。

Layout 組件用於向用戶顯示最後經過格式化的輸出信息。

在程序中使用Log4Net

定義配置文件

在開始對你的程序進行日誌記錄前,需要先啓動log4net引擎。這意味着你需要先配置前面提到的三種組件。你可以用兩種方法來設定配置:在單獨的文件中設定配置或在代碼中定義配置。

使用配置文件

當我們創建了上面的配置文件後,我們接下來需要把它和我們的應用聯繫起來。缺省的,每個獨立的可執行程序集都會定義它自己的配置。log4net框架使用 log4net.Config.DOMConfiguratorAttribute在程序集的級別上定義配置文件。

簡而言之

使用log不過是記錄程序當前的運行狀態,所以,大概分爲以下幾個步驟:

1、NuGet包管理器去找log4Net;

2、配置log4Net.config,也可以直接配在App.config裏面

3、在需要使用log輸出的命名空間下加入:

[assembly: log4net.Config.XmlConfigurator(ConfigFile="Log4Net.config", Watch=true)]

4、cs文件中對log4Net的不同等級的引用


Control.DataBinding數據綁定

引:

數據綁定即控件與數據表、行之間的自動同步。怎麼理解自動同步呢?首先聊聊手動同步,比如,Form1上有一個TextBox1的控件,我要給TextBox1賦值,一般的做法即讓TextBox1的Text屬性等於你所要賦的值。當我想取出TextBox1中的值時,應該聲明一個字符串類型的變量來接收Text值,而當我再改變TextBox1的值時,string的值不會跟着變,我必須重新取出TextBox1的值給string纔可以。那麼有什麼辦法可以不重複操作而達到這樣的效果呢?這個便用到C#中Control.DataBinding類。它能夠自動刷新數據。

DataBindings

WinForm中的很多控件,如Label、TextBox等都包含DataBindings屬性,其類型爲ControlBindingsCollection,是Binding類的集合。Binding類代表某對象屬性值和某控件屬性值之間的簡單綁定。

DataBindings屬性是很多控件都有的屬性,作用有2方面。一方面是用於與數據庫的數據進行綁定,進行數據顯示。另一方面用於與控件或類的對象進行數據綁定。

DataBindings的一個常用的方法:

 /************************************************
  * 第一個值:要綁定到TextBox的什麼地方
  * 第二個值:數據源
  * 第三個值:數據源的什麼屬性,即下例中,"Value"是trackBar1的屬性
  * 第四個值:是否開啓數據格式化
  * 第五個值:在什麼時候啓用數據源綁定——可以保證實時更新數據
  * *********************************************/
  textBox1.DataBindings.Add("Text", trackBar1, "Value", false, DataSourceUpdateMode.OnPropertyChanged);

也可以將自己的類作爲數據源,獲取類中的屬性。

MSDN中的類似一個例子:

//將某個對象的某個屬性與指定對象的指定屬性進行關聯,如下所示。
textbox1.DataBindings.Add(new Binding("Text", Label1, "Name"));
//將Label1.Name屬性與textbox1進行綁定。

中提到的例子,這裏需要實現INotifyPropertyChanged接口,通知屬性值發生變化,進一步通知DataBindings實時更新TextBox1的顯示。

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace samplesForUpdateBindings
{
    public partial class Form1 : Form,INotifyPropertyChanged
    {
        private string _s;
        public string S
        {
            get { return _s; }
            set
            {
                if (_s != value)
                { 
                    _s = value;
                    FirePropertyChanged("S"); }
            } //<--通知屬性更改,相關控件可以更新顯示
        }
        public Form1()
        {
            InitializeComponent();
            this.S = "start";
            textBox1.DataBindings.Add("Text", this, "S", false, DataSourceUpdateMode.OnPropertyChanged);
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;
        void FirePropertyChanged(string propertyName)
        {
            PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
            if (propertyChanged != null) propertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

        private void button1_Click(object sender, EventArgs e)
        {
            this.S = "end";
        }
    }
}

如果不實現INotifyPropertyChanged接口,DataBindings便會失效。

另外,用BindingList替換List可以通知到控件:數據已經更改。以下的兩個例子:

//From: MSDN Examples
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace MsSample
{
    public partial class Form1 : Form
    {
        // This button causes the value of a list element to be changed.  
        private Button changeItemBtn = new Button();

        // This DataGridView control displays the contents of the list.  
        private DataGridView customersDataGridView = new DataGridView();

        // This BindingSource binds the list to the DataGridView control.  
        private BindingSource customersBindingSource = new BindingSource();
        public Form1()
        {
            InitializeComponent();

            // Set up the "Change Item" button.  
            this.changeItemBtn.Text = "Change Item";
            this.changeItemBtn.Dock = DockStyle.Bottom;
            this.changeItemBtn.Click +=
                new EventHandler(changeItemBtn_Click);
            this.Controls.Add(this.changeItemBtn);

            // Set up the DataGridView.  
            customersDataGridView.Dock = DockStyle.Top;
            this.Controls.Add(customersDataGridView);

            this.Size = new Size(400, 200);
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            // Create and populate the list of DemoCustomer objects  
            // which will supply data to the DataGridView.  
            BindingList<DemoCustomer> customerList = new BindingList<DemoCustomer>();
            customerList.Add(DemoCustomer.CreateNewCustomer());
            customerList.Add(DemoCustomer.CreateNewCustomer());
            customerList.Add(DemoCustomer.CreateNewCustomer());

            // Bind the list to the BindingSource.  
            this.customersBindingSource.DataSource = customerList;

            // Attach the BindingSource to the DataGridView.  
            this.customersDataGridView.DataSource =
                this.customersBindingSource;
        }

        // Change the value of the CompanyName property for the first   
        // item in the list when the "Change Item" button is clicked.  
        void changeItemBtn_Click(object sender, EventArgs e)
        {
            // Get a reference to the list from the BindingSource.  
            BindingList<DemoCustomer> customerList =
                this.customersBindingSource.DataSource as BindingList<DemoCustomer>;

            // Change the value of the CompanyName property for the   
            // first item in the list.  
            customerList[0].CustomerName = "Tailspin Toys";
            customerList[0].PhoneNumber = "(708)555-0150";
        }
    }

    // This is a simple customer class that   
    // implements the IPropertyChange interface.  
    public class DemoCustomer : INotifyPropertyChanged
    {
        // These fields hold the values for the public properties.  
        private Guid idValue = Guid.NewGuid();
        private string customerNameValue = String.Empty;
        private string phoneNumberValue = String.Empty;

        public event PropertyChangedEventHandler PropertyChanged;

        // This method is called by the Set accessor of each property.  
        // The CallerMemberName attribute that is applied to the optional propertyName  
        // parameter causes the property name of the caller to be substituted as an argument.  
        private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        // The constructor is private to enforce the factory pattern.  
        private DemoCustomer()
        {
            customerNameValue = "Customer";
            phoneNumberValue = "(312)555-0100";
        }

        // This is the public factory method.  
        public static DemoCustomer CreateNewCustomer()
        {
            return new DemoCustomer();
        }

        // This property represents an ID, suitable  
        // for use as a primary key in a database.  
        public Guid ID
        {
            get
            {
                return this.idValue;
            }
        }

        public string CustomerName
        {
            get
            {
                return this.customerNameValue;
            }

            set
            {
                if (value != this.customerNameValue)
                {
                    this.customerNameValue = value;
                    NotifyPropertyChanged();
                }
            }
        }

        public string PhoneNumber
        {
            get
            {
                return this.phoneNumberValue;
            }

            set
            {
                if (value != this.phoneNumberValue)
                {
                    this.phoneNumberValue = value;
                    NotifyPropertyChanged();
                }
            }
        }
    }
}

也可以用以下方式直接更新:

 public BindingList<BlogNew> blogNewsRegardUI {get;set; }  //應用於DataGridView界面UI更新 

 private void mainFrm_Load(object sender, EventArgs e)
 {
       blogNewsRegardUI = new BindingList<BlogNew>();
       blogNewsRegardUI.Add(new BlogNew { BlogID = 11, BlogTitle = "僵臥孤村不自哀" });
       blogNewsRegardUI.Add(new BlogNew { BlogID = 12, BlogTitle = "尚思爲國戍輪臺" });
       blogNewsRegardUI.Add(new BlogNew { BlogID = 13, BlogTitle = "夜闌臥聽風吹雨" });

       dataGridView2.DataBindings.Add("DataSource", this, "blogNewsRegardUI", false, DataSourceUpdateMode.OnPropertyChanged);

  }


  private void button4_Click(object sender, EventArgs e)
  {
       /*這裏主要用來解決DataGridView1界面不更新的問題,其實原因在於使用了List<BlogNew>,這裏我們採用BindList<BlogNew>
        *通過測試,我們發現,只要數據源改變,界面就可以自動的進行更新了,很是方便,不需要重新綁定
         */
        var dataRegardUI = dataGridView2.DataSource as BindingList<BlogNew>;
        dataRegardUI.Add(new BlogNew { BlogID = 20, BlogTitle = "竹外桃花三兩枝,春江水暖鴨先知" });
  }

.Net 網絡

.NET Framework允許應用程序使用IPV4和 IPV6。

見【.Net網絡】一節

.Net 平臺下C#socket 通信

什麼是socket?

在操作系統中,通常會爲應用程序提供一組應用程序接口(API),稱爲套接字接口(英語:socket API)。應用程序可以通過套接字接口,來使用網絡套接字,以進行數據交換。最早的套接字接口來自於4.2 BSD,因此現代常見的套接字接口大多源自Berkeley套接字(Berkeley sockets)標準。在套接字接口中,以IP地址及通信端口組成套接字地址(socket address)。遠程的套接字地址,以及本地的套接字地址完成連接後,再加上使用的協議(protocol),這個五元組(five-element tuple),作爲套接字對(socket pairs),之後就可以彼此交換數據。參考:維基百科

注:socketsocket,協議是協議,不要把兩個概念混淆。

自定義協議

目前業界主要採取的協議定義方式是:包頭+包體長度+包體。具體如下:

一般包頭使用一個int定義,例如int = 173173173;作用是區分每一個有效的數據包,因此我們的服務器可以通過這個int去切割、合併包,組裝出完整的傳輸協議。有人使用回車字符去分割包體,例如常見的SMTP/POP協議,這種做法在特定的協議是沒有問題的,可是如果我們傳輸的信息內容自帶了回車字符串,那麼就糟糕了。所以在設計協議的時候要特別小心。

包體長度使用一個int定義,這個長度表示包體所佔的比特流長度,用於服務器正確讀取並分割出包。

包體就是自定義的一些協議內容,例如是對像序列化的內容(現有的系統已經很常見了,使用對象序列化、反序列化能夠極大簡化開發流程,等版本穩定後再轉入手工壓入byte操作)。

一個實際編寫的例子:比如我要傳輸2個整型 int = 1, int = 2,那麼實際傳輸的數據包如下:

包頭 包體長度 包體
173173173 8 1 2

這個數據包就是4個整型,總長度 = 4*4 = 16。

具體詳見【C#自定義協議】

TCP/IP

TCP/IP:Transmission Control Protocol/Internet Protocol,傳輸控制協議/因特網互聯協議,又名網絡通訊協議。簡單來說:TCP控制傳輸數據,負責發現傳輸的問題,一旦有問題就發出信號,要求重新傳輸,直到所有數據安全正確地傳輸到目的地,而IP是負責給因特網中的每一臺電腦定義一個地址,以便傳輸。

TCP/IP由:網絡接口層(鏈路層)、網絡層、傳輸層、應用層。

Tcp/Ip三次握手

1 客戶端發送syn報文到服務器端,並置發送序號爲x。

2 服務器端接收到客戶端發送的請求報文,然後向客戶端發送syn報文,並且發送確認序號x+1,並置發送序號爲y。

3 客戶端受到服務器發送確認報文後,發送確認信號y+1,並置發送序號爲z。至此客戶端和服務器端建立連接。

31093719-34ccd74b68c646859b6b9ddd8620217f


C#通信——自定義協議

參考

數據協議

簡單點說,數據通信協議就是數據通信系統中通信對象之間能準確有效地進行通信所必須遵循的規則和各種約定事項,是爲保證數據通信網中通信雙方能有效,可靠通信而規定的一系列約定。這些約定包括數據的格式,順序和速率,數據傳輸的確認或拒收,差錯檢測,重傳控制和詢問等操作。

消息的結構

消息頭 + 消息體。每次先發送出去的是消息頭,然後是消息體。消息頭裏描述了這個數據包的類型,長度,序列號等信息。消息頭的長度是固定的,消息體的長度是根據每個消息類型會有所的區別。

消息頭的定義:

字段 長度(字節) 類型 說明
Length 4 Int 消息的總長度(字節)
Command ID 4 Int 命令ID
NodeID 4 Int 結點ID
TimeID 4 Int 時間戳
SequenceID 4 Int 遞增序列號

要成爲一個完整的消息,一般還必須包含消息體,客戶機與服務器連接上後,它通常會發送一個綁定(Bind) 消息給服務器端。例如:驗證確認客戶端的合法性。那麼此時的Bind消息的格式是:

字段 長度(字節) 類型 說明
HEAD 上面的消息頭部
loginName 16 string 用戶名(固定16位,不足用空格填充)
LoginPassword 16 string 密碼(固定16位,不足用空格填充)

1、用於IP地址的.Net類

.NET Framework提供了許多能夠幫助尋找IP地址和主機信息的類。

(1)IPAddress類

IPAdkss類代表IP地址。IPAddress類也實現靜態的Parse()方法,這個方法的作用與ToString()方法正好相反,把小數點隔開的十進制字符串轉化爲IP地址。

IPAddress ipAddress = IPAddress.Parse("234.12.1.2");

//得到一個byte數組,分別存放234 12 1 2
byte[] address = ipAddress.GetAddressBytes();

//是和Parse相逆的操作
string ipString = ipAddress.ToString();

//Loopback地址允許計算機給它自己發送消息,loopback的值爲"127.0.0.1"
string loopback = IPAddress.Loopback.ToString();

//Broadcast地址允許多播到本地網絡,用來給本地所有機器發送信息,返回值爲""255,255,255,255
string broadcast = IPAddress.Broadcast.ToString();

(2)IPHostEntry類

IPHostEntry類用於封裝與某臺特定的主機相關的信息。通過這個類的HostName屬性(這個屬性返回一個字符串),可以使用主機名;通過AddressList屬性返回一個IPAddress對象數組。

(3)Dns類

該類能與默認的DNS服務器進行通信,以檢索IP地址。Dns類有兩個重要的靜態方法:Resolve()方法和GetHostByAddress()

===================================================


斷點續傳

斷點續傳即下載任務暫停後可以繼續,而無需重新下載,即下載時需要通知服務器的起始位置。如果允許多線程進行分片下載,必須提供起始-截止位置。說到底就是可以選擇下載某個片段,整個文件的字節流,可以截取流的片段,也能實現流的累積,最終完成文件下載。

C#編程總結(十二)斷點續傳 (基於HTTP,暫時不看)

HTTP協議

HTTP協議是一種基於TCP的簡單協議,分爲請求回覆兩種。

請求協議是由 客戶機(瀏覽器)向服務器(WEB SERVER)提交請求時發送報文的協議。

回覆協議是由服務器(web server),向客戶機(瀏覽器)回覆報文時的協議。

請求和回覆協議都由頭和體組成。頭和體之間以一行空行爲分隔。

原理

在 HTTP/1.1裏新增的一個頭屬性:Range,也是現在衆多號稱多線程下載工具(如 FlashGet、迅雷等)實現多線程下載的核心所在。老版本的HTTP協議不支持,所以一些老的服務器還不支持斷點續傳。


序列化與反序列化

序列化

將對象的狀態信息轉換爲可以存儲或傳輸的形式的過程。

反序列化

序列化的逆過程。

C#下的實現思路:利用結構體轉換——

參考

//C#中結構體與字節流互相轉換
class Converter  
   {  
       //Structure轉爲Byte數組,實現了序列化  
       public static Byte[] StructToBytes(Object structure)  
       {  
           Int32 size = Marshal.SizeOf(structure);  
           Console.WriteLine(size);  
           IntPtr buffer = Marshal.AllocHGlobal(size);  
           try  
           {  
               Marshal.StructureToPtr(structure, buffer, false);  
               Byte[] bytes = new Byte[size];  
               Marshal.Copy(buffer, bytes, 0, size);  
               return bytes;  
           }  
           finally  
           {  
               Marshal.FreeHGlobal(buffer);  
           }  
       }  
       //Byte數組轉爲Structure,實現了反序列化  
       public static Object BytesToStruct(Byte[] bytes, Type strcutType)  
       {  
           Int32 size = Marshal.SizeOf(strcutType);  
           IntPtr buffer = Marshal.AllocHGlobal(size);  
           try  
           {  
               Marshal.Copy(bytes, 0, buffer, size);  
               return Marshal.PtrToStructure(buffer, strcutType);  
           }  
         //不管程序執行try還是catch,finally 始終會執行.通常在finally語句中是進行資源的清除工作。如關閉打開的文件和通訊句柄,或者數據庫鏈接等。
           finally  
           {  
               Marshal.FreeHGlobal(buffer);  
           }  
       }  
   }  

關於IntPtr

首先看一下System.Runtime.InteropServices.Marshal.AllocHGlobal方法:

Calls the System.Runtime.InteropServices.Marshal.AllocHGlobal method to allocate the same number of bytes as the unmanaged string occupies. The method returns an IntPtr object that points to the beginning of the unmanaged block of memory. The Visual Basic example uses this pointer directly; in the C++ and C# examples, it is cast to a pointer to a byte.(From MSDN)

//MSDN中關於內存和指針操作的一個例子
class Serielize
{
    static public void Main()
    {
        string stringA = "I seem to be turned around!";
        int copylen = stringA.Length;

        // Allocate HGlobal memory for source and destination strings
        //將 ANSI 字符從指定的字符串(在託管堆中)複製到非託管堆中的緩衝區。該方法(StringToHGlobalAnsi)還分配大小正確的目標堆。
        IntPtr sptr = Marshal.StringToHGlobalAnsi(stringA);
        IntPtr dptr = Marshal.AllocHGlobal(copylen + 1);

        // The unsafe section where byte pointers are used.
        //在運行unsafe 代碼是要在項目屬性-生成選項裏配置下"允許運行不安全代碼"。
        unsafe
        {
            byte* src = (byte*)sptr.ToPointer();
            byte* dst = (byte*)dptr.ToPointer();

            if (copylen > 0)
            {
                // set the source pointer to the end of the string
                // to do a reverse copy.
                src += copylen - 1;

                while (copylen-- > 0)
                {
                    *dst++ = *src--;
                }
                //結尾爲0,用來標誌字符串的結束
                *dst = 0;
            }
        }
        string stringB = Marshal.PtrToStringAnsi(dptr);

        Console.WriteLine("Original:\n{0}\n", stringA);
        Console.WriteLine("Reversed:\n{0}", stringB);

        // Free HGlobal memory
        //釋放非託管資源
        Marshal.FreeHGlobal(dptr);
        Marshal.FreeHGlobal(sptr);
    }
}

從上例可以看出,

  1. IntPtr對象是C#中的內存對象,可以通過調用ToPointer()函數的方法返回內存開始位置的指針;
  2. 同時也可以看出,C#中字符串結尾的標誌位也是0;
  3. Marshal.AllocHGlobal(size)和C語言中的malloc(size)的作用是一樣的,分配指定大小的內存空間;
  4. Marshal.StringToHGlobalAnsi(stringA)爲獲取指定對象的可操作內存;

關於Marshal.PtrToStructure Method系列

其實就是PtrTo系列函數,它們的作用就是將格式化內存數據——Marshals data from an unmanaged block of memory to a managed object.

小結: Marshal 類中定義的 static 方法對於處理非託管代碼至關重要。此類中定義的大多數方法通常由需要在託管和非託管編程模型之間提供橋樑的開發人員使用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章