C#高性能Socket服务器IOCP实现

引言

我一直在探寻一个高性能的Socket客户端代码。以前,我使用Socket类写了一些基于传统异步编程模型的代码(BeginSend、BeginReceive,等等)也看过很多博客的知识,在linux中有poll和epoll来实现,在windows下面

微软MSDN中也提供了SocketAsyncEventArgs这个类来实现IOCP 地址:https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx

看我主页简介免费C++学习资源,视频教程、职业规划、面试详解、学习路线、开发工具

每晚8点直播讲解C++编程技术。

NET Framework中的APM也称为Begin/End模式。这是因为会调用Begin方法来启动异步操作,然后返回一个IAsyncResult 对象。可以选择将一个代理作为参数提供给Begin方法,异步操作完成时会调用该方法。或者,一个线程可以等待 IAsyncResult.AsyncWaitHandle。当回调被调用或发出等待信号时,就会调用End方法来获取异步操作的结果。这种模式很灵活,使用相对简单,在 .NET Framework 中非常常见。

但是,您必须注意,如果进行大量异步套接字操作,是要付出代价的。针对每次操作,都必须创建一个IAsyncResult对象,而且该对象不能被重复使用。由于大量使用对象分配和垃圾收集,这会影响性能。为了解决这个问题,新版本提供了另一个使用套接字上执行异步I/O的方法模式。这种新模式并不要求为每个套接字操作分配操作上下文对象。

代码下载:http://download.csdn.net/detail/zhujunxxxxx/8431289 这里的代码优化了的

目标

在上面微软提供的例子我觉得不是很完整,没有具体一个流程,只是受到客户端消息后发送相同内容给客户端,初学者不容易看懂流程,因为我花了一天的时间来实现一个功能齐全的IOCP服务器,

效果如下

代码

首先是ICOPServer.cs 这个类是IOCP服务器的核心类,目前这个类是网络上比较全的代码,MSDN上面的例子都没有我的全

usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Net.Sockets;usingSystem.Net;usingSystem.Threading;namespaceServerTest{///<summary>///IOCP SOCKET服务器///</summary>publicclassIOCPServer:IDisposable{constintopsToPreAlloc =2;#regionFields///<summary>///服务器程序允许的最大客户端连接数///</summary>privateint_maxClient;///<summary>///监听Socket,用于接受客户端的连接请求///</summary>privateSocket _serverSock;///<summary>///当前的连接的客户端数///</summary>privateint_clientCount;///<summary>///用于每个I/O Socket操作的缓冲区大小///</summary>privateint_bufferSize =1024;///<summary>///信号量///</summary>Semaphore _maxAcceptedClients;///<summary>///缓冲区管理///</summary>BufferManager _bufferManager;///<summary>///对象池///</summary>SocketAsyncEventArgsPool _objectPool;privatebooldisposed =false;#endregion#regionProperties///<summary>///服务器是否正在运行///</summary>publicboolIsRunning {get;privateset; }///<summary>///监听的IP地址///</summary>publicIPAddress Address {get;privateset; }///<summary>///监听的端口///</summary>publicintPort {get;privateset; }///<summary>///通信使用的编码///</summary>publicEncoding Encoding {get;set; }#endregion#regionCtors///<summary>///异步IOCP SOCKET服务器///</summary>///<param name="listenPort">监听的端口</param>///<param name="maxClient">最大的客户端数量</param>publicIOCPServer(intlistenPort,intmaxClient)        :this(IPAddress.Any, listenPort, maxClient){        }///<summary>///异步Socket TCP服务器///</summary>///<param name="localEP">监听的终结点</param>///<param name="maxClient">最大客户端数量</param>publicIOCPServer(IPEndPoint localEP,intmaxClient)        :this(localEP.Address, localEP.Port, maxClient){        }///<summary>///异步Socket TCP服务器///</summary>///<param name="localIPAddress">监听的IP地址</param>///<param name="listenPort">监听的端口</param>///<param name="maxClient">最大客户端数量</param>publicIOCPServer(IPAddress localIPAddress,intlistenPort,intmaxClient){this.Address = localIPAddress;this.Port = listenPort;this.Encoding = Encoding.Default;            _maxClient = maxClient;            _serverSock =newSocket(localIPAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);            _bufferManager =newBufferManager(_bufferSize * _maxClient * opsToPreAlloc, _bufferSize);            _objectPool =newSocketAsyncEventArgsPool(_maxClient);            _maxAcceptedClients =newSemaphore(_maxClient, _maxClient);        }#endregion#region初始化///<summary>///初始化函数///</summary>publicvoidInit(){// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds // against memory fragmentation_bufferManager.InitBuffer();// preallocate pool of SocketAsyncEventArgs objectsSocketAsyncEventArgs readWriteEventArg;for(inti =0; i < _maxClient; i++)            {//Pre-allocate a set of reusable SocketAsyncEventArgsreadWriteEventArg =newSocketAsyncEventArgs();                readWriteEventArg.Completed +=newEventHandler(OnIOCompleted);                readWriteEventArg.UserToken =null;// assign a byte buffer from the buffer pool to the SocketAsyncEventArg object_bufferManager.SetBuffer(readWriteEventArg);// add SocketAsyncEventArg to the pool_objectPool.Push(readWriteEventArg);            }        }#endregion#regionStart///<summary>///启动///</summary>publicvoidStart(){if(!IsRunning)            {                Init();                IsRunning =true;                IPEndPoint localEndPoint =newIPEndPoint(Address, Port);// 创建监听socket_serverSock =newSocket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);//_serverSock.ReceiveBufferSize = _bufferSize;//_serverSock.SendBufferSize = _bufferSize;if(localEndPoint.AddressFamily == AddressFamily.InterNetworkV6)                {// 配置监听socket为 dual-mode (IPv4 & IPv6) // 27 is equivalent to IPV6_V6ONLY socket option in the winsock snippet below,_serverSock.SetSocketOption(SocketOptionLevel.IPv6, (SocketOptionName)27,false);                    _serverSock.Bind(newIPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));                }else{                    _serverSock.Bind(localEndPoint);                }// 开始监听_serverSock.Listen(this._maxClient);// 在监听Socket上投递一个接受请求。StartAccept(null);            }        }#endregion#regionStop///<summary>///停止服务///</summary>publicvoidStop(){if(IsRunning)            {                IsRunning =false;                _serverSock.Close();//TODO 关闭对所有客户端的连接}        }#endregion#regionAccept///<summary>///从客户端开始接受一个连接操作///</summary>privatevoidStartAccept(SocketAsyncEventArgs asyniar){if(asyniar ==null)            {                asyniar =newSocketAsyncEventArgs();                asyniar.Completed +=newEventHandler(OnAcceptCompleted);            }else{//socket must be cleared since the context object is being reusedasyniar.AcceptSocket =null;            }            _maxAcceptedClients.WaitOne();if(!_serverSock.AcceptAsync(asyniar))            {                ProcessAccept(asyniar);//如果I/O挂起等待异步则触发AcceptAsyn_Asyn_Completed事件//此时I/O操作同步完成,不会触发Asyn_Completed事件,所以指定BeginAccept()方法}        }///<summary>///accept 操作完成时回调函数///</summary>///<param name="sender">Object who raised the event.</param>///<param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>privatevoidOnAcceptCompleted(objectsender, SocketAsyncEventArgs e){            ProcessAccept(e);        }///<summary>///监听Socket接受处理///</summary>///<param name="e">SocketAsyncEventArg associated with the completed accept operation.</param>privatevoidProcessAccept(SocketAsyncEventArgs e){if(e.SocketError == SocketError.Success)            {                Socket s = e.AcceptSocket;//和客户端关联的socketif(s.Connected)                {try{                        Interlocked.Increment(ref_clientCount);//原子操作加1SocketAsyncEventArgs asyniar = _objectPool.Pop();                        asyniar.UserToken = s;                        Log4Debug(String.Format("客户 {0} 连入, 共有 {1} 个连接。", s.RemoteEndPoint.ToString(), _clientCount));if(!s.ReceiveAsync(asyniar))//投递接收请求{                            ProcessReceive(asyniar);                        }                    }catch(SocketException ex)                    {                        Log4Debug(String.Format("接收客户 {0} 数据出错, 异常信息: {1} 。", s.RemoteEndPoint, ex.ToString()));//TODO 异常处理}//投递下一个接受请求StartAccept(e);                }            }        }#endregion#region发送数据///<summary>///异步的发送数据///</summary>///<param name="e"></param>///<param name="data"></param>publicvoidSend(SocketAsyncEventArgs e,byte[] data){if(e.SocketError == SocketError.Success)            {                Socket s = e.AcceptSocket;//和客户端关联的socketif(s.Connected)                {                    Array.Copy(data,0, e.Buffer,0, data.Length);//设置发送数据//e.SetBuffer(data, 0, data.Length); //设置发送数据if(!s.SendAsync(e))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件{// 同步发送时处理发送完成事件ProcessSend(e);                    }else{                        CloseClientSocket(e);                    }                }            }        }///<summary>///同步的使用socket发送数据///</summary>///<param name="socket"></param>///<param name="buffer"></param>///<param name="offset"></param>///<param name="size"></param>///<param name="timeout"></param>publicvoidSend(Socket socket,byte[] buffer,intoffset,intsize,inttimeout){            socket.SendTimeout =0;intstartTickCount = Environment.TickCount;intsent =0;// how many bytes is already sentdo{if(Environment.TickCount > startTickCount + timeout)                {//throw new Exception("Timeout.");}try{                    sent += socket.Send(buffer, offset + sent, size - sent, SocketFlags.None);                }catch(SocketException ex)                {if(ex.SocketErrorCode == SocketError.WouldBlock ||                    ex.SocketErrorCode == SocketError.IOPending ||                    ex.SocketErrorCode == SocketError.NoBufferSpaceAvailable)                    {// socket buffer is probably full, wait and try againThread.Sleep(30);                    }else{throwex;// any serious error occurr}                }            }while(sent < size);        }///<summary>///发送完成时处理函数///</summary>///<param name="e">与发送完成操作相关联的SocketAsyncEventArg对象</param>privatevoidProcessSend(SocketAsyncEventArgs e){if(e.SocketError == SocketError.Success)            {                Socket s = (Socket)e.UserToken;//TODO}else{                CloseClientSocket(e);            }        }#endregion#region接收数据///<summary>///接收完成时处理函数///</summary>///<param name="e">与接收完成操作相关联的SocketAsyncEventArg对象</param>privatevoidProcessReceive(SocketAsyncEventArgs e){if(e.SocketError == SocketError.Success)//if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success){// 检查远程主机是否关闭连接if(e.BytesTransferred >0)                {                    Socket s = (Socket)e.UserToken;//判断所有需接收的数据是否已经完成if(s.Available ==0)                    {//从侦听者获取接收到的消息。 //String received = Encoding.ASCII.GetString(e.Buffer, e.Offset, e.BytesTransferred);//echo the data received back to the client//e.SetBuffer(e.Offset, e.BytesTransferred);byte[] data =newbyte[e.BytesTransferred];                        Array.Copy(e.Buffer, e.Offset, data,0, data.Length);//从e.Buffer块中复制数据出来,保证它可重用stringinfo = Encoding.Default.GetString(data);                        Log4Debug(String.Format("收到 {0} 数据为 {1}", s.RemoteEndPoint.ToString(), info));//TODO 处理数据//增加服务器接收的总字节数。}if(!s.ReceiveAsync(e))//为接收下一段数据,投递接收请求,这个函数有可能同步完成,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件{//同步接收时处理接收完成事件ProcessReceive(e);                    }                }            }else{                CloseClientSocket(e);            }        }#endregion#region回调函数///<summary>///当Socket上的发送或接收请求被完成时,调用此函数///</summary>///<param name="sender">激发事件的对象</param>///<param name="e">与发送或接收完成操作相关联的SocketAsyncEventArg对象</param>privatevoidOnIOCompleted(objectsender, SocketAsyncEventArgs e){// Determine which type of operation just completed and call the associated handler.switch(e.LastOperation)            {caseSocketAsyncOperation.Accept:                    ProcessAccept(e);break;caseSocketAsyncOperation.Receive:                    ProcessReceive(e);break;default:thrownewArgumentException("The last operation completed on the socket was not a receive or send");            }        }#endregion#regionClose///<summary>///关闭socket连接///</summary>///<param name="e">SocketAsyncEventArg associated with the completed send/receive operation.</param>privatevoidCloseClientSocket(SocketAsyncEventArgs e){            Log4Debug(String.Format("客户 {0} 断开连接!", ((Socket)e.UserToken).RemoteEndPoint.ToString()));            Socket s = e.UserTokenasSocket;            CloseClientSocket(s, e);        }///<summary>///关闭socket连接///</summary>///<param name="s"></param>///<param name="e"></param>privatevoidCloseClientSocket(Socket s, SocketAsyncEventArgs e){try{                s.Shutdown(SocketShutdown.Send);            }catch(Exception)            {// Throw if client has closed, so it is not necessary to catch.}finally{                s.Close();            }            Interlocked.Decrement(ref_clientCount);            _maxAcceptedClients.Release();            _objectPool.Push(e);//SocketAsyncEventArg 对象被释放,压入可重用队列。}#endregion#regionDispose///<summary>///Performs application-defined tasks associated with freeing,///releasing, or resetting unmanaged resources.///</summary>publicvoidDispose(){            Dispose(true);            GC.SuppressFinalize(this);        }///<summary>///Releases unmanaged and - optionally - managed resources///</summary>///<param name="disposing"><c>true</c>to release///both managed and unmanaged resources;<c>false</c>///to release only unmanaged resources.</param>protectedvirtualvoidDispose(booldisposing){if(!this.disposed)            {if(disposing)                {try{                        Stop();if(_serverSock !=null)                        {                            _serverSock =null;                        }                    }catch(SocketException ex)                    {//TODO 事件}                }                disposed =true;            }        }#endregionpublicvoidLog4Debug(stringmsg){            Console.WriteLine("notice:"+ msg);        }    }}

BufferManager.cs 这个类是缓存管理类,是采用MSDN上面的例子一样的 地址: https://msdn.microsoft.com/zh-cn/library/bb517542.aspx

SocketAsyncEventArgsPool.cs 这个类也是来自MSDN的 地址:https://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs.aspx

需要的话自己到MSDN网站上去取,我就不贴出来了

服务器端

staticvoidMain(string[] args){    IOCPServer server =newIOCPServer(8088,1024);    server.Start();    Console.WriteLine("服务器已启动....");    System.Console.ReadLine();}

客户端

客户端代码也是很简单

staticvoidMain(string[] args){    IPAddress remote = IPAddress.Parse("192.168.3.4");    client c =newclient(8088, remote);    c.connect();    Console.WriteLine("服务器连接成功!");while(true)    {        Console.Write("send>");stringmsg = Console.ReadLine();if(msg =="exit")break;        c.send(msg);    }    c.disconnect();    Console.ReadLine();}

client.cs

publicclassclient{publicTcpClient _client;publicintport;publicIPAddress remote;publicclient(intport, IPAddress remote){this.port = port;this.remote = remote;    }publicvoidconnect(){this._client =newTcpClient();        _client.Connect(remote, port);    }publicvoiddisconnect(){        _client.Close();    }publicvoidsend(stringmsg){byte[] data = Encoding.Default.GetBytes(msg);        _client.GetStream().Write(data,0, data.Length);    }}

IOCPClient类,使用SocketAsyncEventArgs类建立一个Socket客户端。虽然MSDN说这个类特别设计给网络服务器应用,但也没有限制在客户端代码中使用APM。下面给出了IOCPClient类的样例代码:

publicclassIOCPClient{///<summary>///连接服务器的socket///</summary>privateSocket _clientSock;///<summary>///用于服务器执行的互斥同步对象///</summary>privatestaticMutex mutex =newMutex();///<summary>///Socket连接标志///</summary>privateBoolean _connected =false;privateconstintReceiveOperation =1, SendOperation =0;privatestaticAutoResetEvent[]    autoSendReceiveEvents =newAutoResetEvent[]    {newAutoResetEvent(false),newAutoResetEvent(false)    };///<summary>///服务器监听端点///</summary>privateIPEndPoint _remoteEndPoint;publicIOCPClient(IPEndPoint local, IPEndPoint remote){        _clientSock =newSocket(local.AddressFamily, SocketType.Stream, ProtocolType.Tcp);        _remoteEndPoint = remote;    }#region连接服务器///<summary>///连接远程服务器///</summary>publicvoidConnect(){        SocketAsyncEventArgs connectArgs =newSocketAsyncEventArgs();        connectArgs.UserToken = _clientSock;        connectArgs.RemoteEndPoint = _remoteEndPoint;        connectArgs.Completed +=newEventHandler(OnConnected);        mutex.WaitOne();if(!_clientSock.ConnectAsync(connectArgs))//异步连接{            ProcessConnected(connectArgs);        }    }///<summary>///连接上的事件///</summary>///<param name="sender"></param>///<param name="e"></param>voidOnConnected(objectsender, SocketAsyncEventArgs e){        mutex.ReleaseMutex();//设置Socket已连接标志。 _connected = (e.SocketError == SocketError.Success);    }///<summary>///处理连接服务器///</summary>///<param name="e"></param>privatevoidProcessConnected(SocketAsyncEventArgs e){//TODO}#endregion#region发送消息///<summary>///向服务器发送消息///</summary>///<param name="data"></param>publicvoidSend(byte[] data){        SocketAsyncEventArgs asyniar =newSocketAsyncEventArgs();        asyniar.Completed +=newEventHandler(OnSendComplete);        asyniar.SetBuffer(data,0, data.Length);        asyniar.UserToken = _clientSock;        asyniar.RemoteEndPoint = _remoteEndPoint;        autoSendReceiveEvents[SendOperation].WaitOne();if(!_clientSock.SendAsync(asyniar))//投递发送请求,这个函数有可能同步发送出去,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件{// 同步发送时处理发送完成事件ProcessSend(asyniar);        }    }///<summary>///发送操作的回调方法///</summary>///<param name="sender"></param>///<param name="e"></param>privatevoidOnSendComplete(objectsender, SocketAsyncEventArgs e){//发出发送完成信号。 autoSendReceiveEvents[SendOperation].Set();        ProcessSend(e);    }///<summary>///发送完成时处理函数///</summary>///<param name="e">与发送完成操作相关联的SocketAsyncEventArg对象</param>privatevoidProcessSend(SocketAsyncEventArgs e){//TODO}#endregion#region接收消息///<summary>///开始监听服务端数据///</summary>///<param name="e"></param>publicvoidStartRecive(SocketAsyncEventArgs e){//准备接收。 Socket s = e.UserTokenasSocket;byte[] receiveBuffer =newbyte[255];        e.SetBuffer(receiveBuffer,0, receiveBuffer.Length);        e.Completed +=newEventHandler(OnReceiveComplete);        autoSendReceiveEvents[ReceiveOperation].WaitOne();if(!s.ReceiveAsync(e))        {            ProcessReceive(e);        }    }///<summary>///接收操作的回调方法///</summary>///<param name="sender"></param>///<param name="e"></param>privatevoidOnReceiveComplete(objectsender, SocketAsyncEventArgs e){//发出接收完成信号。 autoSendReceiveEvents[ReceiveOperation].Set();        ProcessReceive(e);    }///<summary>///接收完成时处理函数///</summary>///<param name="e">与接收完成操作相关联的SocketAsyncEventArg对象</param>privatevoidProcessReceive(SocketAsyncEventArgs e){if(e.SocketError == SocketError.Success)        {// 检查远程主机是否关闭连接if(e.BytesTransferred >0)            {                Socket s = (Socket)e.UserToken;//判断所有需接收的数据是否已经完成if(s.Available ==0)                {byte[] data =newbyte[e.BytesTransferred];                    Array.Copy(e.Buffer, e.Offset, data,0, data.Length);//从e.Buffer块中复制数据出来,保证它可重用//TODO 处理数据}if(!s.ReceiveAsync(e))//为接收下一段数据,投递接收请求,这个函数有可能同步完成,这时返回false,并且不会引发SocketAsyncEventArgs.Completed事件{//同步接收时处理接收完成事件ProcessReceive(e);                }            }        }    }#endregionpublicvoidClose(){        _clientSock.Disconnect(false);    }///<summary>///失败时关闭Socket,根据SocketError抛出异常。///</summary>///<param name="e"></param>privatevoidProcessError(SocketAsyncEventArgs e){        Socket s = e.UserTokenasSocket;if(s.Connected)        {//关闭与客户端关联的Sockettry{                s.Shutdown(SocketShutdown.Both);            }catch(Exception)            {//如果客户端处理已经关闭,抛出异常 }finally{if(s.Connected)                {                    s.Close();                }            }        }//抛出SocketException thrownewSocketException((Int32)e.SocketError);    }///<summary>///释放SocketClient实例///</summary>publicvoidDispose(){        mutex.Close();        autoSendReceiveEvents[SendOperation].Close();        autoSendReceiveEvents[ReceiveOperation].Close();if(_clientSock.Connected)        {            _clientSock.Close();        }    }}

这个类我没有测试,但是理论上是没问题的。

看我主页简介免费C++学习资源,视频教程、职业规划、面试详解、学习路线、开发工具

每晚8点直播讲解C++编程技术。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容