C#下利用封包俯艰、拆包原理解決Socket粘包、半包問題(新手篇)

介于網(wǎng)絡(luò)上充斥著大量的含糊其辭的Socket初級教程锌订,擾亂著新手的學(xué)習(xí)方向竹握,我來扼要的教一下新手應(yīng)該怎么合理的處理Socket這個(gè)玩意兒。

一般來說瀑志,教你C#下Socket編程的老師涩搓,很少會教你如何解決Socket粘包、半包問題劈猪。

更甚至昧甘,某些師德有問題的老師,根本就沒跟你說過Socket的粘包战得、半包問題是什么玩意兒充边。

直到有一天,你的Socket程序在傳輸信息時(shí)出現(xiàn)了你預(yù)期之外的結(jié)果(多于的信息常侦、不完整的信息浇冰、亂碼、Bug等等)聋亡。

任你喊了一萬遍“我擦”肘习,依舊是不知道問題出在哪兒!

好了坡倔,不說廢話了漂佩,進(jìn)入正題,包教包會罪塔,學(xué)不會投蝉,送路費(fèi)。


如果你讀到這篇文章了征堪,想必你已經(jīng)遇到了以上問題瘩缆,情況再理想一點(diǎn)兒,其實(shí)你在原理上已經(jīng)知道怎么解決這個(gè)問題了佃蚜,只是不知道怎么動手庸娱。

那么着绊,首先,你需要新建一個(gè)【消息協(xié)議類】涌韩,這個(gè)類我們暫定由5大屬性組成畔柔,分別是:

【(①=1個(gè)byte)(②=1個(gè)byte])(③=1個(gè)int)(④=1個(gè)byte[])(⑤=1個(gè)byte[])】

解釋一下這個(gè)【消息協(xié)議類】:

(①=1個(gè)byte):這個(gè)屬性占1個(gè)字節(jié),可以放一個(gè)0到254的整數(shù)臣樱,我稱作1號標(biāo)志靶擦;

(②=1個(gè)byte):這個(gè)屬性占1個(gè)字節(jié),可以放一個(gè)0到254的整數(shù)雇毫,我稱作2號標(biāo)志玄捕。那么1號標(biāo)志和2號標(biāo)志就有多達(dá)255×255個(gè)組合,我們用它來自定義這個(gè)消息的標(biāo)志棚放,比如枚粘,0-0表示登錄請求消息,1-1表示物理攻擊飘蚯,1-2表示魔法攻擊馍迄,3-3表示坐標(biāo)移動;

(③=1個(gè)int):這個(gè)屬性占4個(gè)字節(jié)局骤,可以放一個(gè)0到2147483647的整數(shù)攀圈,它表示(④=1個(gè)byte[])的長度;

(④=1個(gè)byte[]):這個(gè)屬性存著你要發(fā)送的全部消息體字節(jié)峦甩,所以赘来,你的消息體需要被轉(zhuǎn)化為字節(jié)數(shù)組才可存放進(jìn)去;

(⑤=1個(gè)byte[]):這個(gè)屬性存著【多余的消息體字節(jié)】凯傲。那么問題來了犬辰,什么是【多余的消息體字節(jié)】?休息一下繼續(xù)往下看冰单。

再解釋一下這個(gè)【消息協(xié)議類】具體怎么用幌缝。

1,【消息發(fā)送方】先定義【消息協(xié)議類】①②屬性诫欠,也就是隨便寫2個(gè)數(shù)狮腿;

2,【消息發(fā)送方】再將消息“裝入”【消息協(xié)議類】的④屬性呕诉,那么③屬性就有了;

3吃度,【消息發(fā)送方】將封裝好的【消息協(xié)議類】轉(zhuǎn)為byte[]甩挫,發(fā)送給【消息接收方】,我們把這道工序稱作【封包】椿每;

4伊者,【消息接收方】接收到【消息發(fā)送方】發(fā)來的byte[]時(shí)英遭,先判斷這個(gè)byte[]長度是否大于6双肤,即是否大于①屬性+②屬性+③屬性的長度和妄田,如果byte[]長度小于6婆硬,【消息接收方】就循環(huán)繼續(xù)接收屋讶;

5耙箍,【消息接收方】接收到【消息發(fā)送方】發(fā)來的byte[]長度大于等于6了V嗝宙地!則將byte[]還原為【消息協(xié)議類】送漠,為了區(qū)別搂蜓,我們暫時(shí)把它為【新消息協(xié)議類】狼荞;

6,循環(huán)判斷【消息發(fā)送方】發(fā)來的byte[]長度減去6之后的值是否大于等于【新消息協(xié)議類】的③的值帮碰。這個(gè)可以理解為byte[]是否為一個(gè)完整的【消息協(xié)議類】相味,如果是就把【新消息協(xié)議類】的④屬性拆出來,就得到了一個(gè)剛剛好完整的消息殉挽,不多也不少丰涉。那么,我們就把這道工序稱作【拆包】斯碌;

7一死,相信你已經(jīng)反應(yīng)過來⑤這個(gè)【多余的消息體字節(jié)】是干嘛用的了。上一步當(dāng)中输拇,如果byte[]信息剛好是一個(gè)完整長度自然用不到⑤了摘符,但是在網(wǎng)絡(luò)傳輸中byte[]自然不會永遠(yuǎn)那么剛好了,所以當(dāng)byte[]長度大于一個(gè)完整消息時(shí)策吠,就把多于的byte放入⑤當(dāng)中逛裤,和下次新接收的byte[]組合在一起,再次進(jìn)行這樣的循環(huán)猴抹,保證數(shù)據(jù)的完整性和獨(dú)立性带族。

8,好了蟀给,以上過程就是利用封包蝙砌、拆包原理解決Socket粘包、半包問題跋理,接下來你可以在發(fā)送方以非常頻繁的發(fā)送頻率來發(fā)送择克,接收方依然會規(guī)規(guī)矩矩完完整整的以正確的姿勢來接收消息了。最下面是要點(diǎn)代碼前普,相信聰明的你一定學(xué)會了肚邢,如果還沒學(xué)會,可以加我QQ:119945778,包教包會骡湖,不然我還得送路費(fèi)不是...

下面是源碼時(shí)間:

? 1///<summary>? 2/// 【消息協(xié)議】=【協(xié)議一級標(biāo)志】+【協(xié)議二級標(biāo)志】+【實(shí)際消息長度】+【實(shí)際消息內(nèi)容】+【多于消息內(nèi)容】? 3///</summary>? 4publicclass MessageXieYi? 5? ? {? 6#region自定義? 7#region協(xié)議一級標(biāo)志贱纠,值 = (0 至 254 )? 8privatebyte xieYiFirstFlag;? 9///<summary> 10/// 協(xié)議類別,值 = ( 0 直 254 ) 11///</summary> 12publicbyte XieYiFirstFlag 13? ? ? ? { 14get{return xieYiFirstFlag; } 15set{ xieYiFirstFlag = value; } 16? ? ? ? } 17#endregion 18 19#region協(xié)議二級標(biāo)志响蕴,值 = (0 至 254 ) 20privatebyte xieYiSecondFlag; 21///<summary> 22/// 協(xié)議二級標(biāo)志谆焊,值 = (0 至 254 ) 23///</summary> 24publicbyte XieYiSecondFlag 25? ? ? ? { 26get{return xieYiSecondFlag; } 27set{ xieYiSecondFlag = value; } 28? ? ? ? } 29#endregion 30 31#region實(shí)際消息長度 32privateint messageContentLength; 33///<summary> 34/// 實(shí)際消息長度 35///</summary> 36publicint MessageContentLength 37? ? ? ? { 38get{return messageContentLength; } 39set{ messageContentLength = value; } 40? ? ? ? } 41#endregion 42 43#region實(shí)際消息內(nèi)容 44privatebyte[] messageContent =newbyte[] { }; 45///<summary> 46/// 實(shí)際消息內(nèi)容 47///</summary> 48publicbyte[] MessageContent 49? ? ? ? { 50get{return messageContent; } 51set{ messageContent = value; } 52? ? ? ? } 53#endregion 54 55#region多余的Bytes 56privatebyte[] duoYvBytes; 57///<summary> 58/// 多余的Bytes 59///</summary> 60publicbyte[] DuoYvBytes 61? ? ? ? { 62get{return duoYvBytes; } 63set{ duoYvBytes = value; } 64? ? ? ? } 65 66#endregion 67#endregion 68 69#region構(gòu)造函數(shù)兩個(gè) 70public MessageXieYi() 71? ? ? ? { 72// 73? ? ? ? } 74 75publicMessageXieYi(byte_xieYiFirstFlage,byte_xieYiSecondFlage,byte[] _messageContent) 76? ? ? ? { 77xieYiFirstFlag = _xieYiFirstFlage; 78xieYiFirstFlag = _xieYiSecondFlage; 79messageContentLength = _messageContent.Length; 80messageContent = _messageContent; 81? ? ? ? } 82#endregion 83 84#regionMessageXieYi 轉(zhuǎn)換為 byte[] 85///<summary> 86/// MessageXieYi 轉(zhuǎn)換為 byte[] 87///</summary> 88///<returns></returns> 89publicbyte[] ToBytes() 90? ? ? ? { 91byte[] _bytes;//自定義字節(jié)數(shù)組,用以裝載消息協(xié)議 92 93using(MemoryStream memoryStream =newMemoryStream())//創(chuàng)建內(nèi)存流 94? ? ? ? ? ? { 95BinaryWriter binaryWriter =newBinaryWriter(memoryStream);//以二進(jìn)制寫入器往這個(gè)流里寫內(nèi)容 96 97binaryWriter.Write(xieYiFirstFlag);//寫入?yún)f(xié)議一級標(biāo)志浦夷,占1個(gè)字節(jié) 98binaryWriter.Write(xieYiSecondFlag);//寫入?yún)f(xié)議二級標(biāo)志辖试,占1個(gè)字節(jié) 99binaryWriter.Write(messageContentLength);//寫入實(shí)際消息長度,占4個(gè)字節(jié)100101if(messageContentLength >0)102? ? ? ? ? ? ? ? {103binaryWriter.Write(messageContent);//寫入實(shí)際消息內(nèi)容104? ? ? ? ? ? ? ? }105106_bytes = memoryStream.ToArray();//將流內(nèi)容寫入自定義字節(jié)數(shù)組107108binaryWriter.Close();//關(guān)閉寫入器釋放資源109? ? ? ? ? ? }110111return_bytes;//返回填充好消息協(xié)議對象的自定義字節(jié)數(shù)組112? ? ? ? }113#endregion114115#regionbyte[] 轉(zhuǎn)換為 MessageXieYi116///<summary>117/// byte[] 轉(zhuǎn)換為 MessageXieYi118///</summary>119///<param name="buffer">字節(jié)數(shù)組緩沖器军拟。</param>120///<returns></returns>121publicstaticMessageXieYi FromBytes(byte[] buffer)122? ? ? ? {123intbufferLength = buffer.Length;124125MessageXieYi messageXieYi =new MessageXieYi();126127using(MemoryStream memoryStream =newMemoryStream(buffer))//將字節(jié)數(shù)組填充至內(nèi)存流128? ? ? ? ? ? {129BinaryReader binaryReader =newBinaryReader(memoryStream);//以二進(jìn)制讀取器讀取該流內(nèi)容130131messageXieYi.xieYiFirstFlag = binaryReader.ReadByte();//讀取協(xié)議一級標(biāo)志剃执,讀1個(gè)字節(jié)132messageXieYi.xieYiSecondFlag = binaryReader.ReadByte();//讀取協(xié)議二級標(biāo)志,讀1個(gè)字節(jié)133messageXieYi.messageContentLength = binaryReader.ReadInt32();//讀取實(shí)際消息長度懈息,讀4個(gè)字節(jié)? ? ? ? ? ? ? ? 134135//如果【進(jìn)來的Bytes長度】大于【一個(gè)完整的MessageXieYi長度】136if((bufferLength - 6) >messageXieYi.messageContentLength)137? ? ? ? ? ? ? ? {138messageXieYi.messageContent = binaryReader.ReadBytes(messageXieYi.messageContentLength);//讀取實(shí)際消息內(nèi)容肾档,從第7個(gè)字節(jié)開始讀139messageXieYi.duoYvBytes = binaryReader.ReadBytes(bufferLength -6- messageXieYi.messageContentLength);140? ? ? ? ? ? ? ? }141142//如果【進(jìn)來的Bytes長度】等于【一個(gè)完整的MessageXieYi長度】143if((bufferLength - 6) ==messageXieYi.messageContentLength)144? ? ? ? ? ? ? ? {145messageXieYi.messageContent = binaryReader.ReadBytes(messageXieYi.messageContentLength);//讀取實(shí)際消息內(nèi)容怒见,從第7個(gè)字節(jié)開始讀146? ? ? ? ? ? ? ? }147148binaryReader.Close();//關(guān)閉二進(jìn)制讀取器,是否資源149? ? ? ? ? ? }150151returnmessageXieYi;//返回消息協(xié)議對象152? ? ? ? }153#endregion154}

1///<summary> 2/// 按照先后順序合并字節(jié)數(shù)組類 3///</summary> 4publicclass CombineBytes 5? ? { 6///<summary> 7/// 按照先后順序合并字節(jié)數(shù)組纪隙,并返回合并后的字節(jié)數(shù)組。 8///</summary> 9///<param name="firstBytes">第一個(gè)字節(jié)數(shù)組</param>10///<param name="firstIndex">第一個(gè)字節(jié)數(shù)組的開始截取索引</param>11///<param name="firstLength">第一個(gè)字節(jié)數(shù)組的截取長度</param>12///<param name="secondBytes">第二個(gè)字節(jié)數(shù)組</param>13///<param name="secondIndex">第二個(gè)字節(jié)數(shù)組的開始截取索引</param>14///<param name="secondLength">第二個(gè)字節(jié)數(shù)組的截取長度</param>15///<returns></returns>16publicstaticbyte[] ToArray(byte[] firstBytes,intfirstIndex,intfirstLength,byte[] secondBytes,intsecondIndex,int secondLength)17? ? ? ? {18using(MemoryStream ms =new MemoryStream())19? ? ? ? ? ? {20BinaryWriter bw =new BinaryWriter(ms);21? ? ? ? ? ? ? ? bw.Write(firstBytes, firstIndex, firstLength);22? ? ? ? ? ? ? ? bw.Write(secondBytes, secondIndex, secondLength);2324? ? ? ? ? ? ? ? bw.Close();25? ? ? ? ? ? ? ? bw.Dispose();2627return ms.ToArray();28? ? ? ? ? ? }29? ? ? ? }30}

1byte[] msgBytes = Encoding.Unicode.GetBytes("要發(fā)送的消息");2MessageXieYi msgXY =newMessageXieYi(0,0, msgBytes);3networkStream.Write(msgXY.ToBytes(),0, msgXY.ToBytes().Length);

1#region線程執(zhí)行體,接收消息 2///<summary> 3/// 線程執(zhí)行體,接收消息 4///</summary> 5///<param name="obj">傳遞給線程執(zhí)行體的用戶名麸锉,用以與用戶通信</param> 6privatevoidThreadReceive(object obj) 7? ? ? ? { 8//通過用戶名找出已經(jīng)保存在哈希表里的Socket 9Socket savedSocket = hashtable_UserNameToSocket[obj]as Socket;1011MessageXieYi msgXY =new MessageXieYi();1213byte[] buffer =newbyte[64];//定義一個(gè)大小為64的緩沖區(qū)14//byte[] receivedBytes = new byte[] { };15byte[] newBuffer =newbyte[] { };//大小可變的緩存器1617int receivedLength;18intavailableLength;//沒什么實(shí)際意義,就是為了方便理解Socket傳輸機(jī)制1920while(true)21? ? ? ? ? ? {22try23? ? ? ? ? ? ? ? {24buffer =newbyte[64];2526for(inti =0; i <10; i++)27? ? ? ? ? ? ? ? ? ? {28availableLength = savedSocket.Available;2930Console.WriteLine("【循環(huán)判斷有多少可讀Bytes】savedSocket.Available["+ i +"]="+ availableLength);//沒實(shí)際意義毙芜,就是來個(gè)直觀感受Socket的原理31? ? ? ? ? ? ? ? ? ? }3233Console.WriteLine("【可變緩存器大小】newBuffer.Length="+ newBuffer.Length);3435receivedLength = savedSocket.Receive(buffer);3637Console.WriteLine("【接收到數(shù)據(jù)】buffer.Length="+ receivedLength);3839newBuffer = CombineBytes.ToArray(newBuffer,0, newBuffer.Length, buffer,0, receivedLength);4041Console.WriteLine("【將接收到的數(shù)據(jù)追加在newBuffer后】newBuffer.Length="+ newBuffer.Length);4243if(newBuffer.Length <6)44? ? ? ? ? ? ? ? ? ? {45Console.WriteLine("newBuffer.Length="+ newBuffer.Length +"< 6 \t -> \t continue");46continue;47? ? ? ? ? ? ? ? ? ? }48else//newBuffer.Length >= 649? ? ? ? ? ? ? ? ? ? {50//取msgXY包頭部分51msgXY = MessageXieYi.FromBytes(newBuffer);52intfirstFlag = msgXY.XieYiFirstFlag;53intsecondFlag = msgXY.XieYiSecondFlag;54intmsgContentLength = msgXY.MessageContentLength;555657//判斷去掉msgXY包頭剩下的長度是否達(dá)到可以取包實(shí)質(zhì)內(nèi)容58while((newBuffer.Length -6) >= msgContentLength)59? ? ? ? ? ? ? ? ? ? ? ? {60Console.WriteLine("【newBuffer去掉包頭的長度="+ (newBuffer.Length -6) +"】>=【"+"包實(shí)質(zhì)內(nèi)容長度="+ msgContentLength +"】");61msgXY =null;62msgXY = MessageXieYi.FromBytes(newBuffer);63Console.WriteLine("\n【拆包】="+ Encoding.Unicode.GetString(msgXY.MessageContent) +"\n");6465newBuffer = msgXY.DuoYvBytes;66Console.WriteLine("【剩余的newBuffer】newBuffer.Length="+ newBuffer.Length);6768if(newBuffer.Length >=6)69? ? ? ? ? ? ? ? ? ? ? ? ? ? {70msgXY = MessageXieYi.FromBytes(newBuffer);71firstFlag = msgXY.XieYiFirstFlag;72secondFlag = msgXY.XieYiSecondFlag;73msgContentLength = msgXY.MessageContentLength;74continue;75? ? ? ? ? ? ? ? ? ? ? ? ? ? }76else77? ? ? ? ? ? ? ? ? ? ? ? ? ? {78break;79? ? ? ? ? ? ? ? ? ? ? ? ? ? }80? ? ? ? ? ? ? ? ? ? ? ? }81? ? ? ? ? ? ? ? ? ? }8283availableLength = savedSocket.Available;84Console.WriteLine("savedSocket.Available="+ availableLength +"\n\n\n\n");8586continue;87? ? ? ? ? ? ? ? }88catch89? ? ? ? ? ? ? ? {90//異常處理91? ? ? ? ? ? ? ? }92? ? ? ? ? ? }93? ? ? ? }94#endregion

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末展辞,一起剝皮案震驚了整個(gè)濱河市洽腺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌藕坯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異齐疙,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來毒嫡,“玉大人碘梢,你說我怎么就攤上這事肛鹏。” “怎么了健田?”我有些...
    開封第一講書人閱讀 164,548評論 0 354
  • 文/不壞的土叔 我叫張陵好爬,是天一觀的道長蜈漓。 經(jīng)常有香客問我,道長融虽,這世上最難降的妖魔是什么享完? 我笑而不...
    開封第一講書人閱讀 58,657評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮有额,結(jié)果婚禮上般又,老公的妹妹穿的比我還像新娘彼绷。我一直安慰自己,他們只是感情好茴迁,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評論 6 392
  • 文/花漫 我一把揭開白布寄悯。 她就那樣靜靜地躺著,像睡著了一般堕义。 火紅的嫁衣襯著肌膚如雪热某。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,554評論 1 305
  • 那天胳螟,我揣著相機(jī)與錄音,去河邊找鬼筹吐。 笑死糖耸,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的丘薛。 我是一名探鬼主播嘉竟,決...
    沈念sama閱讀 40,302評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼洋侨!你這毒婦竟也來了舍扰?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,216評論 0 276
  • 序言:老撾萬榮一對情侶失蹤希坚,失蹤者是張志新(化名)和其女友劉穎边苹,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體裁僧,經(jīng)...
    沈念sama閱讀 45,661評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡个束,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了聊疲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片茬底。...
    茶點(diǎn)故事閱讀 39,977評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖获洲,靈堂內(nèi)的尸體忽然破棺而出阱表,到底是詐尸還是另有隱情,我是刑警寧澤贡珊,帶...
    沈念sama閱讀 35,697評論 5 347
  • 正文 年R本政府宣布最爬,位于F島的核電站,受9級特大地震影響飞崖,放射性物質(zhì)發(fā)生泄漏烂叔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評論 3 330
  • 文/蒙蒙 一固歪、第九天 我趴在偏房一處隱蔽的房頂上張望蒜鸡。 院中可真熱鬧胯努,春花似錦、人聲如沸逢防。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽忘朝。三九已至灰署,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間局嘁,已是汗流浹背溉箕。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悦昵,地道東北人肴茄。 一個(gè)月前我還...
    沈念sama閱讀 48,138評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像但指,于是被迫代替她去往敵國和親寡痰。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 一棋凳、基礎(chǔ)知識:1拦坠、JVM、JRE和JDK的區(qū)別:JVM(Java Virtual Machine):java虛擬機(jī)...
    殺小賊閱讀 2,379評論 0 4
  • iPhone的標(biāo)準(zhǔn)推薦是CFNetwork 庫編程剩岳,其封裝好的開源庫是 cocoa AsyncSocket庫贞滨,用它...
    Ethan_Struggle閱讀 2,244評論 2 12
  • 說明 本文 翻譯自 realpython 網(wǎng)站上的文章教程 Socket Programming in Pytho...
    keelii閱讀 2,121評論 0 16
  • 不避幽遠(yuǎn) 騎行取道 探山訪水 搜奇獵異 潭水小丘 石渠山澗 紛至沓來 美不勝收 聞水聲 如鳴...
    青衫濕舊閱讀 366評論 47 18
  • 看到一起學(xué)車的伙伴們有的拿到駕照了,有的科二考過了卢肃,有的在考科三了疲迂,心里真不是滋味,自己科二掛了莫湘,要明天才夠10天...
    小縣城生活日記閱讀 189評論 0 1