最近浏览了几篇有关Socket发送消息的文章,发现大家对Socket Send方法理解有所偏差,现将自己在开发过程中对Socket的领悟写出来,以供大家参考。

  (一)架构

  基于TCP协议的Socket通信,架构类似于B/S架构,一个Socket通信服务器,多个Socket通信客户端。Socket通信服务器启动时,会建立一个侦听Socket,侦听Socket将侦听到的Socket连接传给接受Socket,然后由接受Socket完成接受、发送消息,当Socket存在异常时,断开连接。在实际开发项目中,往往要求Socket通信服务器能提供高效、稳定的服务,一般会用到以下技术:双工通信、完成端口、SAEA、池、多线程、异步等。特别是池,用的比较多,池一般包括一下几种:

1)Buffer池,用于集中管控Socket缓冲区,防止内存碎片。

2)SAEA池,用于集中管控Socket,重复利用Socket。

3)SQL池,用于分离网络服务层与数据访问层(SQL的执行效率远远低于网络层执行效率)。

4)线程池,用于从线程池中调用空闲线程执行业务逻辑,进一步提高网络层运行效率。

  (二)Send

  主服务器接受Socket为一端口,客户端Socket为一端口,这两个端口通过TCP协议建立连接,通信基础系统负责管理此连接,它有两个功能:

  1)发送消息

  2)接受消息

  Socket的Send方法,并非大家想象中的从一个端口发送消息到另一个端口,它仅仅是拷贝数据到基础系统的发送缓冲区,然后由基础系统将发送缓冲区的数据到连接的另一端口。值得一说的是,这里的拷贝数据与异步发送消息的拷贝是不一样的,同步发送的拷贝,是直接拷贝数据到基础系统缓冲区,拷贝完成后返回,在拷贝的过程中,执行线程会IO等待, 此种拷贝与Socket自带的Buffer空间无关,但异步发送消息的拷贝,是将Socket自带的Buffer空间内的所有数据,拷贝到基础系统发送缓冲区,并立即返回,执行线程无需IO等待,所以异步发送在发送前必须执行SetBuffer方法,拷贝完成后,会触发你自定义回调函数ProcessSend,在ProcessSend方法中,调用SetBuffer方法,重新初始化Buffer空间。

  口说无凭,下面给个例子:

  服务器端:

客户端:

解释:

客户端第一次发送数据:1234567890。

客户端第一个接受数据:1234567890,该数据由服务端用Send同步方法发送返回。

客户端第二个接受数据:1234567890,该数据由服务端用Send异步方法发送返回。

以上似乎没什么异常,好,接下来,我只发送abc。

客户端第一个接受数据:abc,理所当然,没什么问题。

客户端第二个接受数据:abc4567890!为什么呢?应该是abc才对呀!

好,现在为大家解释一下:

异步发送是将其Buffer空间中所有数据拷贝到基础系统发送缓冲区,第一次拷贝1234567890到发送缓冲区,所以收到1234567890,第二次拷贝abc到发送缓冲区,替换了先前的123,所以收到abc4567890,大家明白的?

源码:


using System.Collections.Generic;
using System.Net.Sockets; // This class creates a single large buffer which can be divided up 
// and assigned to SocketAsyncEventArgs objects for use with each 
// socket I/O operation. 
// This enables bufffers to be easily reused and guards against 
// fragmenting heap memory.
// 
// The operations exposed on the BufferManager class are not thread safe.
class BufferManager
{
int m_numBytes; // the total number of bytes controlled by the buffer pool
byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager
Stack<int> m_freeIndexPool; //
int m_currentIndex;
int m_bufferSize; public BufferManager(int totalBytes, int bufferSize)
{
m_numBytes = totalBytes;
m_currentIndex =0;
m_bufferSize = bufferSize;
m_freeIndexPool =new Stack<int>();
} // Allocates buffer space used by the buffer pool
publicvoid InitBuffer()
{
// create one big large buffer and divide that 
// out to each SocketAsyncEventArg object
m_buffer =newbyte[m_numBytes];
} // Assigns a buffer from the buffer pool to the 
// specified SocketAsyncEventArgs object
//
// <returns>true if the buffer was successfully set, else false</returns>
publicbool SetBuffer(SocketAsyncEventArgs args)
{ if (m_freeIndexPool.Count >0)
{
args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);
}
else
{
if ((m_numBytes - m_bufferSize) < m_currentIndex)
{
returnfalse;
}
args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);
m_currentIndex += m_bufferSize;
}
returntrue;
} // Removes the buffer from a SocketAsyncEventArg object. 
// This frees the buffer back to the buffer pool
publicvoid FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
} }

using System;
using System.Collections.Generic;
using System.Net.Sockets; // Represents a collection of reusable SocketAsyncEventArgs objects. 
class SocketAsyncEventArgsPool
{
Stack<SocketAsyncEventArgs> m_pool; // Initializes the object pool to the specified size
//
// The "capacity" parameter is the maximum number of 
// SocketAsyncEventArgs objects the pool can hold
public SocketAsyncEventArgsPool(int capacity)
{
m_pool =new Stack<SocketAsyncEventArgs>(capacity);
} // Add a SocketAsyncEventArg instance to the pool
//
//The "item" parameter is the SocketAsyncEventArgs instance 
// to add to the pool
publicvoid Push(SocketAsyncEventArgs item)
{
if (item ==null) { thrownew ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); }
lock (m_pool)
{
m_pool.Push(item);
}
} // Removes a SocketAsyncEventArgs instance from the pool
// and returns the object removed from the pool
public SocketAsyncEventArgs Pop()
{
lock (m_pool)
{
return m_pool.Pop();
}
} // The number of SocketAsyncEventArgs instances in the pool
publicint Count
{
get { return m_pool.Count; }
} }

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets; class AsyncUserToken
{
public Socket Socket;
}

using System;
using System.Threading;
using System.Net.Sockets;
using System.Net;
using System.Text; // Implements the connection logic for the socket server. 
// After accepting a connection, all data read from the client 
// is sent back to the client. The read and echo back to the client pattern 
// is continued until the client disconnects.
class Server
{
privateint m_numConnections; // the maximum number of connections the sample is designed to handle simultaneously 
privateint m_receiveBufferSize;// buffer size to use for each socket I/O operation 
BufferManager m_bufferManager; // represents a large reusable set of buffers for all socket operations
constint opsToPreAlloc =2; // read, write (don't alloc buffer space for accepts)
Socket listenSocket; // the socket used to listen for incoming connection requests
// pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
SocketAsyncEventArgsPool m_readWritePool;
int m_totalBytesRead; // counter of the total # bytes received by the server
int m_numConnectedSockets; // the total number of clients connected to the server 
Semaphore m_maxNumberAcceptedClients; // Create an uninitialized server instance. 
// To start the server listening for connection requests
// call the Init method followed by Start method 
//
// <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
// <param name="receiveBufferSize">buffer size to use for each socket I/O operation</param>
public Server(int numConnections, int receiveBufferSize)
{
m_totalBytesRead =0;
m_numConnectedSockets =0;
m_numConnections = numConnections;
m_receiveBufferSize = receiveBufferSize;
// allocate buffers such that the maximum number of sockets can have one outstanding read and 
//write posted to the socket simultaneously 
m_bufferManager =new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
receiveBufferSize); m_readWritePool =new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients =new Semaphore(numConnections, numConnections);
} // Initializes the server by preallocating reusable buffers and 
// context objects. These objects do not need to be preallocated 
// or reused, but it is done this way to illustrate how the API can 
// easily be used to create reusable objects to increase server performance.
//
publicvoid Init()
{
// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds 
// against memory fragmentation
m_bufferManager.InitBuffer(); // preallocate pool of SocketAsyncEventArgs objects
SocketAsyncEventArgs readWriteEventArg; for (int i =0; i < m_numConnections; i++)
{
//Pre-allocate a set of reusable SocketAsyncEventArgs
readWriteEventArg =new SocketAsyncEventArgs();
readWriteEventArg.Completed +=new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken =new AsyncUserToken(); // assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
m_bufferManager.SetBuffer(readWriteEventArg); // add SocketAsyncEventArg to the pool
m_readWritePool.Push(readWriteEventArg);
} } // Starts the server such that it is listening for 
// incoming connection requests. 
//
// <param name="localEndPoint">The endpoint which the server will listening 
// for connection requests on</param>
publicvoid Start(IPEndPoint localEndPoint)
{
// create the socket which listens for incoming connections
listenSocket =new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
// start the server with a listen backlog of 100 connections
listenSocket.Listen(100); // post accepts on the listening socket
StartAccept(null); //Console.WriteLine("{0} connected sockets with one outstanding receive posted to each....press any key", m_outstandingReadCount);
Console.WriteLine("Press any key to terminate the server process....");
Console.ReadKey();
} // Begins an operation to accept a connection request from the client 
//
// <param name="acceptEventArg">The context object to use when issuing 
// the accept operation on the server's listening socket</param>
publicvoid StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg ==null)
{
acceptEventArg =new SocketAsyncEventArgs();
acceptEventArg.Completed +=new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
}
else
{
// socket must be cleared since the context object is being reused
acceptEventArg.AcceptSocket =null;
} m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
} // This method is the callback method associated with Socket.AcceptAsync 
// operations and is invoked when an accept operation is complete
//
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
} privatevoid ProcessAccept(SocketAsyncEventArgs e)
{
Interlocked.Increment(ref m_numConnectedSockets);
Console.WriteLine("Client connection accepted. There are {0} clients connected to the server",
m_numConnectedSockets); // Get the socket for the accepted client connection and put it into the 
//ReadEventArg object user token
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
((AsyncUserToken)readEventArgs.UserToken).Socket = e.AcceptSocket; // As soon as the client is connected, post a receive to the connection
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
if (!willRaiseEvent)
{
ProcessReceive(readEventArgs);
} // Accept the next connection request
StartAccept(e);
} // This method is called whenever a receive or send operation is completed on a socket 
//
// <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
void IO_Completed(object sender, SocketAsyncEventArgs e)
{
// determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
thrownew ArgumentException("The last operation completed on the socket was not a receive or send");
} } // This method is invoked when an asynchronous receive operation completes. 
// If the remote host closed the connection, then the socket is closed. 
// If data was received then the data is echoed back to the client.
//
privatevoid ProcessReceive(SocketAsyncEventArgs e)
{
// check if the remote host closed the connection
AsyncUserToken token = (AsyncUserToken)e.UserToken;
if (e.BytesTransferred >0&& e.SocketError == SocketError.Success)
{
//increment the count of the total bytes receive by the server
Interlocked.Add(ref m_totalBytesRead, e.BytesTransferred);
Console.WriteLine("The server has read a total of {0} bytes", m_totalBytesRead); Int32 BytesToProcess = e.BytesTransferred;
Byte[] bt =new Byte[BytesToProcess];
Buffer.BlockCopy(e.Buffer, e.Offset, bt, 0, BytesToProcess);
string strReceive = Encoding.Default.GetString(bt); Send(token.Socket, bt, 0, bt.Length, 1000); Thread.Sleep(1000); //echo the data received back to the client
//e.SetBuffer(e.Offset, e.BytesTransferred);
bool willRaiseEvent = token.Socket.SendAsync(e);
if (!willRaiseEvent)
{
ProcessSend(e);
} }
else
{
CloseClientSocket(e);
}
} publicstaticvoid Send(Socket socket, byte[] buffer, int offset, int size, int timeout)
{
socket.SendTimeout =0;
int startTickCount = Environment.TickCount;
int sent =0; // how many bytes is already sent
do
{
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 again
Thread.Sleep(30);
}
else
throw ex; // any serious error occurr
}
} while (sent < size);
} // This method is invoked when an asynchronous send operation completes. 
// The method issues another receive on the socket to read any additional 
// data sent from the client
//
// <param name="e"></param>
privatevoid ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
//e.SetBuffer(e.Offset, 10); // done echoing data back to the client
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// read the next block of data send from the client
bool willRaiseEvent = token.Socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
} privatevoid CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken; // close the socket associated with the client
try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
// throws if client process has already closed
catch (Exception) { }
token.Socket.Close(); // decrement the counter keeping track of the total number of clients connected to the server
Interlocked.Decrement(ref m_numConnectedSockets);
m_maxNumberAcceptedClients.Release();
Console.WriteLine("A client has been disconnected from the server. There are {0} clients connected to the server", m_numConnectedSockets); // Free the SocketAsyncEventArg so they can be reused by another client
m_readWritePool.Push(e);
} }

using System;
using System.Net;
using System.Collections.Generic;
using System.IO; class Program
{
staticvoid Main(string[] args)
{
IPEndPoint iep =new IPEndPoint(IPAddress.Parse("10.1.20.6"), 1333); Server objServer =new Server(1000, 10);
objServer.Init();
objServer.Start(iep);
}
}

深入探析c# Socket的更多相关文章

  1. 中文分词工具探析(二):Jieba

    1. 前言 Jieba是由fxsjy大神开源的一款中文分词工具,一款属于工业界的分词工具--模型易用简单.代码清晰可读,推荐有志学习NLP或Python的读一下源码.与采用分词模型Bigram + H ...

  2. 中文分词工具探析(一):ICTCLAS (NLPIR)

    1. 前言 ICTCLAS是张华平在2000年推出的中文分词系统,于2009年更名为NLPIR.ICTCLAS是中文分词界元老级工具了,作者开放出了free版本的源代码(1.0整理版本在此). 作者在 ...

  3. 深入探析koa之中间件流程控制篇

    koa被认为是第二代web后端开发框架,相比于前代express而言,其最大的特色无疑就是解决了回调金字塔的问题,让异步的写法更加的简洁.在使用koa的过程中,其实一直比较好奇koa内部的实现机理.最 ...

  4. Emmet 语法探析

    Emmet 语法探析 Emmet(Zen Coding)是一个能大幅度提高前端开发效率的一个工具. 大多数编辑器都支持Snippet,即存储和重用一些代码块.但是前提是:你必须先定义 这些代码块. E ...

  5. 开源中文分词工具探析(三):Ansj

    Ansj是由孙健(ansjsun)开源的一个中文分词器,为ICTLAS的Java版本,也采用了Bigram + HMM分词模型(可参考我之前写的文章):在Bigram分词的基础上,识别未登录词,以提高 ...

  6. 开源中文分词工具探析(四):THULAC

    THULAC是一款相当不错的中文分词工具,准确率高.分词速度蛮快的:并且在工程上做了很多优化,比如:用DAT存储训练特征(压缩训练模型),加入了标点符号的特征(提高分词准确率)等. 1. 前言 THU ...

  7. 开源中文分词工具探析(五):FNLP

    FNLP是由Fudan NLP实验室的邱锡鹏老师开源的一套Java写就的中文NLP工具包,提供诸如分词.词性标注.文本分类.依存句法分析等功能. [开源中文分词工具探析]系列: 中文分词工具探析(一) ...

  8. Erlang调度器细节探析

    Erlang调度器细节探析 Erlang的很多基础特性使得它成为一个软实时的平台.其中包括垃圾回收机制,详细内容可以参见我的上一篇文章Erlang Garbage Collection Details ...

  9. 开源中文分词工具探析(五):Stanford CoreNLP

    CoreNLP是由斯坦福大学开源的一套Java NLP工具,提供诸如:词性标注(part-of-speech (POS) tagger).命名实体识别(named entity recognizer ...

随机推荐

  1. centos7.4下搭建JDK+Tomcat+Nginx+Mysql+redis+Mongodb+maven+Git+Jenkins

    先干两件大事!先干两件大事!先干两件大事! 1.关闭selinux [root@mycentos ~]# vi /etc/selinux/config SELINUX=disabled 2.关闭防火墙 ...

  2. Flask---ajax(jquery)交互

    目录结构如下: |--| |--run.py |--static |--test.txt |--templates |--index.html 前端代码如下: index.html <!DOCT ...

  3. POJ3037 Skiing

    Skiing 题目大意: 给定一个M*N的网格,已知在每个网格中的点可以向上下左右四个方向移动一个单位,每个点都有一个高度值. 从每个点开始移动时存在一个速度值,从A点移动到B点,则此时B点的速度为& ...

  4. JAVA自定义监听器的示例代码

    JAVA用户自定义事件监听完整例子 JAVA用户自定义事件监听完整例子- —sunfruit     很多介绍用户自定义事件都没有例子,或是例子不全,下面写了一个完整的例子,并写入了注释以便参考,完整 ...

  5. 清北省选 DAY last 集锦

    这是题目描述的链接: http://lifecraft-mc.com/wp-content/uploads/2018/03/problems1.pdf (虽然这次没去清北,但还是厚颜无耻的做了一下这套 ...

  6. [MGR——Mysql的组复制之多主模式 ] 详细搭建部署过程

    组复制可以在两种模式下运行. 1.在单主模式下,组复制具有自动选主功能,每次只有一个 server成员接受更新.2.在多主模式下,所有的 server 成员都可以同时接受更新.   组复制与异步主从复 ...

  7. Gerrit实现代码审计(code review)

    1.Gerrit是个啥? 实现代码审计,比git(gitlab.github)多了代码审计的功能. 2.两类角色 角色1:我是代码编写者,只能写代码和提交给审计者代码,不能直接把代码合并到线上发布 角 ...

  8. jquery显示和隐藏元素

    1.$('#id').show()/$('#id').hide()/$('#id').toggle() 2.$('#id').css('display','none')/$('#id').css('d ...

  9. 解决安装oracle11g r2时提示pdksh conflicts with ksh-20100621-2.el6.i686问题

    http://blog.csdn.net/linghao00/article/details/7943740 http://www.2cto.com/os/201306/218566.html 在Ce ...

  10. SLF4J: Failed to load class的问题及解决

    今天在做接口测试,一运行测试程序,就跳出这样一个大大的错误: SLF4J: Failed to load class “org.slf4j.impl.StaticLoggerBinder”. SLF4 ...