深入探析c# Socket

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

  (一)架构

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

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

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

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

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

 


  (二)Send

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

1)发送消息           

2)接受消息

SocketSend方法,并非大家想象中的从一个端口发送消息到另一个端口,它仅仅是拷贝数据到基础系统的发送缓冲区,然后由基础系统将发送缓冲区的数据到连接的另一端口。值得一说的是,这里的拷贝数据与异步发送消息的拷贝是不一样的,同步发送的拷贝,是直接拷贝数据到基础系统缓冲区,拷贝完成后返回,在拷贝的过程中,执行线程会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 largebuffer 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 bufferpool
byte[] m_buffer; // the underlying byte array maintained by theBuffer 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 thebuffer pool
publicvoid InitBuffer()
{
// create one big large buffer anddivide that 
// out to each SocketAsyncEventArg object
m_buffer =newbyte[m_numBytes];
}

// Assigns a buffer from the bufferpool to the 
// specified SocketAsyncEventArgs object
//
// <returns>true if the buffer was successfully set, elsefalse</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 aSocketAsyncEventArg object. 
// This frees the buffer back to the buffer pool
publicvoid FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null00);
}

}

 

 

 

using System;
using System.Collections.Generic;
using System.Net.Sockets;

// Represents a collection ofreusable SocketAsyncEventArgs objects. 
class SocketAsyncEventArgsPool
{
Stack<SocketAsyncEventArgs> m_pool;

// Initializes the object pool to thespecified 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 instanceto 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 SocketAsyncEventArgsPoolcannot be null"); }
lock (m_pool)
{
m_pool.Push(item);
}
}

// Removes a SocketAsyncEventArgsinstance from the pool
// and returns the object removed from the pool
public SocketAsyncEventArgs Pop()
{
lock (m_pool)
{
return m_pool.Pop();
}
}

// The number of SocketAsyncEventArgsinstances 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 logicfor 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 clientpattern 
// is continued until the client disconnects.
class Server
{
privateint m_numConnections; // the maximum number of connections the sample isdesigned to handle simultaneously 
privateint m_receiveBufferSize;// buffer size to use for each socket I/O operation 
BufferManager m_bufferManager; // represents a large reusable set ofbuffers for all socket operations
constint opsToPreAlloc =2// read, write (don't alloc buffer spacefor accepts)
Socket listenSocket; // the socket used to listen forincoming connection requests
// pool of reusable SocketAsyncEventArgs objects for write, read andaccept socket operations
SocketAsyncEventArgsPool m_readWritePool;
int m_totalBytesRead; // counter of the total # bytes received by theserver
int m_numConnectedSockets; // the total number of clients connectedto the server 
Semaphore m_maxNumberAcceptedClients;

// Create an uninitialized serverinstance. 
// To start the server listening for connection requests
// call the Init method followed by Start method 
//
// <param name="numConnections">the maximum number ofconnections the sample is designed to handle simultaneously</param>
// <param name="receiveBufferSize">buffer size to use foreach 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 themaximum 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 bypreallocating 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 serverperformance.
//
publicvoid Init()
{
// Allocates one large byte bufferwhich all I/O operations use a piece of. This gaurds 
// against memory fragmentation
m_bufferManager.InitBuffer();

// preallocate pool ofSocketAsyncEventArgs objects
SocketAsyncEventArgs readWriteEventArg;

for (int i =0; i < m_numConnections;i++)
{
//Pre-allocate a set of reusableSocketAsyncEventArgs
readWriteEventArg =new SocketAsyncEventArgs();
readWriteEventArg.Completed +=new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken =new AsyncUserToken();

// assign a byte buffer from thebuffer pool to the SocketAsyncEventArg object
m_bufferManager.SetBuffer(readWriteEventArg);

// add SocketAsyncEventArg to thepool
m_readWritePool.Push(readWriteEventArg);
}

}

// Starts the server such that it islistening for 
// incoming connection requests. 
//
// <param name="localEndPoint">The endpoint which theserver will listening 
// for connection requests on</param>
publicvoid Start(IPEndPoint localEndPoint)
{
// create the socket which listensfor incoming connections
listenSocket =new Socket(localEndPoint.AddressFamily, SocketType.Stream,ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
// start the server with a listenbacklog of 100 connections
listenSocket.Listen(100);

// post accepts on the listeningsocket
StartAccept(null);

//Console.WriteLine("{0} connectedsockets with one outstanding receive posted to each....press any key",m_outstandingReadCount);
Console.WriteLine("Press any key to terminate the serverprocess....");
Console.ReadKey();
}


// Begins an operation to accept aconnection request from the client 
//
// <param name="acceptEventArg">The context object to usewhen 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 thecontext object is being reused
acceptEventArg.AcceptSocket =null;
}

m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
}

// This method is the callback methodassociated 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("Clientconnection accepted. There are {0} clients connected to the server",
m_numConnectedSockets);

// Get the socket for the acceptedclient 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 isconnected, 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 areceive or send operation is completed on a socket 
//
// <param name="e">SocketAsyncEventArg associated with thecompleted receive operation</param>
void IO_Completed(object sender,SocketAsyncEventArgs e)
{
// determine which type of operationjust 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 nota receive or send");
}

}

// This method is invoked when anasynchronous 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 closedthe connection
AsyncUserToken token = (AsyncUserToken)e.UserToken;
if (e.BytesTransferred >0&& e.SocketError == SocketError.Success)
{
//increment the count of the total bytesreceive by the server
Interlocked.Add(ref m_totalBytesRead,e.BytesTransferred);
Console.WriteLine("The serverhas 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 theclient
//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 newException("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 anasynchronous 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 sendfrom 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 withthe client
try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
// throws if client process hasalready closed
catch (Exception) { }
token.Socket.Close();

// decrement the counter keepingtrack of the total number of clients connected to the server
Interlocked.Decrement(ref m_numConnectedSockets);
m_maxNumberAcceptedClients.Release();
Console.WriteLine("A clienthas been disconnected from the server. There are {0} clients connected to theserver",m_numConnectedSockets);

// Free the SocketAsyncEventArg sothey 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(100010);
objServer.Init();
objServer.Start(iep);
}
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值