C#網絡編程(異步傳輸字符串) - Part.3[轉自JimmyZhang博客]

?

源碼下載:http://www.tracefact.net/SourceCode/Network-Part3.rar

C#網絡編程(異步傳輸字符串) - Part.3

這篇文章我們將前進一大步,使用異步的方式來對服務端編程,以使它成為一個真正意義上的服務器:可以為多個客戶端的多次請求服務。但是開始之前,我們需要解決上一節中遺留的一個問題。

消息發送時的問題

這個問題就是:客戶端分兩次向流中寫入數據(比如字符串)時,我們主觀上將這兩次寫入視為兩次請求;然而服務端有可能將這兩次合起來視為一條請求,這在兩個請求間隔時間比較短的情況下尤其如此。同樣,也有可能客戶端發出一條請求,但是服務端將其視為兩條請求處理。下面列出了可能的情況,假設我們在客戶端連續發送兩條“Welcome to Tracefact.net!”,則數據到達服務端時可能有這樣三種情況:

NOTE:在這里我們假設采用ASCII編碼方式,因為此時上面的一個方框正好代表一個字節,而字符串到達末尾后為持續的0(因為byte是值類型,且最小為0)。

上面的第一種情況是最理想的情況,此時兩條消息被視為兩個獨立請求由服務端完整地接收。第二種情況的示意圖如下,此時一條消息被當作兩條消息接收了:

而對于第三種情況,則是兩條消息被合并成了一條接收:

如果你下載了上一篇文章所附帶的源碼,那么將Client2.cs進行一下修改,不通過用戶輸入,而是使用一個for循環連續的發送三個請求過去,這樣會使請求的間隔時間更短,下面是關鍵代碼:

string msg = "Welcome to TraceFact.Net!";

for (int i = 0; i <= 2; i++) {
??? byte[] buffer = Encoding.Unicode.GetBytes(msg);???? // 獲得緩存
??? try {
??????? streamToServer.Write(buffer, 0, buffer.Length); // 發往服務器
??????? Console.WriteLine("Sent: {0}", msg);
??? } catch (Exception ex) {
??????? Console.WriteLine(ex.Message);
??????? break;
??? }
}

運行服務端,然后再運行這個客戶端,你可能會看到這樣的結果:

可以看到,盡管上面將消息分成了三條單獨發送,但是服務端卻將后兩條合并成了一條。對于這些情況,我們可以這樣處理:就好像HTTP協議一樣,在實際的請求和應答內容之前包含了HTTP頭,其中是一些與請求相關的信息。我們也可以訂立自己的協議,來解決這個問題,比如說,對于上面的情況,我們就可以定義這樣一個協議:

[length=XXX]:其中xxx是實際發送的字符串長度(注意不是字節數組buffer的長度),那么對于上面的請求,則我們發送的數據為:“[length=25]Welcome to TraceFact.Net!”。而服務端接收字符串之后,首先讀取這個“元數據”的內容,然后再根據“元數據”內容來讀取實際的數據,它可能有下面這樣兩種情況:

NOTE:我覺得這里借用“元數據”這個術語還算比較恰當,因為“元數據”就是用來描述數據的數據。

  • “[“”]”中括號是完整的,可以讀取到length的字節數。然后根據這個數值與后面的字符串長度相比,如果相等,則說明發來了一條完整信息;如果多了,那么說明接收的字節數多了,取出合適的長度,并將剩余的進行緩存;如果少了,說明接收的不夠,那么將收到的進行一個緩存,等待下次請求,然后將兩條合并。
  • “[”“]”中括號本身就不完整,此時讀不到length的值,因為中括號里的內容被截斷了,那么將讀到的數據進行緩存,等待讀取下次發送來的數據,然后將兩次合并之后再按上面的方式進行處理。

接下來我們來看下如何來進行實際的操作,實際上,這個問題已經不屬于C#網絡編程的內容了,而完全是對字符串的處理。所以我們不再編寫服務端/客戶端代碼,直接編寫處理這幾種情況的方法:

public class RequestHandler {
??? private string temp = string.Empty;

??? public string[] GetActualString(string input) {
??????? return GetActualString(input, null);
??? }

??? private string[] GetActualString(string input, List<string> outputList) {
??????? if (outputList == null)
??????????? outputList = new List<string>();

??????? if (!String.IsNullOrEmpty(temp))
??????????? input = temp + input;

??????? string output = "";
??????? string pattern = @"(?<=^\[length=)(\d+)(?=\])";
??????? int length;
???????????????????
??????? if (Regex.IsMatch(input, pattern)) {

??????????? Match m = Regex.Match(input, pattern);

??????????? // 獲取消息字符串實際應有的長度
??????????? length = Convert.ToInt32(m.Groups[0].Value);

??????????? // 獲取需要進行截取的位置
??????????? int startIndex = input.IndexOf(']') + 1;

??????????? // 獲取從此位置開始后所有字符的長度
??????????? output = input.Substring(startIndex);

??????????? if (output.Length == length) {
??????????????? // 如果output的長度與消息字符串的應有長度相等
??????????????? // 說明剛好是完整的一條信息
??????????????? outputList.Add(output);
??????????????? temp = "";
??????????? } else if (output.Length < length) {
??????????????? // 如果之后的長度小于應有的長度,
??????????????? // 說明沒有發完整,則應將整條信息,包括元數據,全部緩存
??????????????? // 與下一條數據合并起來再進行處理
??????????????? temp = input;
??????????????? // 此時程序應該退出,因為需要等待下一條數據到來才能繼續處理

??????????? } else if (output.Length > length) {
??????????????? // 如果之后的長度大于應有的長度,
??????????????? // 說明消息發完整了,但是有多余的數據
??????????????? // 多余的數據可能是截斷消息,也可能是多條完整消息

??????????????? // 截取字符串
??????????????? output = output.Substring(0, length);
??????????????? outputList.Add(output);
??????????????? temp = "";

??????????????? // 縮短input的長度
??????????????? input = input.Substring(startIndex + length);

??????????????? // 遞歸調用
??????????????? GetActualString(input, outputList);
??????????? }
??????? } else {??? // 說明“[”,“]”就不完整
??????????? temp = input;
??????? }

??????? return outputList.ToArray();
??? }
}

這個方法接收一個滿足協議格式要求的輸入字符串,然后返回一個數組,這是因為如果出現多次請求合并成一個發送過來的情況,那么就將它們全部返回。隨后簡單起見,我在這個類中添加了一個靜態的Test()方法和PrintOutput()幫助方法,進行了一個簡單的測試,注意我直接輸入了length=13,這個是我提前計算好的。

public static void Test() {
??? RequestHandler handler = new RequestHandler();
??? string input;

??? // 第一種情況測試 - 一條消息完整發送
??? input = "[length=13]明天中秋,祝大家節日快樂!";
??? handler.PrintOutput(input);

??? // 第二種情況測試 - 兩條完整消息一次發送
??? input = "明天中秋,祝大家節日快樂!";
??? input = String.Format
??????? ("[length=13]{0}[length=13]{0}", input);
??? handler.PrintOutput(input);

??? // 第三種情況測試A - 兩條消息不完整發送
??? input = "[length=13]明天中秋,祝大家節日快樂![length=13]明天中秋";
??? handler.PrintOutput(input);

??? input = ",祝大家節日快樂!";
??? handler.PrintOutput(input);

??? // 第三種情況測試B - 兩條消息不完整發送
??? input = "[length=13]明天中秋,祝大家";
??? handler.PrintOutput(input);

??? input = "節日快樂![length=13]明天中秋,祝大家節日快樂!";
??? handler.PrintOutput(input);

???
??? // 第四種情況測試 - 元數據不完整
??? input = "[leng";
??? handler.PrintOutput(input);???? // 不會有輸出

??? input = "th=13]明天中秋,祝大家節日快樂!";
??? handler.PrintOutput(input);

}

// 用于測試輸出
private void PrintOutput(string input) {
??? Console.WriteLine(input);
??? string[] outputArray = GetActualString(input);
??? foreach (string output in outputArray) {
??????? Console.WriteLine(output);
??? }
??? Console.WriteLine();
}

運行上面的程序,可以得到如下的輸出:

OK,從上面的輸出可以看到,這個方法能夠滿足我們的要求。對于這篇文章最開始提出的問題,可以很輕松地通過加入這個方法來解決,這里就不再演示了,但在本文所附帶的源代碼含有修改過的程序。在這里花費了很長的時間,接下來讓我們回到正題,看下如何使用異步方式完成上一篇中的程序吧。

異步傳輸字符串

在上一篇中,我們由簡到繁,提到了服務端的四種方式:服務一個客戶端的一個請求、服務一個客戶端的多個請求、服務多個客戶端的一個請求、服務多個客戶端的多個請求。我們說到可以將里層的while循環交給一個新建的線程去讓它來完成。除了這種方式以外,我們還可以使用一種更好的方式――使用線程池中的線程來完成。我們可以使用BeginRead()、BeginWrite()等異步方法,同時讓這BeginRead()方法和它的回調方法形成一個類似于while的無限循環:首先在第一層循環中,接收到一個客戶端后,調用BeginRead(),然后為該方法提供一個讀取完成后的回調方法,然后在回調方法中對收到的字符進行處理,隨后在回調方法中接著調用BeginRead()方法,并傳入回調方法本身。

由于程序實現功能和上一篇完全相同,我就不再細述了。而關于異步調用方法更多詳細內容,可以參見 C#中的委托和事件(續)

1.服務端的實現

當程序越來越復雜的時候,就需要越來越高的抽象,所以從現在起我們不再把所有的代碼全部都扔進Main()里,這次我創建了一個RemoteClient類,它對于服務端獲取到的TcpClient進行了一個包裝:

public class RemoteClient {
??? private TcpClient client;
??? private NetworkStream streamToClient;
??? private const int BufferSize = 8192;
??? private byte[] buffer;
??? private RequestHandler handler;
???
??? public RemoteClient(TcpClient client) {
??????? this.client = client;

??????? // 打印連接到的客戶端信息
??????? Console.WriteLine("\nClient Connected!{0} <-- {1}",
??????????? client.Client.LocalEndPoint, client.Client.RemoteEndPoint);

??????? // 獲得流
??????? streamToClient = client.GetStream();
??????? buffer = new byte[BufferSize];

??????? // 設置RequestHandler
??????? handler = new RequestHandler();

??????? // 在構造函數中就開始準備讀取
??????? AsyncCallback callBack = new AsyncCallback(ReadComplete);
??????? streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);
??? }

??? // 再讀取完成時進行回調
??? private void ReadComplete(IAsyncResult ar) {
??????? int bytesRead = 0;
??????? try {
??????????? lock (streamToClient) {
??????????????? bytesRead = streamToClient.EndRead(ar);
??????????????? Console.WriteLine("Reading data, {0} bytes ...", bytesRead);
??????????? }
??????????? if (bytesRead == 0) throw new Exception("讀取到0字節");

??????????? string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
??????????? Array.Clear(buffer,0,buffer.Length);??????? // 清空緩存,避免臟讀
???????
??????????? string[] msgArray = handler.GetActualString(msg);?? // 獲取實際的字符串

??????????? // 遍歷獲得到的字符串
??????????? foreach (string m in msgArray) {
??????????????? Console.WriteLine("Received: {0}", m);
??????????????? string back = m.ToUpper();

??????????????? // 將得到的字符串改為大寫并重新發送
??????????????? byte[] temp = Encoding.Unicode.GetBytes(back);
??????????????? streamToClient.Write(temp, 0, temp.Length);
??????????????? streamToClient.Flush();
??????????????? Console.WriteLine("Sent: {0}", back);
??????????? }??????????????

??????????? // 再次調用BeginRead(),完成時調用自身,形成無限循環
??????????? lock (streamToClient) {
??????????????? AsyncCallback callBack = new AsyncCallback(ReadComplete);
??????????????? streamToClient.BeginRead(buffer, 0, BufferSize, callBack, null);
??????????? }
??????? } catch(Exception ex) {
??????????? if(streamToClient!=null)
??????????????? streamToClient.Dispose();
??????????? client.Close();
??????????? Console.WriteLine(ex.Message);????? // 捕獲異常時退出程序?????????????
??????? }
??? }
}

隨后,我們在主程序中僅僅創建TcpListener類型實例,由于RemoteClient類在構造函數中已經完成了初始化的工作,所以我們在下面的while循環中我們甚至不需要調用任何方法:

class Server {
??? static void Main(string[] args) {
??????? Console.WriteLine("Server is running ... ");
??????? IPAddress ip = new IPAddress(new byte[] { 127, 0, 0, 1 });
??????? TcpListener listener = new TcpListener(ip, 8500);

??????? listener.Start();?????????? // 開始偵聽
??????? Console.WriteLine("Start Listening ...");

??????? while (true) {
??????????? // 獲取一個連接,同步方法,在此處中斷
??????????? TcpClient client = listener.AcceptTcpClient();?????????????
??????????? RemoteClient wapper = new RemoteClient(client);
??????? }
??? }
}

好了,服務端的實現現在就完成了,接下來我們再看一下客戶端的實現:

2.客戶端的實現

與服務端類似,我們首先對TcpClient進行一個簡單的包裝,使它的使用更加方便一些,因為它是服務端的客戶,所以我們將類的名稱命名為ServerClient:

public class ServerClient {
??? private const int BufferSize = 8192;
??? private byte[] buffer;
??? private TcpClient client;
??? private NetworkStream streamToServer;
??? private string msg = "Welcome to TraceFact.Net!";

??? public ServerClient() {
??????? try {
??????????? client = new TcpClient();
??????????? client.Connect("localhost", 8500);????? // 與服務器連接
??????? } catch (Exception ex) {
??????????? Console.WriteLine(ex.Message);
??????????? return;
??????? }
??????? buffer = new byte[BufferSize];

??????? // 打印連接到的服務端信息
??????? Console.WriteLine("Server Connected!{0} --> {1}",
??????????? client.Client.LocalEndPoint, client.Client.RemoteEndPoint);

??????? streamToServer = client.GetStream();
??? }

??? // 連續發送三條消息到服務端
??? public void SendMessage(string msg) {

??????? msg = String.Format("[length={0}]{1}", msg.Length, msg);

??????? for (int i = 0; i <= 2; i++) {
??????????? byte[] temp = Encoding.Unicode.GetBytes(msg);?? // 獲得緩存
??????????? try {
??? ??????????? streamToServer.Write(temp, 0, temp.Length); // 發往服務器
??????????????? Console.WriteLine("Sent: {0}", msg);
??????????? } catch (Exception ex) {
??????????????? Console.WriteLine(ex.Message);
??????????????? break;
??????????? }
??????? }

??????? lock (streamToServer) {
??????????? AsyncCallback callBack = new AsyncCallback(ReadComplete);
??????????? streamToServer.BeginRead(buffer, 0, BufferSize, callBack, null);
??????? }
??? }

??? public void SendMessage() {
??????? SendMessage(this.msg);
??? }

??? // 讀取完成時的回調方法
??? private void ReadComplete(IAsyncResult ar) {
??????? int bytesRead;

??????? try {
??????????? lock (streamToServer) {
??????????????? bytesRead = streamToServer.EndRead(ar);
??????????? }
??????????? if (bytesRead == 0) throw new Exception("讀取到0字節");

??????????? string msg = Encoding.Unicode.GetString(buffer, 0, bytesRead);
??????????? Console.WriteLine("Received: {0}", msg);
??????????? Array.Clear(buffer, 0, buffer.Length);????? // 清空緩存,避免臟讀

??????????? lock (streamToServer) {
??????????????? AsyncCallback callBack = new AsyncCallback(ReadComplete);
??????????????? streamToServer.BeginRead(buffer, 0, BufferSize, callBack, null);
??????????? }
??????? } catch (Exception ex) {
??????????? if(streamToServer!=null)
??????????????? streamToServer.Dispose();
??????????? client.Close();

??????????? Console.WriteLine(ex.Message);
??????? }
??? }
}

在上面的SendMessage()方法中,我們讓它連續發送了三條同樣的消息,這么僅僅是為了測試,因為異步操作同樣會出現上面說過的:服務器將客戶端的請求拆開了的情況。最后我們在Main()方法中創建這個類型的實例,然后調用SendMessage()方法進行測試:

class Client {
??? static void Main(string[] args) {
??? ??? ConsoleKey key;

??????? ServerClient client = new ServerClient();
??????? client.SendMessage();
???????
??????? Console.WriteLine("\n\n輸入\"Q\"鍵退出。");
??????? do {
??????????? key = Console.ReadKey(true).Key;
??????? } while (key != ConsoleKey.Q);
??? }
}

是不是感覺很清爽?因為良好的代碼重構,使得程序在復雜程度提高的情況下依然可以在一定程度上保持良好的閱讀性。

3.程序測試

最后一步,我們先運行服務端,接著連續運行兩個客戶端,看看它們的輸出分別是什么:

大家可以看到,在服務端,我們可以連接多個客戶端,同時為它們服務;除此以外,由接收的字節數發現,兩個客戶端均有兩個請求被服務端合并成了一條請求,因為我們在其中加入了特殊的協議,所以在服務端可以對這種情況進行良好的處理。

在客戶端,我們沒有采取類似的處理,所以當客戶端收到應答時,仍然會發生請求合并的情況。對于這種情況,我想大家已經知道該如何處理了,就不再多費口舌了。

使用這種定義協議的方式有它的優點,但缺點也很明顯,如果客戶知道了這個協議,有意地輸入[length=xxx],但是后面的長度卻不匹配,此時程序就會出錯。可選的解決辦法是對“[”和“]”進行編碼,當客戶端有意輸入這兩個字符時,我們將它替換成“\[”和“\]”或者別的字符,在讀取后再將它還原。

關于這個范例就到此結束了,剩下的兩個范例都將采用異步傳輸的方式,并且會加入更多的協議內容。下一篇我們將介紹如何向服務端發送或接收文件。

轉載于:https://www.cnblogs.com/kakaliush/archive/2012/03/07/2384489.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/274963.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/274963.shtml
英文地址,請注明出處:http://en.pswp.cn/news/274963.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

chrome黑暗模式_黑暗模式:如何克服黑暗面

chrome黑暗模式This article has been written by Redmadrobot Design Lab. Translated and reposted with permission by Alconost Inc., professional translation and localization company.本文由 Redmadrobot設計實驗室 撰寫 。 經過 專業翻譯和本地化公司 Alconost Inc.的…

Deco 智能代碼體驗版正式上線啦,快來體驗設計稿一鍵生成代碼~

Deco 是什么&#xff1f;—Deco 智能代碼項目是我們團隊在「前端智能化」方向上的探索&#xff0c;其聚焦設計稿一鍵生成多端代碼這一切入點&#xff0c;實現將 Sketch/Photoshop 等設計稿進行解析并直接生成多端代碼&#xff08;Taro/React/Vue&#xff09;的能力。Deco 可以使…

jQuery 五角星評分

五角星打分 我用的是搜狗輸入法上帶的特殊符號打出來的 空五角星&#xff1a;☆ 實五角星&#xff1a;★ 1.html 1 <ul class"comment"> 2 <li>☆</li> 3 <li>☆</li> 4 <li>☆</li> 5 …

平面設計和網頁設計的規則_從平面設計到用戶界面:這是您應該知道的最重要的規則

平面設計和網頁設計的規則Maybe you’re here because you think UI Design is the future of Graphic Design. Maybe what motivates you is the money. Or maybe you just woke up one day and someone at work told you “So, you are a designer, right? Well, we need an…

即將到來的 ECMAScript 2022 新特性

大家好&#xff0c;我是若川。持續組織了5個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時極力推薦訂閱我寫的《學習源碼整體架構系列》 包含20余篇源碼文章。ECMAScript 規范每…

代碼備忘錄

1.用指針&#xff0c;函數調用實現交換兩個變量值 #include<stdio.h> int exchange(int *a,int *b){ int temp; temp*a; *a*b; *btemp;return 0;} int main(){ int i9,j2; int *p1,*p2; p1&i; p2&j; exchange(p1,p2); printf("p1%d,p2%d\n",*p1,*p2);…

mysql實戰38 | 都說InnoDB好,那還要不要使用Memory引擎?

我在上一篇文章末尾留給你的問題是&#xff1a;兩個 group by 語句都用了 order by null&#xff0c;為什么使用內存臨時表得到的語句結果里&#xff0c;0 這個值在最后一行&#xff1b;而使用磁盤臨時表得到的結果里&#xff0c;0 這個值在第一行&#xff1f;今天我們就來看看…

設計類的五個原則_內容設計的5個原則

設計類的五個原則重點 (Top highlight)There are many heuristics and principles for creating good content. Some are created from a UX perspective, others from a content marketing point of view. They range from very long to very concise ones. I reviewed a larg…

Umi 4 RC 發布

大家好&#xff0c;我是若川。感謝大家一年以來的支持和陪伴。這一年疫情反復&#xff0c;年底應該有由于疫情不能回家的小伙伴。在這里先祝福大家&#xff0c;新年快樂。本打算今天不發文&#xff0c;但看到這篇覺得不錯&#xff0c;就發一下。大家好&#xff0c;Umi 4 經過幾…

讓你沉迷的五種設計

讓你沉迷的五種設計 好游戲總是能令人沉迷其中無法自拔&#xff0c;外媒cracked經過分析&#xff0c;發現有五種設計方法必不可少&#xff1b; 1.斯金納箱原理&#xff1a;這是行為心理學派在實驗室內研究動物學習能力的箱形實驗裝置&#xff0c;游戲開發也得益于此&#xff0c…

Java學習路線詳解

有很多的[Java請添加鏈接描述](http://www.hfxms.com.cn/java/)程序員&#xff0c;在初期學習時&#xff0c;通常會對如何學習而感到迷茫。[合肥學碼思請添加鏈接描述](http://www.hfxms.com.cn/)小編就為大家分析如何學好Java編程&#xff0c;相信能幫助那些正在處于迷茫狀態的…

figma下載_在Figma中將約束與布局網格一起使用

figma下載While doing research for the book “Designing in Figma”, I discovered a powerful way to lay out objects using a combination of Layout Grid and Constraints. The interface of Figma does not indicate a connection between the two, so it can be discov…

換一種方式表達

http://player.youku.com/player.php/sid/XMjY2MTE5NDU2/v.swf 轉載于:https://www.cnblogs.com/JCSU/archive/2012/03/17/2403324.html

新的一年,碎片化學習前端,我推薦這幾個公眾號~

大家好&#xff0c;我是若川。假期余額不足&#xff0c;無法充值。快樂的時光總是短暫的。馬上又開始一年的學習和“奮斗”。前端技術日新月異&#xff0c;發展迅速&#xff0c;作為一個與時俱進的前端工程師&#xff0c;需要不斷的學習。這里強烈推薦幾個前端開發工程師必備的…

Java單元測試之JUnit4詳解

2019獨角獸企業重金招聘Python工程師標準>>> Java單元測試之JUnit4詳解 與JUnit3不同&#xff0c;JUnit4通過注解的方式來識別測試方法。目前支持的主要注解有&#xff1a; BeforeClass 全局只會執行一次&#xff0c;而且是第一個運行Before 在測試方法運行之前運行…

我在黑暗中看到你眼中的月光_你好黑暗,我的老朋友

我在黑暗中看到你眼中的月光(Originally published on https://web.dev/prefers-color-scheme/.)(最初發布于https://web.dev/prefers-color-scheme/ 。) 介紹 (Introduction) &#x1f4da; I have done a lot of background research on the history and theory of dark mod…

ant 實現批量打包android應用

很多的應用中需要加上應用推廣的統計&#xff0c;如果一個一個的去生成不同渠道包的應用&#xff0c;效率低不說&#xff0c;還有可能不小心弄錯了分發渠道&#xff0c;使用ant可以批量生成應用。一、添加渠道包信息為了統計渠道信息&#xff0c;就不得不在程序的某個地方加入渠…

Element Plus 正式版發布啦!

大家好&#xff0c;我是若川。祝大家新年快樂&#xff0c;開工大吉。公眾號回復「紅包」可以領取源碼共讀紅包封面。持續組織了6個月源碼共讀活動&#xff0c;感興趣的可以點此加我微信 ruochuan12 參與&#xff0c;每周大家一起學習200行左右的源碼&#xff0c;共同進步。同時…

大型網站技術架構(一)大型網站架構演化

2019獨角獸企業重金招聘Python工程師標準>>> 看完了有一本書&#xff0c;就應該有所收獲&#xff0c;有所總結&#xff0c;最近把《大型網站技術架構》一書給看完了&#xff0c;給人的印象實在深刻&#xff0c;再加上之前也搞過書本上講的反向代理和負載均衡以及ses…

永不示弱_永不過時的網頁設計:今天和2000年的在線投資組合

永不示弱重點 (Top highlight)Philippe Starck, a renowned industrial designer, once said:著名的工業設計師Philippe Starck曾經說過&#xff1a; “A designer has a duty to create timeless design. To be timeless you have to think really far into the future, not …