在上週,我密集面試了若干位Java後端的候選人,工做經驗在3到5年間。個人標準其實不復雜:第一能幹活,第二Java基礎要好,第三最好熟悉些分佈式框架,我相信其它公司招初級開發時,應該也照着這個標準來面的。css
我也知道,很多候選人能力其實不差,但面試時沒準備或不會說,這樣的人可能在進團隊幹活後確實能達到指望,但可能就沒法經過面試,但面試官老是隻根據面試狀況來判斷。html
但現實狀況是,大多數人可能面試前沒準備,或準備方法不得當。要知道,咱們平時幹活更偏重於業務,不可能大量接觸到算法,數據結構,底層代碼這類面試必問的問題點,換句話說,面試準備點和平時工做要點匹配度很小。前端
做爲面試官,我只能根據候選人的回答來決定面試結果。不過,與人方便本身方便,因此我在本文裏,將經過一些經常使用的問題來介紹面試的準備技巧。你們在看後必定會感嘆:只要方法得當,準備面試第一不難,第二用的時間也不會太多。java
1 框架是重點,但別讓人感受你只會山寨別人的代碼jquery
在面試前,我會閱讀簡歷以查看候選人在框架方面的項目經驗,在候選人的項目介紹的環節,我也會着重關注候選人最近的框架經驗,目前比較熱門的是SSM。linux
不過,通常工做在5年內的候選人,大多僅僅是能「山寨」別人的代碼,也就是說能在現有框架的基礎上,照着別人寫的流程,擴展出新的功能模塊。好比要寫個股票掛單的功能模塊,是會模仿現有的下單流程,而後從前端到後端再到數據庫,依樣畫葫蘆寫一遍,最多把功能相關的代碼點改掉。ios
其實咱們每一個人都這樣過來的,但在面試時,若是你僅僅表現出這樣的能力,就和大多數人的水平差很少了,在這點就無法體現出你的優點了。nginx
咱們知道,若是單純使用SSM框架,大多數項目都會有痛點。好比數據庫性能差,或者業務模塊比較複雜,併發量比較高,用Spring MVC裏的Controller沒法知足跳轉的需求。因此我通常還會主動問:你除了依照現有框架寫業務代碼時,還作了哪些改動?c++
我聽到的回答有:增長了Redis緩存,以免頻繁調用一些不變的數據。或者,在MyBitas的xml裏,select語句where條件有isnull,即這個值有就增長一個where條件,對此,會對任何一個where增長一個不帶isnull的查詢條件,以避免該語句當傳入參數都是null時,作全表掃描。或者,乾脆說,後端異步返回的數據量很大,時間很長,我在項目裏就調大了異步返回的最大時間,或者對返回信息作了壓縮處理,以增長網絡傳輸性能。git
對於這個問題,我不在意聽到什麼回答,我只關心回答符不符邏輯。通常只要答對,我就會給出「在框架層面有本身的體會,有必定的瞭解」,不然,我就只會給出「只能在項目經理帶領下編寫框架代碼,對框架自己瞭解很少」。
其實,在準備面試時,概括框架裏的要點並不難,我就不信全部人在作項目時一點積累也沒,只要你說出來,能夠說,這方面你就碾壓了將近7成的競爭者。
2 別單純看單機版的框架,適當瞭解些分佈式
此外,在描述項目裏框架技術時,最好你再帶些分佈式的技術。下面我列些你們能夠準備的分佈式技術。
1 反向代理方面,nginx的基本配置,好比如何經過lua語言設置規則,如何設置session粘滯。若是能夠,再看些nginx的底層,好比協議,集羣設置,失效轉移等。
2 遠程調用dubbo方面,能夠看下dubbo和zookeeper整合的知識點,再深一步,瞭解下dubbo底層的傳輸協議和序列化方式。
3 消息隊列方面,能夠看下kafka或任意一種組件的使用方式,簡單點能夠看下配置,工做組的設置,再深刻點,能夠看下Kafka集羣,持久化的方式,以及發送消息是用長鏈接仍是短攔截。
以上僅僅是用3個組件舉例,你們還能夠看下Redis緩存,日誌框架,MyCAT分庫分表等。準備的方式有兩大類,第一是要會說怎麼用,這比較簡單,能經過配置文件搭建成一個功能模塊便可,第二是能夠適當讀些底層代碼,以此瞭解下協議,集羣和失效轉移之類的高級知識點。
若是能在面試中侃侃而談分佈式組件的底層,那麼獲得的評價就會比較好了,好比「深刻了解框架底層」,或「框架經驗豐富」,這樣就算去面試架構師也行了,更況且是高級開發。
3 數據庫方面,別就知道增刪改查,得了解性能優化
在實際項目裏,大多數程序員用到的可能僅僅是增刪改查,當咱們用Mybatis時,這個狀況更廣泛。不過若是你面試時也這樣表現,估計你的能力就和其它競爭者差很少了。
這方面,你能夠準備以下的技能。
1 SQL高級方面,好比group by, having,左鏈接,子查詢(帶in),行轉列等高級用法。
2 建表方面,你能夠考慮下,你項目是用三範式仍是反範式,理由是什麼?
3 尤爲是優化,你能夠準備下如何經過執行計劃查看SQL語句改進點的方式,或者其它能改善SQL性能的方式(好比建索引等)。
4 若是你感受有能力,還能夠準備些MySQL集羣,MyCAT分庫分表的技能。好比經過LVS+Keepalived實現MySQL負載均衡,MyCAT的配置方式。一樣,若是能夠,也看些相關的底層代碼。
哪怕你在前三點表現通常,那麼至少也能超越將近通常的候選人,尤爲當你在SQL優化方面表現很是好,那麼你在面試高級開發時,數據庫層面必定是達標的,若是你連第四點也回答很是好,那麼恭喜你,你在數據庫方面的能力甚至達到了初級架構的級別。
4 Java核心方面,圍繞數據結構和性能優化準備面試題
Java核心這塊,網上的面試題不少,不過在此以外,你們還應當着重關注集合(即數據結構)和多線程併發這兩塊,在此基礎上,你們能夠準備些設計模式和虛擬機的說辭。
下面列些我通常會問的部分問題:
1 String a = "123"; String b = "123"; a==b的結果是什麼? 這包含了內存,String存儲方式等諸多知識點。
2 HashMap裏的hashcode方法和equal方法何時須要重寫?若是不重寫會有什麼後果?對此你們能夠進一步瞭解HashMap(甚至ConcurrentHashMap)的底層實現。
3 ArrayList和LinkedList底層實現有什麼差異?它們各自適用於哪些場合?對此你們也能夠了解下相關底層代碼。
4 volatile關鍵字有什麼做用?由此展開,你們能夠了解下線程內存和堆內存的差異。
5 CompletableFuture,這個是JDK1.8裏的新特性,經過它怎麼實現多線程併發控制?
6 JVM裏,new出來的對象是在哪一個區?再深刻一下,問下如何查看和優化JVM虛擬機內存。
7 Java的靜態代理和動態代理有什麼差異?最好結合底層代碼來講。
經過上述的問題點,我其實不只僅停留在「會用」級別,好比我不會問如何在ArrayList裏放元素。你們能夠看到,上述問題包含了「多線程併發」,「JVM優化」,「數據結構對象底層代碼」等細節,你們也能夠觸類旁通,經過看一些高級知識,多準備些其它相似面試題。
咱們知道,目前Java開發是以Web框架爲主,那麼爲何還要問Java核心知識點呢?我這個是有切身體會的。
以前在我團隊裏,我見過兩我的,一個是就會幹活,具體表現是會用Java核心基本的API,並且也沒有深刻了解的意願(估計不知道該怎麼深刻了解),另外一位平時專門會看些Java併發,虛擬機等的高級知識。過了半年之後,後者的能力快速升級到高級開發,因爲對JAVA核心知識點了解很透徹,因此看一些分佈式組件的底層實現沒什麼大問題。 而前者,一直在重複勞動,能力也只一直停留在「會幹活」的層面。
而在現實的面試中,若是不熟悉Java核心知識點,估計升高級開發都難,更別說是面試架構師級別的崗位了。
5 Linux方面,至少了解如何看日誌排查問題
若是候選人能證實本身有「排查問題」和「解決問題」的能力,這絕對是個加分項,但怎麼證實?
目前大多數的互聯網項目,都是部署在Linux上,也就是說,日誌都是在Linux,下面概括些實際的Linux操做。
1 能經過less命令打開文件,經過Shift+G到達文件底部,再經過?+關鍵字的方式來根據關鍵來搜索信息。
2 能經過grep的方式查關鍵字,具體用法是, grep 關鍵字 文件名,若是要兩次在結果裏查找的話,就用grep 關鍵字1 文件名 | 關鍵字2 --color。最後--color是高亮關鍵字。
3 能經過vi來編輯文件。
4 能經過chmod來設置文件的權限。
固然,還有更多更實用的Linux命令,但在實際面試過程當中,很多候選人連一條linux命令也不知道。仍是這句話,你哪怕知道些很基本的,也比通常人強了。
6 通讀一段底層代碼,做爲加分項
如何證實本身對一個知識點很是瞭解?莫過於能經過底層代碼來講明。我在和很多工做經驗在5年以內的程序員溝通時,很多人認爲這很難?確實,若是要經過閱讀底層代碼瞭解分佈式組件,那難度不小,但若是以下部分的底層代碼,並不難懂。
1 ArrayList,LinkedList的底層代碼裏,包含着基於數組和鏈表的實現方式,若是你們能以此講清楚擴容,「經過枚舉器遍歷「等方式,絕對能證實本身。
2 HashMap直接對應着Hash表這個數據結構,在HashMap的底層代碼裏,包含着hashcode的put,get等的操做,甚至在ConcurrentHashMap裏,還包含着Lock的邏輯。我相信,若是你們在面試中,看看而言ConcurrentHashMap,再結合在紙上邊說邊畫,那必定能征服面試官。
3 能夠看下靜態代理和動態代理的實現方式,再深刻一下,能夠看下Spring AOP裏的實現代碼。
4 或許Spirng IOC和MVC的底層實現代碼比較難看懂,但你們能夠說些關鍵的類,根據關鍵流程說下它們的實現方式。
其實準備的底層代碼未必要多,並且也不限於在哪一個方面,好比集合裏基於紅黑樹的TreeSet,基於NIO的開源框架,甚至分佈式組件的Dubbo,均可以準備。並且準備時未必要背出全部的底層(事實上很難作到),你只要能結合一些重要的類和方法,講清楚思路便可(好比講清楚HashMap如何經過hashCode快速定位)。
那麼在面試時,如何找到個好機會說出你準備好的上述底層代碼?在面試時,總會被問到集合,Spring MVC框架等相關知識點,你在回答時,順便說一句,「我還了解這塊的底層實現」,那麼面試官必定會追問,那麼你就能夠說出來了。
不要小看這個對候選人的幫助,一旦你講了,只要意思到位,那麼最少能獲得個「肯積極專業「的評價,若是描述很清楚,那麼評價就會升級到「熟悉Java核心技能(或Spring MVC),且基本功紮實」。要知道,面試中,不多有人能講清楚底層代碼,因此你拋出了這個話題,哪怕最後沒達到預期效果,面試官也不會由此對你下降評價。因此說,準備這塊絕對是「有百利而無一害」的掙錢買賣。
7 一切的一切,把上述技能嵌入到你作過的項目裏
在面試過程當中,我常常會聽到一些比較遺憾的回答,好比候選人對SQL優化技能講得頭頭是道,但最後得知,這是他平時自學時掌握的,並沒用在實際項目裏。
固然這總比不說要好,因此我會寫下「在平時自學過SQL優化技能」,但若是在項目裏實踐過,那麼我就會寫下「有實際數據庫SQL優化的技能」。你們能夠對比下二者的差異,一個是偏重理論,一個是直接能幹活了。其實,不少場景裏,我就不信在實際項目裏必定沒有實踐過SQL優化技能。
從這個案例中,我想告訴你們的是,你以前費了千辛萬苦(其實方法方向獲得,也不用費太大精力)準備的不少技能和說辭,最後應該落實到你的實際項目裏。
好比你有過在Linux日誌裏查詢關鍵字排查問題的經驗,在描述時你能夠帶一句,在以前的項目裏我就這樣乾的。又如,你經過看底層代碼,瞭解了TreeSet和HashSet的差異以及它們的適用範圍,那麼你就能夠回想下你以前作的項目,是否有個場景僅僅適用於TreeSet?若是有,那麼你就能夠適當描述下項目的需求,而後說,經過讀底層代碼,我瞭解了二者的差異,並且在這個實際需求裏,我就用了TreeSet,並且我還專門作了對比性試驗,發現用TreeSet比HashSet要高xx個百分點。
請記得,「實踐經驗」必定比「理論經驗」值錢,並且大多數你知道的理論上的經驗,必定在你的項目裏用過。因此,若是你僅僅讓面試官感受你只有「理論經驗」,那就太虧了。
8 小結:本文更多講述的準備面試的方法
本文給出的面試題並很少,但本文並無打算給出太多的面試題。從本文裏,你們更多看到的是面試官發現的諸多候選人的痛點。
本文的用意是讓你們別再重蹈別人的覆轍,這還不算,本文還給出了很多準備面試的方法。你的能力或許比別人出衆,但若是你準備面試的方式和別人差很少,或者就拿你在項目裏乾的活來講事,而沒有概括出你在項目中的亮點,那麼面試官還真的會看扁你。
本文裏提到的方法和技能,若是能對你們有所幫助,請你們幫忙轉發,或者點擊下面的按鈕來「推薦本文」,或者經過評論來參與討論。
本文歡迎轉載,不過請註明文章來源,若是能夠,請同時給出本人寫的兩本書的鏈接Java Web輕量級開發面試教程和Java核心技術及面試指南。
再次感謝你們讀完本文。
你們在面試時,哪怕準備再充分,也不均可能一路順風。請記住:面試回答很差是很正常的,尤爲在你剛開始面試的時候。
因此你必定要有良好的心態:面試成了最好,不成的話面試官是免費給你一次鍛鍊的機會,並且免費告訴了你一些面試題,你也是賺的。但若是你一方面被在面試中表現很差,同時另外一方面什麼都不作,那麼我能夠說,哪怕面試十次也不會提升,並且即便你進了公司,你的薪資待遇也是被嚴重低估的。
在本文裏,就將結合本人面試官的經驗,告訴你們一些能夠操做的作法。
1 坦誠相對,說明你的擅長點,讓面試官給次機會
我遇到過個別候選人,他技術點知道一點,並不是什麼都不知道,屬於可上可下的。好比項目是要SSM框架,但他在這方面只有學習經驗,沒商用項目經驗,但他JDK,數據庫能夠,他就直說,SSM不行,但亮出他的長處,好比舉例說明他學習能力很強,或者很能吃苦,溝通能力能夠,而後表達出強烈想入職的願望,我通常都會給出「技術能夠(或技術勉強能夠),能參加後繼面試」的評語。
你們在面試的時候,回答問題好壞本身能估計出來,若是太差,屬於一問三不知的,即便說這種話也沒用,但若是你感受回答的時候並不是一無可取,就能夠找機會說出這種話。
我列出一些能夠做爲補救的因素。
補救因素 |
能夠列出的證據 |
雖然沒有XX,(好比SSM框架方面),項目經驗,但在平時學習過,本身動手寫過代碼。 |
我看過XX書,本身瞭解過這種技術,或者瞭解過同類技術,同時說出對這種技術的瞭解點 |
學習能力很強,有強烈的學習新技術的願望。 |
畢業設計的技術我不知道,但我用了很短期就掌握了,或者之前在公司裏我屬於什麼也不懂的,但我肯問,用了XX時間久知道了。 或者,最近比較熱門的XX技術,雖然我項目裏用不到,但我本身已經學過了,而後說說你的學習狀況。 |
肯吃苦,能加班,能出差,能適應大壓力下的環境 |
列出之前公司加班,壓力大的一些狀況 |
很擅長和別人溝通,在項目裏遇到不熟悉的,肯問別人 |
在之前公司的時候,遇到問題我不會積壓,有需求上的問題找XXX,技術上不懂會找XX,遇到有Bug能找Test |
事先了解到這個公司的項目背景,而後說本身知道這方面的知識 |
好比XX公司作雲計算的,你即便沒項目經驗,甚至也沒有動手寫代碼的經驗,但你能夠說,瞭解過這方面的知識,知道開發流程,知道入手點 |
說明你對Java裏某個技術點研究特別深刻,肯鑽研 |
好比很瞭解Java的內存管理,說明你是經過看文檔或者看底層代碼本身研究說,那麼面試官想一想即便你沒他須要的技能,但有本身的一套研究方法,肯鑽研,也會適當考慮。 |
說明你的責任心,穩定性比較強,肯在一個崗位上很鑽研下去 |
這個比較好說,你們能夠結合本身的狀況自行說明 |
2 經過展現你之前的亮點,讓面試官相信你的潛力和能力
若是你屬於工做經驗少於3年的,面試官其實對你不會要求太苛刻,其實更會關心你的學習能力,工做責任心,承受壓力的狀況,責任心,穩定性,剛纔提到的補救措施你必定要有證聽說明,你得用事實講話,畢竟空口無憑。
下面我舉出一些我面試過程當中聽到的別人說出的一些亮點,你們能夠觸類旁通靈活掌握。
1 我雖然對您剛纔說到的SSM技術瞭解不深刻(事實上他是仍是會在項目經理搭建好框架的基礎上開發,還能知道一點,若是一點也不知道,說了也沒用),但我對MVC框架了解過,我之前作過的項目是用Jsp+Servlet3.0+JDBC實現的,也單獨用過Spring的框架,因此我很快能上手。(我會適當問他JSP+servlet+JDBC裏MVC的流程,若是他能說上來,我就會在評語上寫「瞭解基本的SSM,瞭解MVC框架,知道MVC的開發方式」,但若是他不額外說明,或許我就會寫,「只會在項目經理搭建好的基礎上了解SSM,不瞭解框架細節」,這樣即便他經過個人技術面試,後繼的項目經理看到評語也不會對他有太多的好感)
2 最近的項目裏我沒用到SSM,最近的項目我是在作前臺,這個是在一年前用到的(這個有些危險了,最好是在半年前用過這個技術或者相關相似技術,不過話說回來,你即便最近沒用SSM,但在簡歷上說用過,只要你能回答出基本問題,我也無法覈實),但我對SSM框架了解很深,我知道Spring裏MVC的底層實現,感受Spring的MVC有必定的缺陷,也在商業項目裏搭建過SSM,因此我能很快上手。(這樣我會細問他提到的SSH的底層細節,若是他確實對底層細節瞭解不錯,那麼我會寫上「最近一年沒用過SSM,但對SSM底層有必定的瞭解」,不然的話,我僅僅會寫「最近沒用過SSM,SSM的項目經驗僅限於一年前」,你們能夠比對一下兩個評語之間的差異)。
3 (我是爲一個保險項目招人)我沒有 SSM方面的經驗,之前都是用Jsp+servlet3.0+JDBC這套模式開發的,(這是大實話,不過若是他面試前好好準備的話,不應說出這種話出來),但我之前作過保險相關的項目,客戶是XX,實現了保險項目裏的XX流程,並且我知道一些背景的業務。(這樣我會把決定權交給二面的經理,不然的話,我將直接寫「不瞭解SSH,無法經過面試」)。
4 我對Java技術瞭解通常,(確實通常,只會用語法,不會融會貫通),這是由於我在上個項目裏壓力很大,須要直接和客戶交流,我須要直接和客戶交流,直接瞭解需求,本身開發,本身測試,最後打個Jar包給客戶,因此我感受個人綜合能力很強。(我會關於這方面問點細節問題,好比怎麼打jar包,測試的時候怎麼作的,若是確實能說上來,我會在評語上寫「Java能力通常,但知道整個開發的流程,能獨立地完成某個模塊的任務」,不然我只會寫「Java能力很通常,不瞭解一些深刻的知識點」)。
5 雖然我沒有商業項目的經驗(是個應屆畢業生,簡歷上的項目被我問出是畢業設計或者是課程設計項目,但他若是直接把這些技術寫成在讀書時在外面公司裏作的,我是無法覈實的),但我自學能力比較強,我學習的時候走了很多彎路,但我如今很瞭解JDBC和Spring IOC的底層實現,我知道最近熱門的一些技術,因此大家公司的一些技術我能很快上手。(我會在評語上寫,「沒商業項目經驗,但學習能力很強,請後繼面試官斟酌」,這總比「沒商業項目經驗,不建議經過面試」的評語要好)
4 記下全部的面試題,回家後準備好,迎接下次面試
當你感受你成功應聘這個崗位的但願有些渺茫時,你須要作以下的事情:
1 記錄下全部的技術面試題,回家查資料,爲下次一樣問題作準備。
請記住,這裏必定請舉一反三,好比被問倒了Java多線程方面的知識點,那麼最好把相關Java高併發的知識點都看下。
2 找出沒成功的緣由,好比這個崗位須要有項目經驗的,你所描述的項目經驗最終被認爲是非商業項目,那你就要更新項目描述,下次說的時候讓你的項目聽起來更像商業商業項目,若是是由於你其中針對項目框架數據庫等問題沒回答好從而讓面試官認爲這個不是商業項目,你就得去找一個真實的項目,看看這些技術在項目裏是怎麼實現的。
不要說一些沒什麼工做經驗的,即便一些工做經驗5年以上的資深者,在剛開始換工做的幾家面試公司裏,未必能回答好,由於他即便作了不少準備,也不知道當前面試會問些什麼,因此面試前你要作好「不成功」的準備,成了最好,一旦沒成,積累經驗,下次你就成了。
5 你發現你基礎差,不知道怎麼應對面試時的對策
我大概在2016年6月輔導過一我的面試,他上海一個非著名學校計算機系研究生剛畢業,雖然有碩士學歷,雖然有4年工做經驗,可是讀研前不是作計算機的方面的工做,是電腦銷售之類的工做,我第一次給他作模擬面試的時候,他的Java技能估計還不如平均水平,不瞭解Java內存管理,多線程,集合,數據庫方面僅僅會用最基本的,(你想,讀研3年,一年要寫論文,其實也就2年學習,並且學的僅僅是課本上的基本語法,沒Java工做經驗的,能好到哪裏去?能寫出一個能成功運行的SSM代碼就不錯了)。
我給他的建議是:
1 用最多一週時間,惡補Java,數據庫等各方面知識點,不知道的硬背,儘量多瞭解一些細節。
2 本身從網上找一個或多個SSM的項目,不管是商用的仍是學習的都行,若是找不到,出錢到淘寶之類的地方買一個,找到後先配置運行經過,而後逐一看Spring,Mytibas的作法,這個工做須要在1周內完成,加上第一點的工做,最多在10天內完成。
3 更新簡歷,把第二點學到的項目寫到簡歷裏。
4 準備一些亮點,好比本身搭建過SSM,數據庫方面知道索引,知道SQL調優,知道Java內存管理等等,亮點越多越好。
10天后再找他面試,他也很爭氣,至少能像模像樣說出項目經驗和一些基本技能,我再對他說,你去了解一下測試,設計數據表,需求調研的實施要點,同時到網上多找些面試題準備一下,給你2天時間。
2天之後我看他大體能夠,讓他去面試,剛開始找些小公司練手,他去了3家面試,第一家大概有一半問題沒回答上,第二家在框架,數據庫高級應用,Java複雜知識方面沒答好,第三家回答就不錯(由於該問的他都知道了),要了工資8千(信心不足,要少了),當場就成了。
一個0基礎的人都能這樣,只要你作好充分的準備,也必定能成。
本博文的內容摘自Java Web輕量級開發面試教程
[UWP]在應用開發中安全使用文件資源
在WPF或者UWP應用開發中,有時候會不可避免的須要操做文件系統(建立文件/目錄),這時候有幾個坑是須要你們注意下的。
建立文件或目錄時的非法字符檢測
在Windows系統中,咱們建立文件時會注意到,某些特殊字符是不能夠用做文件名輸入的。
那麼,一樣的,若是你的應用能夠提供給用戶建立文件/目錄的功能,要特別注意的是:你必須對用戶鍵入的文件或者目錄名檢測,避免用戶鍵入非法字符。
不然,應用可能會遇到下面這個bug:System.IO.FileNotFoundException:「文件名、目錄名或卷標語法不正確。」
避免手段其實也很簡單,System.IO.Path類中能夠獲取到全部的非法字符,咱們只須要檢測文件或目錄名,避免出現非法字符就能夠了。
不能夠在文件名中出現的字符 Path.GetInvalidFileNameChars():
char[41] { '"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', '\u001e', '\u001f', ':', '*', '?', '\\', '/' }
不能夠在路徑字符串中出現的字符 Path.GetInvalidPathChars():
char[36] { '"', '<', '>', '|', '\0', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\a', '\b', '\t', '\n', '\v', '\f', '\r', '\u000e', '\u000f', '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001a', '\u001b', '\u001c', '\u001d', '\u001e', '\u001f' }
這裏給你們提供一個小竅門,使用C#交互窗口(VS2015及更高版本均可以使用),能夠快速查看代碼片斷執行結果。
在XAML中引用外部資源時的非法字符檢測
此外,在開發WPF或者UWP應用時,若是咱們須要在XAML中引入外部資源URI,那麼狀況會比較特殊一點。
有時候儘管你的文件名或者路徑URI均沒有包含Windows文件系統中的非法字符,應用仍有可能崩潰。這是由於,在XAML中定義了一些不容許出現的字符,這些字符與Windows文件系統中的非法字符不盡相同。
這些字符是:
{ ';' , '/' , '?' , ':' , '@' , '&' , '=' , '+' , '$' , ',','<' , '>' , '#' , '%' , '"' }
例如‘#’,它在文件系統中是合法字符,可是卻不能出如今XAML中引入的外部資源URI字符串裏。
這個問題在邵猛大佬的《WPF 圖片顯示中的保留字符問題》中也是有講到的,可是文章中沒有給到解決方法。
在某些狀況下,如開發應用時,咱們容許用戶上傳圖片到應用文件夾下做爲資源使用,咱們能夠在拷貝資源時經過排除/替換文件名裏非法字符的方法來避免這個BUG。
public static class XamlUriHelper { private static readonly char[] Excluded = { ';' , '/' , '?' , ':' , '@' , '&' , '=' , '+' , '$' , ',','<' , '>' , '#' , '%' , '"' }; public static string GetValidName(string fileName) { foreach (var item in Excluded) { fileName = fileName.Replace(item, '_'); } return fileName; } }
結尾
上面說到的兩種狀況,第一種是比較好處理的,而第二種須要一些折中的處理手段。另外吐槽一點,XAML應用這麼久了,第二種狀況按理說是不該該出現的,不知道微軟方面有沒有注意到(或者說是否有官方解決方法,相似轉義符什麼的?)。若是有了解這個問題的大佬,歡迎在評論區指出!
請教:WCF速度彷佛比Remoting慢
兩段極爲類似的代碼,主要想看看過輸與序列化過程二者的用時差別,結果10000次的調用,WCF用了11秒多,remoting用了5秒不到!
這是測試的源代碼
Remoting的服務端
1
2
3
4
5
6
7
8
9
10
11
12
|
public
class
RemotCalc : MarshalByRefObject, ICalc
{
public
CalcInfo Calc(CalcInfo pInfo)
{
CalcInfo info =
new
CalcInfo();
info.Method =
string
.Format(
"back_{0}"
, pInfo.Method);
info.Para1 = pInfo.Para1;
info.Para2 = pInfo.Para2;
info.Result = pInfo.Result + 999;
return
info;
}
}
|
WCF的服務端
public class WcfCalc : Srv.Interface.ICalc { public CalcInfo Calc(CalcInfo pInfo) { CalcInfo info = new CalcInfo(); info.Method = string.Format("back_{0}", pInfo.Method); info.Para1 = pInfo.Para1; info.Para2 = pInfo.Para2; info.Result = pInfo.Result + 999; return info; } }
代碼能夠理解爲同樣的,如下的客戶端,兩客戶端也能夠視爲同樣的過程
WCF客戶端
static void Main(string[] args) { using(ChannelFactory<Srv.Interface.ICalc> factory = new ChannelFactory<Srv.Interface.ICalc>("Calc2")) { Srv.Interface.ICalc calc = factory.CreateChannel(); CalcInfo info = new CalcInfo(); info.Method = "test"; Console.WriteLine("press any key to run..."); Console.ReadLine(); int max = 10000; Console.WriteLine("it's run..."); DateTime start = DateTime.Now; for (int i = 0; i < max; i++) { CalcInfo res = calc.Calc(info); } TimeSpan sp = DateTime.Now - start; Console.WriteLine("run {0} times use {1}ms ", max, sp.TotalMilliseconds); Console.ReadLine(); } }
Remoting客戶端
static void Main(string[] args) { ChannelServices.RegisterChannel(new TcpClientChannel(), false); ICalc remoteobj = (ICalc)Activator.GetObject(typeof(ICalc), "tcp://localhost:6666/Calc"); CalcInfo info = new CalcInfo(); info.Method = "test"; Console.WriteLine("press any key to run..."); Console.ReadLine(); int max = 10000; DateTime start = DateTime.Now; for (int i = 0; i < max; i++) { CalcInfo res = remoteobj.Calc(info); } TimeSpan sp = DateTime.Now - start; Console.WriteLine("run {0} times use {1}ms ", max, sp.TotalMilliseconds); Console.ReadLine(); }
.NET Remoting提供了一個功能強大、高效的處理遠程對象的方法,從結構上而言,.NET Remote對象很是適合經過網絡訪問資源,而又無需處理由基於SOAP的WebServices所帶來的難題。.NET Remoting使用起來比Java的RMI簡單,但要比建立Web Service難度大一些。
什麼是Remoting,簡而言之,咱們能夠將其看做是一種分佈式處理方式。從微軟的產品角度來看,能夠說Remoting就是DCOM的一種升級,它改 善了不少功能,並極好的融合到.Net平臺下。Microsoft? .NET Remoting 提供了一種容許對象經過應用程序域與另外一對象進行交互的框架。這也正是咱們使用Remoting的緣由。爲何呢?在Windows操做系統中,是將應用 程序分離爲單獨的進程。這個進程造成了應用程序代碼和數據周圍的一道邊界。若是不採用進程間通訊(RPC)機制,則在一個進程中執行的代碼就不能訪問另外一 進程。這是一種操做系統對應用程序的保護機制。然而在某些狀況下,咱們須要跨過應用程序域,與另外的應用程序域進行通訊,即穿越邊界。
在Remoting中是經過通道(channel)來實現兩個應用程序域之間對象的通訊的。首先,客戶端經過Remoting,訪問通道以得到服務端 對象,再經過代理解析爲客戶端對象。這就提供一種可能性,即以服務的方式來發布服務器對象。遠程對象代碼能夠運行在服務器上(如服務器激活的對象和客戶端 激活的對象),而後客戶端再經過Remoting鏈接服務器,得到該服務對象並經過序列化在客戶端運行。
在Remoting中,對於要傳遞的對象,設計者除了須要瞭解通道的類型和端口號以外,無需再瞭解數據包的格式。但必須注意的是,客戶端在獲取服務器 端對象時,並非得到實際的服務端對象,而是得到它的引用。這既保證了客戶端和服務器端有關對象的鬆散耦合,同時也優化了通訊的性能。
Remoting的兩種通道
Remoting的通道主要有兩種:Tcp和Http。在.Net中,System.Runtime.Remoting.Channel中定義了 IChannel接口。IChannel接口包括了TcpChannel通道類型和Http通道類型。它們分別對應Remoting通道的這兩種類型。
TcpChannel類型放在名字空間System.Runtime.Remoting.Channel.Tcp中。Tcp通道提供了基於 Socket 的傳輸工具,使用Tcp協議來跨越Remoting邊界傳輸序列化的消息流。TcpChannel類型默認使用二進制格式序列化消息對象,所以它具備更高 的傳輸性能。HttpChannel類型放在名字空間System.Runtime.Remoting.Channel.Http中。它提供了一種使用 Http協議,使其能在Internet上穿越防火牆傳輸序列化消息流。默認狀況下,HttpChannel類型使用Soap格式序列化消息對象,所以它 具備更好的互操做性。一般在局域網內,咱們更多地使用TcpChannel;若是要穿越防火牆,則使用HttpChannel。
遠程對象的激活方式
在訪問遠程類型的一個對象實例以前,必須經過一個名爲Activation的進程建立它並進行初始化。這種客戶端經過通道來建立遠程對象,稱爲對象的激活。在Remoting中,遠程對象的激活分爲兩大類:服務器端激活和客戶端激活。
服務器端激活,又叫作WellKnow方式,不少又翻譯爲知名對象。爲何稱爲知名對象激活模式呢?是由於服務器應用程序在激活對象實例以前會在一個 衆所周知的統一資源標識符(URI)上來發布這個類型。而後該服務器進程會爲此類型配置一個WellKnown對象,並根據指定的端口或地址來發布對 象。. Net Remoting把服務器端激活又分爲SingleTon模式和SingleCall模式兩種。
SingleTon模式:此爲有狀態模式。若是設置爲SingleTon激活方式,則Remoting將爲全部客戶端創建同一個對象實例。當對象處於 活動狀態時, SingleTon實例會處理全部後來的客戶端訪問請求,而無論它們是同一個客戶端,仍是其餘客戶端。SingleTon實例將在方法調用中一直維持其狀 態。舉例來講,若是一個遠程對象有一個累加方法(i=0;++i),被多個客戶端(例如兩個)調用。若是設置爲SingleTon方式,則第一個客戶得到 值爲1,第二個客戶得到值爲2,由於他們得到的對象實例是相同的。若是熟悉Asp.Net的狀態管理,咱們能夠認爲它是一種Application狀態。
SingleCall模式:SingleCall是一種無狀態模式。一旦設置爲SingleCall模式,則當客戶端調用遠程對象的方法時, Remoting會爲每個客戶端創建一個遠程對象實例,至於對象實例的銷燬則是由GC自動管理的。同上一個例子而言,則訪問遠程對象的兩個客戶得到的都 是1。咱們仍然能夠借鑑Asp.Net的狀態管理,認爲它是一種Session狀態。
客戶端激活。與WellKnown模式不一樣, Remoting在激活每一個對象實例的時候,會給每一個客戶端激活的類型指派一個URI。客戶端激活模式一旦得到客戶端的請求,將爲每個客戶端都創建一個 實例引用。SingleCall模式和客戶端激活模式是有區別的:首先,對象實例建立的時間不同。客戶端激活方式是客戶一旦發出調用的請求,就實例化; 而SingleCall則是要等到調用對象方法時再建立。其次,SingleCall模式激活的對象是無狀態的,對象生命期的管理是由GC管理的,而客戶 端激活的對象則有狀態,其生命週期可自定義。其三,兩種激活模式在服務器端和客戶端實現的方法不同。尤爲是在客戶端,SingleCall模式是由 GetObject()來激活,它調用對象默認的構造函數。而客戶端激活模式,則經過CreateInstance()來激活,它能夠傳遞參數,因此能夠 調用自定義的構造函數來建立實例。
遠程對象的定義
前面講到,客戶端在獲取服務器端對象時,並非得到實際的服務端對象,而是得到它的引用。所以在Remoting中,對於遠程對象有一些必須的定義規範要遵循。
因爲Remoting傳遞的對象是以引用的方式,所以所傳遞的遠程對象類必須繼承MarshalByRefObject。MSDN對 MarshalByRefObject的說明是:MarshalByRefObject 是那些經過使用代理交換消息來跨越應用程序域邊界進行通訊的對象的基類。不是從 MarshalByRefObject 繼承的對象會以隱式方式按值封送。當遠程應用程序引用一個按值封送的對象時,將跨越遠程處理邊界傳遞該對象的副本。由於您但願使用代理方法而不是副本方法 進行通訊,所以須要繼承MarshallByRefObject。
在Remoting中可以傳遞的遠程對象能夠是各類類型,包括複雜的DataSet對象,只要它可以被序列化。遠程對象也能夠包含事件,但服務器端對於事件的處理比較特殊,我將在本系列之三中介紹。
服務器端
根據第一部分所述,根據激活模式的不一樣,通道類型的不一樣服務器端的實現方式也有所不一樣。大致上說,服務器端應分爲三步:
一、註冊通道
要跨越應用程序域進行通訊,必須實現通道。如前所述,Remoting提供了IChannel接口,分別包含TcpChannel和 HttpChannel兩種類型的通道。這兩種類型除了性能和序列化數據的格式不一樣外,實現的方式徹底一致,所以下面咱們就以TcpChannel爲例。
註冊TcpChannel,首先要在項目中添加引用「System.Runtime.Remoting」,而後using名字空間: System.Runtime.Remoting.Channel.Tcp。在實例化通道對象時,將端口號做爲參數傳遞。而後再調用靜態方法 RegisterChannel()來註冊該通道對象便可。
二、註冊遠程對象
註冊了通道後,要能激活遠程對象,必須在通道中註冊該對象。根據激活模式的不一樣,註冊對象的方法也不一樣。
對於WellKnown對象,能夠經過靜態方法 RemotingConfiguration.RegisterWellKnownServiceType()來實現,註冊對象的方法基本上和 SingleTon模式相同,只須要將枚舉參數WellKnownObjectMode改成SingleCall就能夠了。
三、註銷通道
若是要關閉Remoting的服務,則須要註銷通道,也能夠關閉對通道的監聽。在Remoting中當咱們註冊通道的時候,就自動開啓了通道的監聽。 而若是關閉了對通道的監聽,則該通道就沒法接受客戶端的請求,但通道仍然存在,若是你想再一次註冊該通道,會拋出異常。
若是你剛開是學 看幾個例子就好了
remoting webservice 涉及到設計模式
如今不少公司都要求使用 remoting webservice來實現 面向服務架構(SOA)
解決方案讓您更輕鬆地應對變動.不管您的流程多麼複雜或緩慢,SOA都可以幫助簡化它們,幫助組織實現快速變革,進而實現業務影響力.
1、Remoting的優缺點?
優勢:
一、能讓咱們進行分佈式開發
二、Tcp通道的Remoting速度很是快
三、雖然是遠程的,可是很是接近於本地調用對象
四、能夠作到保持對象的狀態
五、沒有應用程序限制,能夠是控制檯,winform,iis,windows服務承載遠程對象
缺點:
一、非標準的應用所以有平臺限制
二、脫離iis的話須要有本身的安全機制
2、Remoting和Web服務的區別?
ASP.NET Web 服務基礎結構經過將 SOAP 消息映射到方法調用,爲 Web 服務提供了簡單的 API。經過提供一種很是簡單的編程模型(基於將 SOAP 消息交換映射到方法調用),它實現了此機制。ASP.NET Web 服務的客戶端不須要了解用於建立它們的平臺、對象模型或編程語言。而服務也不須要了解向它們發送消息的客戶端。惟一的要求是:雙方都要承認正在建立和使用 的 SOAP 消息的格式,該格式是由使用 WSDL 和 XML 架構 (XSD) 表示的 Web 服務合約定義來定義的。
. NET Remoting 爲分佈式對象提供了一個基礎結構。它使用既靈活又可擴展的管線向遠程進程提供 .NET 的徹底對象語義。ASP.NET Web 服務基於消息傳遞提供很是簡單的編程模型,而 .NET Remoting 提供較爲複雜的功能,包括支持經過值或引用傳遞對象、回調,以及多對象激活和生命週期管理策略等。要使用 .NET Remoting,客戶端須要瞭解全部這些詳細信息,簡而言之,須要使用 .NET 創建客戶端。.NET Remoting 管線還支持 SOAP 消息,但必須注意這並無改變其對客戶端的要求。若是 Remoting 端點提供 .NET 專用的對象語義,無論是否經過 SOAP,客戶端必須理解它們。
3、最簡單的Remoting的例子
項目DEMO截圖:
一、遠程對象:
創建類庫項目:RemoteObject
using System;
namespace RemoteObject
{
public class MyObject:MarshalByRefObject
{
public int Add(int a,int b)
{
return a+b;
}
}
}
二、服務端
創建控制檯項目:RemoteServer
using System;
using System.Runtime.Remoting;
namespace RemoteServer
{
class MyServer
{
[STAThread]
static void Main(string[] args)
{
RemotingConfiguration.Configure("RemoteServer.exe.config");
Console.ReadLine();
}
}
}
創建配置文件:app.config
<configuration>
<system.runtime.remoting>
<application name="RemoteServer">
<service>
<wellknown type="RemoteObject.MyObject,RemoteObject" objectUri="RemoteObject.MyObject"
mode="Singleton" />
</service>
<channels>
<channel ref="tcp" port="9999"/>
</channels>
</application>
</system.runtime.remoting>
</configuration>
三、客戶端:
創建控制檯項目:RemoteClient
using System;
namespace RemoteClient
{
class MyClient
{
[STAThread]
static void Main(string[] args)
{
RemoteObject.MyObject app = (RemoteObject.MyObject)Activator.GetObject(typeof(RemoteObject.MyObject),System.Configuration.ConfigurationSettings.AppSettings["ServiceURL"]);
Console.WriteLine(app.Add(1,2));
Console.ReadLine();
}
}
}
創建配置文件:app.config
<configuration>
<appSettings>
<add key="ServiceURL" value="tcp://localhost:9999/RemoteObject.MyObject"/>
</appSettings>
</configuration>
四、測試
在最後編譯的時候會發現編譯報錯:
一、找不到app.Add()
二、找不到RemoteObject
這是由於客戶端RemoteClient沒有添加RemoteObject的引用,編譯器並不知道遠程對象存在哪些成員因此報錯,添加引用之後 vs.net會在客戶端也保存一個dll,可能你們會問這樣若是對遠程對象的修改是否是會很麻煩?其實不麻煩,對項目編譯一次vs.net會從新複製 dll。
而後直接運行客戶端會出現「目標主機拒絕」的異常,也說明了通道沒有打開
運行服務端再運行客戶端出現「找不到程序集RemoteObject」!回頭想一想能夠發現咱們並在服務端對RemoteObject添加引用,編譯的時候 經過是由於這個時候並無用到遠程對象,你們可能不理解運行服務端的時候也經過?這是由於沒有這個時候尚未激活遠程對象。理所固然,對服務端要添加引用 遠程對象,畢竟咱們的對象是要靠遠程承載的。
如今再前後運行服務端程序和客戶端程序,客戶端程序顯示3,測試成功。
4、結束語
咱們經過一個簡單的例子實現了最簡單的remoting,對其實質沒有作任何介紹,我想經過例子入門纔是最簡單的。
本文轉自:http://www.cnblogs.com/Leo_wl/archive/2010/05/06/1729264.html
每一個人在他生活中都經歷過不幸和痛苦。有些人在苦難中只想到本身,他就悲觀消極發出絕望的哀號;有些人在苦難中還想到別人,想到集體,想到祖先和子孫,想到祖國和全人類,他就獲得樂觀和自信。 ——洗星海
WinForm的EXE破解(基於IL修改)
1、目的與目標
1.1 主題目的
部門新人較多,但願經過本次分享讓同窗們對如下知識點有個認識:
- 破解原理
- IL原理
- 強簽名與加密
- resx文件
因爲時間有限,本文做爲部門分享演示過程當中輔助性文檔,會對文中一些關鍵點列出參考學習的博客地址,供你們課後學習。
1.2 本次實戰最終要達到的效果
本次實戰中所要達成效果以下圖:
1. 登陸時跳過,判斷是否註冊函數,直接進入業務操做模塊;
2. 修改logo大圖,改爲其餘圖片,以下圖所示;
圖一 破解前 圖二 破解後(去掉登陸限制)
2、破解過程
一個程序破解的過程,不管是.net仍是c++等,過程大體都是這樣。
1.瞭解業務過程(繞過的點);
2. 找到對應的源代碼;
3. 刪除驗證代碼;
4. 從新爲編譯可執行文件;
*,只是c++等,須要更爲複雜的逆向工程,脫殼等步驟;
那咱們就按此步驟開始;
【【源代碼】】今後處獲取
2.1. 找到須要繞過的點;
通過對須要破解的程序分析,最先突破就在找到【登陸】按鈕的代碼位置;一般驗證的函數都應該獨立封裝爲一個函數;因此得出兩種繞過驗證的方式:
- 刪除業務邏輯中的驗證代碼;
- 修改驗證業務邏輯,無論是否註冊都返回已註冊;
2.2. 找到對應源代碼
這裏借用反編譯工具來將IL反編譯可讀性更高的c#代碼,這樣更快速的幫助咱們定位到button點擊事件或驗證函數(固然沒有這步也是能夠,只是爲了提升咱們理解代碼的速度)。
反編譯工具一般用這兩種「Reflector」 和 「ILSpy」 ,Reflector的反編譯代碼還原度更高可是非開源非免費(能夠找低版本6.8如下),ILSpy反編譯程度不如Reflector高,可是開源免費,你們能夠視本身具體的需求來選取;
1. 使用Reflector查看源代碼, 操做說明
2. 查看WindowsFormsApp1.exe ,以下圖能夠看到,按鈕【登錄】的業務邏輯爲,先調用Class1.getKey()方法來判斷是否註冊;
圖四,反編譯後查看按鈕點擊事件
2.3. 使用ildasm將exe和dll 逆向成IL文件
2.3.1 使用ildasm 將exe反編譯爲il文件並修改
咱們使用微軟官方提供了ildasm.exe來將.net程序反向成il文件
- 找到 ildasm.exe 一般在C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools;或者使用vs提供的開發人員命令操做符。
可是須要注意路徑,在demo中我已經將ildam和ildasm都打包到文件夾中了
2. 輸入 ildasm.exe WindowsFormsApp1.exe /out=il\windowsformsapp1.il
3. 修改IL代碼,跳過驗證:
1.打開windowsformsapp1.il;
2. IL重點行數分析:
128行,調用 Class1.getKey(),壓入值到堆棧;
129行,idc.i4.0在Stack 中int長度爲4,值爲0=>對應代碼爲if(Class1.getKey()==false)中的false
130行,ceq 比較
136行,定義文本"請先註冊"
137行,調用MessageBox.show
138行, pop 彈出堆棧中的值
139行,轉向 IL 0028,對應代碼if(){}else{}那個代碼塊
3. 修改il方式一 (不調用:Class1.getkey())
將128行修改成:IL_0001: ldc.i4.1 //call bool [ClassLibrary1]ClassLibrary1.Class1::getkey()
使用2.3.2從新生成exe,反編譯後能夠看到代碼變成下圖:
4. 邏輯修改二,依然調用驗證。可是取消else代碼塊
將139行註釋。註釋後能夠參照方法2.3.2從新編譯生成exe。查看新的exe的源代碼以下:
2.3.2 從新生成windowsformsapp1.exe(記得這一步哦)
ilasm.exe windowsformsapp1.il /out=windowsformsapp1.exe
2.3.3 其餘修改方案
不改windowform1.il,修改ClassLibrary.dll的驗證邏輯
相似方案一,留做你們本身研究
2.4. 修改資源文件,修改圖片;
修改exe中的圖片,使用如下步驟:
1. 將resources文件轉爲resx
ResGen.exe WindowsFormsApp1.Properties.Resources.resources WindowsFormsApp1.Properties.Resources.resx
2. 使用vs打開新生成的resx文件,替換舊圖片icon-02.png,以下圖:
3. 或用記事本打開resx文件,將base64文本替換爲新圖片的base64
4. 從新編譯爲resources
ResGen.exe WindowsFormsApp1.Properties.Resources.resx WindowsFormsApp1.Properties.Resources.resources
* .resources,編譯後的資源文件,是沒法直接打開沒法直接保存圖片文件;使用反編譯工具反編譯後默認是產生.resources;而.resx 可被VS打開的文件;
那怎麼將resources轉爲resx呢,咱們能夠利用官方提供的Resgen.exe進行文件轉換
https://docs.microsoft.com/zh-cn/dotnet/framework/tools/resgen-exe-resource-file-generator
3、防護方法
既然.net這麼輕易的被破解,那咱們要怎麼對咱們的程序進行安全防禦,以保證咱們的代碼安全呢;主要也是經過如下兩點:
- 使用強簽名,防止DLL被篡改;
- 使用代碼混淆工具,防止被反編譯源碼;
強簽名:
本文不在描述,強簽名的做用和使用方式
代碼混淆工具:
代碼混淆工具備不少,如:微軟官方的Dotfuscator,有開源的ConfuserEX;還有其餘的xeoncode、foxit;
代碼混淆能夠單獨做爲一個主題鋪開來講(java,ios 等等都面臨這個問題,因此不要過度擔憂.net)
ConfuserEx操做步驟:https://blog.csdn.net/xiaoyong_net/article/details/78988264
4、最後
最後,做爲開發人員的咱們並不該該法盜用他人勞動成果,這篇文章只是爲了讓你們更好的認識IL語言,以及IL語言模式的缺點與防護方式,讓你們可以更改好的提升防範意識,本文並未真正深刻到破解細節中去,僅起來點睛入門的做用,有興趣的同窗能夠自行深刻去研究,win32下的逆向工程、加殼、脫殼等技術;
【【源代碼】】今後處獲取
WebApi實現單個文件的上傳下載
上傳和下載是很經常使用的功能了,只有當用到的時候才發現不會寫...,通過一番百度、篩選、整理修改後,實現了功能,下面簡單的記錄下實現方法。
1、上傳功能
1.前端代碼
上傳文件 <input type="file" id="file" /> <input type="button" id="upload" value="上傳文件" /> <script> //上傳 $("#upload").click(function () { var formData = new FormData(); var file = document.getElementById("file").files[0]; formData.append("fileInfo", file); $.ajax({ url: "../api/File/UploadFile", type: "POST", data: formData, contentType: false,//必須false纔會自動加上正確的Content-Type processData: false,//必須false纔會避開jQuery對 formdata 的默認處理,XMLHttpRequest會對 formdata 進行正確的處理 success: function (data) { alert(data); }, error: function (data) { alert("上傳失敗!"); } }); }); </script>
2.後臺代碼
1 /// <summary> 2 /// 上傳文件 3 /// </summary> 4 [HttpPost] 5 public string UploadFile() 6 { 7 string result = string.Empty; 8 try 9 { 10 string uploadPath = HttpContext.Current.Server.MapPath("~/App_Data/"); 11 HttpRequest request = System.Web.HttpContext.Current.Request; 12 HttpFileCollection fileCollection = request.Files; 13 // 判斷是否有文件 14 if (fileCollection.Count > 0) 15 { 16 // 獲取文件 17 HttpPostedFile httpPostedFile = fileCollection[0]; 18 string fileExtension = Path.GetExtension(httpPostedFile.FileName);// 文件擴展名 19 string fileName = Guid.NewGuid().ToString() + fileExtension;// 名稱 20 string filePath = uploadPath + httpPostedFile.FileName;// 上傳路徑 21 // 若是目錄不存在則要先建立 22 if (!Directory.Exists(uploadPath)) 23 { 24 Directory.CreateDirectory(uploadPath); 25 } 26 // 保存新的文件 27 while (File.Exists(filePath)) 28 { 29 fileName = Guid.NewGuid().ToString() + fileExtension; 30 filePath = uploadPath + fileName; 31 } 32 httpPostedFile.SaveAs(filePath); 33 result = "上傳成功"; 34 } 35 } 36 catch (Exception) 37 { 38 result = "上傳失敗"; 39 } 40 return result; 41 }
2、下載功能
1.前端代碼
1 <form action="../api/File/DownloadFile" method="get" id="form"> 2 下載文件 <input type="text" id="name" name="fileName" value="222" /> 3 </form> 4 <input type="button" id="download" value="下載文件" /> 5 6 <script> 7 //下載 8 $("#download").click(function () { 9 var form = $("#form"); 10 form.submit(); 11 }); 12 </script>
2.後臺代碼
1 /// <summary> 2 /// 下載文件 3 /// </summary> 4 /// <param name="fileName"></param> 5 [HttpGet] 6 public void DownloadFile(string fileName) 7 { 8 string filePath = Path.Combine(HttpContext.Current.Server.MapPath("~/App_Data/"), fileName); 9 if (File.Exists(filePath)) 10 { 11 HttpResponse response = HttpContext.Current.Response; 12 response.Clear(); 13 response.ClearHeaders(); 14 response.ClearContent(); 15 response.Buffer = true; 16 response.AddHeader("content-disposition", string.Format("attachment; FileName={0}", fileName)); 17 response.Charset = "GB2312"; 18 response.ContentEncoding = Encoding.GetEncoding("GB2312"); 19 response.ContentType = MimeMapping.GetMimeMapping(fileName); 20 response.WriteFile(filePath); 21 response.Flush(); 22 response.Close(); 23 } 24 }
3、遇到的問題
1.寫了個測試的html頁,如何讓程序運行時打開這個頁面,在默認執行的HomeControler中添加劇定向代碼
HttpContext.Response.Redirect("Html/Index.html", true);
2.跨域問題
當問題1中html頁和後端程序分開部署時,就會產生跨域問題
可在web.config中進行以下配置
1 <system.webServer> 2 <httpProtocol> 3 <customHeaders> 4 <add name="Access-Control-Allow-Origin" value="*"/> 5 <add name="Access-Control-Allow-Headers" value="X-Requested-With,Content-Type,Accept,Origin"/> 6 <add name="Access-Control-Allow-Methods" value="GET,POST,PUT,DELETE,OPTIONS"/> 7 </customHeaders> 8 </httpProtocol> 9 </system.webServer>
詳情可閱讀:http://www.javashuo.com/article/p-eegskrpp-eb.html
Demo下載:https://pan.baidu.com/s/1zV1-4WvrP3ZTWwTDFAmExQ
Log4Net使用學習筆記
項目源文件下載https://files.cnblogs.com/files/ckym/Log4NetTestSourceCode.zip
Log4net是一款很是好用的日誌記錄的框架,使用它能夠實現將日誌輸出到控制檯,文件,數據庫等功能
網上有不少log4net的使用教程,一些很是簡陋,一些又很深奧,我學習使用log4net的時候查閱了不少資料,爲了能夠記住相關功能的實現方式,故記錄這篇文章,僅用於初學者使用此文檔來進行學習,若有錯誤,請大神不吝賜教,更多深刻用法和原理請你們查詢相關文檔。
- 1. 新建一個控制檯應用程序(Core)
- 2. 在Nuget中安裝log4net完成以後新建一個log4net的配置文件,配置文件示例以下
<?xml version="1.0" encoding="utf-8" ?>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout" value="%date [%thread] %-5level %logger - %message%newline" />
</appender>
<appender name="FileAppender" type="log4net.Appender.FileAppender">
<file value="log-file.log" />
<appendToFile value="true" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<appender name="RollingLogFileAppender" type="log4net.Appender.RollingFileAppender">
<file value="Log/" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<staticLogFileName value="false" />
<datePattern value="yyyyMMdd'.log'" />
<maxSizeRollBackups value="10" />
<maximumFileSize value="1MB" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
<!-- name屬性指定其名稱,type則是log4net.Appender命名空間的一個類的名稱,意思是,指定使用哪一種介質-->
<appender name="ADONetAppender" type="MicroKnights.Logging.AdoNetAppender, log4net.AdoNetAppender">
<!--日誌緩存寫入條數 設置爲0時只要有一條就馬上寫到數據庫-->
<bufferSize value="1" />
<!--日誌數據庫鏈接串-->
<connectionType value="System.Data.SqlClient.SqlConnection, System.Data" />
<connectionString value="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=SCTest;Integrated Security=True;Connect Timeout=30;Encrypt=False;TrustServerCertificate=False;ApplicationIntent=ReadWrite;MultiSubnetFailover=False;" />
<!--日誌數據庫腳本-->
<commandText value="INSERT INTO Sys_Logs (ID,CreateTime,CreateUser,LogLevel,Message,UserIP) VALUES (4,@CreateTime,@CreateUser,@LogLevel,@CustomMessage,@UserIP)" />
<!--日誌時間LogDate -->
<parameter>
<parameterName value="@CreateTime" />
<dbType value="DateTime" />
<layout type="log4net.Layout.RawTimeStampLayout" />
</parameter>
<parameter>
<parameterName value="@LogLevel" />
<dbType value="String" />
<size value="200" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%p" />
</layout>
</parameter>
<parameter>
<parameterName value="@CustomMessage" />
<dbType value="String" />
<size value="3000" />
<layout type="Log4NetTest.CustomLayout,Log4NetTest">
<conversionPattern value="%CustomMessage{CustomMessage}" />
</layout>
</parameter>
<!--自定義UserName -->
<parameter>
<parameterName value="@CreateUser" />
<dbType value="String" />
<size value="30" />
<layout type="Log4NetTest.CustomLayout,Log4NetTest" >
<conversionPattern value = "%CreateUser{CreateUser}"/>
</layout>
</parameter>
<parameter>
<parameterName value="@UserIP" />
<dbType value="String" />
<size value="20" />
<layout type="Log4NetTest.CustomLayout,Log4NetTest" >
<conversionPattern value = "%UserIP{UserIP}"/>
</layout>
</parameter>
</appender>
<!-- Setup the root category, add the appenders and set the default level -->
<root>
<level value="ALL" />
<appender-ref ref="ConsoleAppender" />
<appender-ref ref="FileAppender" />
<appender-ref ref="RollingLogFileAppender" />
<appender-ref ref="ADONetAppender" />
</root>
</log4net>
接着在App.config中添加以下行
<configuration>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configuration>
而後再Main文件中添加以下代碼
ILoggerRepository repository = LogManager.CreateRepository("MyCustomRepository");
//BasicConfigurator.Configure(repository);//簡單配置,只能寫日誌到控制檯
//讀取配置文件的方式實現
//var log4NetConfig = new XmlDocument();
//log4NetConfig.Load(File.OpenRead("log4net.config"));
//XmlConfigurator.Configure(repository, log4NetConfig["log4net"]);
//直接使用文件的方式
//注意,要求全部的配置文件必須放置在Debug文件夾下面,不然會形成程序不報錯,可是也不能正確寫入文件的問題
XmlConfigurator.Configure(repository, new FileInfo("log4net.config"));
ILog log = LogManager.GetLogger(repository.Name,"MyCustomLogger");
//log.Debug("This is a Log info From Log4net Test");
//log.Info("NETCorelog4net log");
//log.Info("test log");
//log.Error("error");
//log.Info("linezero");
//記錄日誌到數據庫
CustomLogInfo logInfo = new CustomLogInfo() { ID = new Random().Next(1, 100), Message = "This is a Log info By Log4Net", CreateUser = "SC", UserIP = Dns.GetHostAddresses(Dns.GetHostName())[0].ToString() };
log.Debug(logInfo);
Console.WriteLine("日誌記錄完成!");
Console.ReadKey();
根據註釋取消或者註釋掉相關的代碼,點擊運行,能夠看到日誌輸出到文件,或者控制檯,或者數據庫文件
異常處理:
- 找不到AdoNetAppender
在使用log4net2.0.8的時候咱們運行會發現報錯,由於2.0.8的版本沒有提供AdoNetAppender,因此須要咱們本身去實現,源代碼在項目中能夠找到,其餘數據庫,例如MySql,Oracle等數據庫同樣的實現方式
- 實現自定義的轉換器和佈局,這個能夠參考原代碼的實現和註釋,便可實現相關的功能(注意須要反射的只是).
下面是輸出的日誌模板的一下縮寫對應的關係,能夠參考,這些是log4net中自帶的轉換器實現的。
1)NewLinePatternConverter
做用:換行;通配符:%newline,%n
2)LoggerPatternConverter
做用:顯示Logger名;通配符:%logger,%c
3)TypeNamePatternConverter
做用:顯示類名;通配符:%C,%class,%type
4)DatePatternConverter
做用:顯示時間;通配符:%d,%date
5)ExceptionPatternConverter
做用:異常信息;通配符:%exception
6)LineLocationPatternConverter
做用:語句所在的行號;通配符:%L,%line
7)MessagePatternConverter
做用:信息內容;通配符:%message,%m
8)LevelPatternConverter
做用:消息等級;通配符:%level,%p
ASP.NET Core2基於RabbitMQ對Web前端實現推送功能
在咱們不少的Web應用中會遇到須要從後端將指定的數據或消息實時推送到前端,一般的作法是前端寫個腳本定時到後端獲取,或者藉助WebSocket技術實現先後端實時通信。因定時刷新的方法弊端不少(已再也不採用),因此基於WebSocket技術實現的通信方案正愈來愈受你們喜好,因而在ASP.NET中就有了鼎鼎大名的Signalr。但Signalr不是我們這裏的主角,這裏將給你們介紹另外一套基於WebSocket的先後端通信方案,能夠給你們在應用中多一個選擇。
準備
在開始動手前,我們先簡單介紹下方案的組成部分,以下:
RabbitMQ:是一個成熟的MQ隊列服務,由 Erlang 語言開發的 AMQP 的開源實現。這裏用來接收後端的指令並廣播到前端(基於topic模式)。關於更多RabbitMQ的實現能夠查看我另外一篇文章,傳送門
RabbitMQ插件stomp:是一個讓RabbitMQ支持stomp協議的插件,必需安裝後才能經過RabbitMQ實現前端通信。安裝說明在此:http://www.rabbitmq.com/stomp.html
stomp.js:是一個基於stomp協議的客戶端實現,底層基於WebSocket通信協議。這裏用於前端實現WebSocket通信。官網地址:https://github.com/jmesnil/stomp-websocket
Lezhima.Rest:是一個基於ASP.NET Core2的Web Api後端程序,用來模擬向前端發送指令。
Lezhima.Site:是一個純前端技術的前端程序,用來模擬前端實時接收後臺的指令。
實現
如上面所述,咱們已經清楚了整個實現思路,那麼下面就來看看具體的代碼實現吧。
一、首先來看看Lezhima.Rest的MQ生產者代碼,以下:
1 /// <summary> 2 /// MQ生產者,採用topic模式推送指定內容 3 /// </summary> 4 /// <param name="objText"></param> 5 public static void PushMessage(string objText) 6 { 7 //建立MQ鏈接工廠 8 var factory = new ConnectionFactory() 9 { 10 HostName = "localhost", 11 Port = 5672, 12 UserName = "fans", 13 Password = "123456" 14 }; 15 //建立MQ鏈接 16 using (var connection = factory.CreateConnection()) 17 using (var channel = connection.CreateModel()) 18 { 19 //綁定交換器 20 channel.ExchangeDeclare(exchange: "topic/test", type: "topic"); 21 var body = Encoding.UTF8.GetBytes(objText); 22 //對指定routingkey發送內容 23 channel.BasicPublish(exchange: "amq.topic", 24 routingKey: "test", 25 basicProperties: null, 26 body: body); 27 } 28 }
二、Lezhima.Site的前端代碼,以下:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <link href="main.css" rel="stylesheet" type="text/css" /> 6 <script src="Script/jquery.js"></script> 7 <script src="stomp.js"></script> 8 <style> 9 10 .box { 11 width: 440px; 12 float: left; 13 margin: 0 20px 0 20px; 14 } 15 .box div, .box input { 16 border: 1px solid; 17 -moz-border-radius: 4px; 18 border-radius: 4px; 19 width: 100%; 20 padding: 5px; 21 margin: 3px 0 10px 0; 22 } 23 24 .box div { 25 border-color: grey; 26 height: 300px; 27 overflow: auto; 28 } 29 30 div code { 31 display: block; 32 } 33 34 #first div code { 35 -moz-border-radius: 2px; 36 border-radius: 2px; 37 border: 1px solid #eee; 38 margin-bottom: 5px; 39 } 40 </style> 41 </head> 42 <body lang="en"> 43 <div id="first" class="box"> 44 <h2>接收來自後端的消息</h2> 45 <div></div> 46 </div> 47 <script> 48 var has_had_focus = false; 49 //封裝個接收呈現方法 50 var pipe = function (el_name) { 51 var div = $(el_name + ' div'); 52 var print = function (m) { 53 div.append($("<code>").text('後端的指令:'+ m)); 54 div.scrollTop(div.scrollTop() + 10000); 55 }; 56 return print; 57 }; 58 59 //RabbitMQ的服務地址 60 var mqUrl = "ws://localhost:15674/ws"; 61 //聲明個Stompjs客戶端 62 var client = Stomp.client(mqUrl); 63 64 var print_first = pipe('#first', function (data) { 65 client.send('/topic/test', { "content-type": "text/plain" }, data); 66 }); 67 68 //監聽鏈接事件 69 var on_connect = function (x) { 70 id = client.subscribe("/topic/test", function (d) { 71 print_first(d.body); 72 }); 73 }; 74 var on_error = function () { 75 console.log('error'); 76 }; 77 //鏈接MQ 78 client.connect('fans', '123456', on_connect, on_error, '/'); 79 80 </script> 81 </body> 82 </html> 83
三、分別運行「Lezhima.Rest」與「Lezhima.Site」程序後,效果是這個樣子的,以下:
總結
一、藉助RabbitMQ實現先後端通信功能時,必需先安裝RabbitMQ插件stomp,經過該插件可以使RabbitMQ支持WebSocket通信能力。而咱們的後端開發人員只需經過「生產者」方法按需向MQ發送數據便可,MQ將根據routingKey廣播給全部客戶端(消費者)。
二、前端藉助stomp.js能夠簡便的實現與RabbitMQ通信,並綁定相應的routingKey後承擔MQ消費者的能力,以達到先後端即時推送的效果。