最近工作需要,需要学习c# sockets
编程,网络上找到了SocketAsyncServer
与 SocketAsyncClient 扩展类,记录下:
Server端:
//SocketListener.cs
using
System;
using
System.IO;
using
System.Net.Sockets;
using
System.Threading;
using
System.Net;
using
System.Text;
namespace
SocketAsyncServer
{
///
<summary>
/// Based on example from
http://msdn2.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.aspx
/// Implements the connection logic for the
socket server.
/// After accepting a connection, all data read
from the client is sent back.
/// The read and echo back to the client pattern
is continued until the client disconnects.
///
</summary>
internal sealed class SocketListener
{
///
<summary>
/// The
socket used to listen for incoming connection requests.
///
</summary>
private
Socket listenSocket;
///
<summary>
/// Mutex
to synchronize server execution.
///
</summary>
private
static Mutex mutex = new Mutex();
///
<summary>
/// Buffer
size to use for each socket I/O operation.
///
</summary>
private
Int32 bufferSize;
///
<summary>
/// The
total number of clients connected to the server.
///
</summary>
private
Int32 numConnectedSockets;
///
<summary>
/// the
maximum number of connections the sample is designed to handle
simultaneously.
///
</summary>
private
Int32 numConnections;
///
<summary>
/// Pool
of reusable SocketAsyncEventArgs objects for write, read and accept
socket operations.
///
</summary>
private
SocketAsyncEventArgsPool readWritePool;
///
<summary>
///
Controls the total number of clients connected to the
server.
///
</summary>
private
Semaphore semaphoreAcceptedClients;
///
<summary>
/// Create
an uninitialized server instance.
/// To
start the server listening for connection requests
/// call
the Init method followed by Start method.
///
</summary>
///
<param name="numConnections">Maximum
number of connections to be handled
simultaneously.</param>
///
<param name="bufferSize">Buffer size
to use for each socket I/O
operation.</param>
internal
SocketListener(Int32 numConnections, Int32 bufferSize)
{
this.numConnectedSockets =
0;
this.numConnections =
numConnections;
this.bufferSize =
bufferSize;
this.readWritePool = new
SocketAsyncEventArgsPool(numConnections);
this.semaphoreAcceptedClients
= new Semaphore(numConnections, numConnections);
// Preallocate pool of
SocketAsyncEventArgs objects.
for (Int32 i = 0; i
< this.numConnections; i++)
{
SocketAsyncEventArgs readWriteEventArg = new
SocketAsyncEventArgs();
readWriteEventArg.Completed += new
EventHandler<SocketAsyncEventArgs>(OnIOCompleted);
readWriteEventArg.SetBuffer(new
Byte[this.bufferSize], 0, this.bufferSize);
// Add SocketAsyncEventArg to the
pool.
this.readWritePool.Push(readWriteEventArg);
}
}
///
<summary>
/// Close
the socket associated with the client.
///
</summary>
///
<param name="e">SocketAsyncEventArg
associated with the completed send/receive
operation.</param>
private
void CloseClientSocket(SocketAsyncEventArgs e)
{
Token token = e.UserToken as
Token;
this.CloseClientSocket(token,
e);
}
private
void CloseClientSocket(Token token, SocketAsyncEventArgs
e)
{
token.Dispose();
// Decrement the counter
keeping track of the total number of clients connected to the
server.
this.semaphoreAcceptedClients.Release();
Interlocked.Decrement(ref
this.numConnectedSockets);
string ip = "";
int handle =
(int)token.Connection.Handle;
Console.WriteLine("A client
Handle[{1}] has been disconnected from the server. There are {0}
clients connected to the server",
this.numConnectedSockets,handle.ToString());
// Free the
SocketAsyncEventArg so they can be reused by another
client.
this.readWritePool.Push(e);
}
///
<summary>
///
Callback method associated with
Socket.AcceptAsync
///
operations and is invoked when an accept operation is
complete.
///
</summary>
///
<param name="sender">Object who
raised the event.</param>
///
<param name="e">SocketAsyncEventArg
associated with the completed accept
operation.</param>
private
void OnAcceptCompleted(object sender, SocketAsyncEventArgs
e)
{
this.ProcessAccept(e);
}
///
<summary>
///
Callback called whenever a receive or send operation is completed
on a socket.
///
</summary>
///
<param name="sender">Object who
raised the event.</param>
///
<param name="e">SocketAsyncEventArg
associated with the completed send/receive
operation.</param>
private
void OnIOCompleted(object sender, SocketAsyncEventArgs
e)
{
// Determine which type of
operation just completed and call the associated
handler.
switch
(e.LastOperation)
{
case SocketAsyncOperation.Receive:
this.ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
this.ProcessSend(e);
break;
default:
throw new
ArgumentException("The last operation completed on the socket was
not a receive or send");
}
}
///
<summary>
///
Process the accept for the socket listener.
///
</summary>
///
<param name="e">SocketAsyncEventArg
associated with the completed accept
operation.</param>
private
void ProcessAccept(SocketAsyncEventArgs e)
{
Socket s =
e.AcceptSocket;
if (s.Connected)
{
try
{
SocketAsyncEventArgs readEventArgs =
this.readWritePool.Pop();
if
(readEventArgs != null)
{
// Get the socket for the
accepted client connection and put it into
the
// ReadEventArg object user
token.
readEventArgs.UserToken = new
Token(s, this.bufferSize);
Interlocked.Increment(ref
this.numConnectedSockets);
string ip =
s.RemoteEndPoint.ToString();
int handle =
(int)s.Handle;
Console.WriteLine("Client[{1}] Handle[{2}] connection accepted.
There are {0} clients connected to the server\n",
this.numConnectedSockets,ip,handle.ToString());
if
(!s.ReceiveAsync(readEventArgs))
{
this.ProcessReceive(readEventArgs);
}
}
else
{
Console.WriteLine("There are
no more available sockets to allocate.");
}
}
catch (SocketException ex)
{
Token
token = e.UserToken as Token;
Console.WriteLine("Error when processing data received from
{0}:\r\n{1}", token.Connection.RemoteEndPoint,
ex.ToString());
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
// Accept the next connection
request.
this.StartAccept(e);
}
}
private
void ProcessError(SocketAsyncEventArgs e)
{
Token token = e.UserToken as
Token;
IPEndPoint localEp =
token.Connection.LocalEndPoint as IPEndPoint;
this.CloseClientSocket(token,
e);
Console.WriteLine("Socket
error {0} on endpoint {1} during {2}.", (Int32)e.SocketError,
localEp, e.LastOperation);
}
///
<summary>
/// 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.
///
</summary>
///
<param name="e">SocketAsyncEventArg
associated with the completed receive
operation.</param>
private
void ProcessReceive(SocketAsyncEventArgs e)
{
// Check if the remote host
closed the connection.
if (e.BytesTransferred
> 0)
{
if (e.SocketError ==
SocketError.Success)
{
Token
token = e.UserToken as Token;
token.SetData(e);
Socket s =
token.Connection;
if
(s.Available == 0)
{
// Set return
buffer.
token.ProcessData(e);
if
(!s.SendAsync(e))
{
// Set the buffer to send back to the
client.
this.ProcessSend(e);
}
}
else if
(!s.ReceiveAsync(e))
{
// Read the next block of
data sent by client.
this.ProcessReceive(e);
}
}
else
{
this.ProcessError(e);
}
}
else
{
this.CloseClientSocket(e);
}
}
///
<summary>
/// 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.
///
</summary>
///
<param name="e">SocketAsyncEventArg
associated with the completed send
operation.</param>
private
void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError ==
SocketError.Success)
{
// Done echoing data back to the
client.
Token token = e.UserToken as Token;
if
(!token.Connection.ReceiveAsync(e))
{
// Read
the next block of data send from the client.
this.ProcessReceive(e);
}
}
else
{
this.ProcessError(e);
}
}
///
<summary>
/// Starts
the server listening for incoming connection requests.
///
</summary>
///
<param name="port">Port where the
server will listen for connection
requests.</param>
internal
void Start(Int32 port)
{
// Get host related
information.
IPAddress[] addressList =
Dns.GetHostEntry(Environment.MachineName).AddressList;
// Get endpoint for the
listener.
IPEndPoint localEndPoint =
new IPEndPoint(addressList[addressList.Length - 1],
port);
// Create the socket which
listens for incoming connections.
this.listenSocket = new
Socket(localEndPoint.AddressFamily, SocketType.Stream,
ProtocolType.Tcp);
this.listenSocket.ReceiveBufferSize = this.bufferSize;
this.listenSocket.SendBufferSize = this.bufferSize;
if
(localEndPoint.AddressFamily ==
AddressFamily.InterNetworkV6)
{
// Set dual-mode (IPv4 & IPv6)
for the socket listener.
// 27 is equivalent to IPV6_V6ONLY socket option
in the winsock snippet below,
// based on
http://blogs.msdn.com/wndp/archive/2006/10/24/creating-ip-agnostic-applications-part-2-dual-mode-sockets.aspx
this.listenSocket.SetSocketOption(SocketOptionLevel.IPv6,
(SocketOptionName)27, false);
this.listenSocket.Bind(new
IPEndPoint(IPAddress.IPv6Any, localEndPoint.Port));
}
else
{
// Associate the socket with the local
endpoint.
this.listenSocket.Bind(localEndPoint);
}
// Start the
server.
this.listenSocket.Listen(this.numConnections);
// Post accepts on the
listening socket.
this.StartAccept(null);
// Blocks the current thread
to receive incoming messages.
mutex.WaitOne();
}
///
<summary>
/// Begins
an operation to accept a connection request from the
client.
///
</summary>
///
<param name="acceptEventArg">The
context object to use when issuing
/// the
accept operation on the server's listening
socket.</param>
private
void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg ==
null)
{
acceptEventArg = new
SocketAsyncEventArgs();
acceptEventArg.Completed += new
EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);
}
else
{
// Socket must be cleared since the context
object is being reused.
acceptEventArg.AcceptSocket = null;
}
this.semaphoreAcceptedClients.WaitOne();
if
(!this.listenSocket.AcceptAsync(acceptEventArg))
{
this.ProcessAccept(acceptEventArg);
}
}
///
<summary>
/// Stop
the server.
///
</summary>
internal
void Stop()
{
this.listenSocket.Close();
mutex.ReleaseMutex();
}
}
}
//Token.cs
using
System;
using
System.Collections.Generic;
using
System.Text;
using
System.Net.Sockets;
using
System.Globalization;
namespace
SocketAsyncServer
{
delegate void ProcessData(SocketAsyncEventArgs
args);
///
<summary>
/// Token for use with
SocketAsyncEventArgs.
///
</summary>
internal sealed class Token :
IDisposable
{
private
Socket connection;
private
StringBuilder sb;
private
Int32 currentIndex;
///
<summary>
/// Class
constructor.
///
</summary>
///
<param name="connection">Socket to
accept incoming
data.</param>
///
<param name="bufferSize">Buffer size
for accepted
data.</param>
internal
Token(Socket connection, Int32 bufferSize)
{
this.connection =
connection;
this.sb = new
StringBuilder(bufferSize);
}
///
<summary>
/// Accept
socket.
///
</summary>
internal
Socket Connection
{
get { return this.connection;
}
}
///
<summary>
///
Process data received from the client.
///
</summary>
///
<param
name="args">SocketAsyncEventArgs used in the
operation.</param>
internal
void ProcessData(SocketAsyncEventArgs args)
{
// Get the message received
from the client.
String received =
this.sb.ToString();
int handle =
(int)this.Connection.Handle;
//int
handle=args.
//TODO Use message received
to perform a specific operation.
Console.WriteLine("Received
From Handle[{2}] Message: "{0}". The server has read {1} bytes.",
received, received.Length,handle.ToString());
Byte[] sendBuffer =
Encoding.ASCII.GetBytes("Return "+ received);
args.SetBuffer(sendBuffer, 0,
sendBuffer.Length);
// Clear StringBuffer, so it
can receive more data from a keep-alive connection
client.
sb.Length = 0;
this.currentIndex =
0;
}
///
<summary>
/// Set
data received from the client.
///
</summary>
///
<param
name="args">SocketAsyncEventArgs used in the
operation.</param>
internal
void SetData(SocketAsyncEventArgs args)
{
Int32 count =
args.BytesTransferred;
if ((this.currentIndex +
count) > this.sb.Capacity)
{
throw new
ArgumentOutOfRangeException("count",
String.Format(CultureInfo.CurrentCulture, "Adding {0} bytes on
buffer which has {1} bytes, the listener buffer will overflow.",
count, this.currentIndex));
}
sb.Append(Encoding.ASCII.GetString(args.Buffer, args.Offset,
count));
this.currentIndex +=
count;
}
#region
IDisposable Members
///
<summary>
///
Release instance.
///
</summary>
public
void Dispose()
{
try
{
this.connection.Shutdown(SocketShutdown.Send);
}
catch
(Exception)
{
// Throw if client has closed, so it is not
necessary to catch.
}
finally
{
this.connection.Close();
}
}
#endregion
}
}
//SocketAsyncEventArgsPool.cs
using
System;
using
System.Collections.Generic;
using
System.Net.Sockets;
namespace
SocketAsyncServer
{
///
<summary>
/// Based on example from
http://msdn2.microsoft.com/en-us/library/system.net.sockets.socketasynceventargs.socketasynceventargs.aspx
/// Represents a collection of reusable
SocketAsyncEventArgs objects.
///
</summary>
internal sealed class
SocketAsyncEventArgsPool
{
///
<summary>
/// Pool
of SocketAsyncEventArgs.
///
</summary>
Stack<SocketAsyncEventArgs>
pool;
///
<summary>
///
Initializes the object pool to the specified size.
///
</summary>
///
<param name="capacity">Maximum number
of SocketAsyncEventArgs objects the pool can
hold.</param>
internal
SocketAsyncEventArgsPool(Int32 capacity)
{
this.pool = new
Stack<SocketAsyncEventArgs>(capacity);
}
///
<summary>
///
Removes a SocketAsyncEventArgs instance from the pool.
///
</summary>
///
<returns>SocketAsyncEventArgs removed
from the pool.</returns>
internal
SocketAsyncEventArgs Pop()
{
lock (this.pool)
{
if (this.pool.Count >
0)
{
return
this.pool.Pop();
}
else
{
return
null;
}
}
}
///
<summary>
/// Add a
SocketAsyncEventArg instance to the
pool.
///
</summary>
///
<param
name="item">SocketAsyncEventArgs instance to add to
the pool.</param>
internal
void Push(SocketAsyncEventArgs item)
{
if (item ==
null)
{
throw new ArgumentNullException("Items added to
a SocketAsyncEventArgsPool cannot be
null");
}
lock (this.pool)
{
this.pool.Push(item);
}
}
}
}
Client端:
//SocketClient.cs
using
System;
using
System.Net;
using
System.Net.Sockets;
using
System.Text;
using
System.Threading;
namespace
SocketAsyncClient
{
///
<summary>
/// Implements the connection logic for the
socket client.
///
</summary>
internal sealed class SocketClient :
IDisposable
{
///
<summary>
///
Constants for socket operations.
///
</summary>
private
const Int32 ReceiveOperation = 1, SendOperation = 0;
///
<summary>
/// The
socket used to send/receive messages.
///
</summary>
private
Socket clientSocket;
///
<summary>
/// Flag
for connected socket.
///
</summary>
private
Boolean connected = false;
///
<summary>
///
Listener endpoint.
///
</summary>
private
IPEndPoint hostEndPoint;
///
<summary>
///
Signals a connection.
///
</summary>
private
static AutoResetEvent autoConnectEvent = new
AutoResetEvent(false);
///
<summary>
///
Signals the send/receive operation.
///
</summary>
private
static AutoResetEvent[] autoSendReceiveEvents = new
AutoResetEvent[]
{
new
AutoResetEvent(false),
new
AutoResetEvent(false)
};
public
bool ConnectState
{
get
{
return this.connected;
}
}
///
<summary>
/// Create
an uninitialized client instance.
/// To
start the send/receive processing
/// call
the Connect method followed by SendReceive method.
///
</summary>
///
<param name="hostName">Name of the
host where the listener is
running.</param>
///
<param name="port">Number of the TCP
port from the
listener.</param>
internal
SocketClient(String hostName, Int32 port)
{
// Get host related
information.
IPHostEntry host =
Dns.GetHostEntry(hostName);
// Addres of the
host.
IPAddress[] addressList =
host.AddressList;
// Instantiates the endpoint
and socket.
this.hostEndPoint = new
IPEndPoint(addressList[addressList.Length - 1], port);
this.clientSocket = new
Socket(this.hostEndPoint.AddressFamily, SocketType.Stream,
ProtocolType.Tcp);
}
///
<summary>
///
Connect to the host.
///
</summary>
///
<returns>True if connection has
succeded, else
false.</returns>
internal
void Connect()
{
SocketAsyncEventArgs
connectArgs = new SocketAsyncEventArgs();
connectArgs.UserToken =
this.clientSocket;
connectArgs.RemoteEndPoint =
this.hostEndPoint;
connectArgs.Completed += new
EventHandler<SocketAsyncEventArgs>(OnConnect);
clientSocket.ConnectAsync(connectArgs);
autoConnectEvent.WaitOne();
SocketError errorCode =
connectArgs.SocketError;
if (errorCode !=
SocketError.Success)
{
throw new
SocketException((Int32)errorCode);
}
}
///
<summary>
///
Disconnect from the host.
///
</summary>
internal
void Disconnect()
{
clientSocket.Disconnect(false);
}
private
void OnConnect(object sender, SocketAsyncEventArgs e)
{
// Signals the end of
connection.
autoConnectEvent.Set();
// Set the flag for socket
connected.
this.connected =
(e.SocketError == SocketError.Success);
}
private
void OnReceive(object sender, SocketAsyncEventArgs e)
{
// Signals the end of
receive.
autoSendReceiveEvents[SendOperation].Set();
}
private
void OnSend(object sender, SocketAsyncEventArgs e)
{
// Signals the end of
send.
autoSendReceiveEvents[ReceiveOperation].Set();
if (e.SocketError ==
SocketError.Success)
{
if (e.LastOperation ==
SocketAsyncOperation.Send)
{
// Prepare
receiving.
Socket s =
e.UserToken as Socket;
byte[]
receiveBuffer = new byte[255];
e.SetBuffer(receiveBuffer, 0, receiveBuffer.Length);
e.Completed += new
EventHandler<SocketAsyncEventArgs>(OnReceive);
s.ReceiveAsync(e);
}
}
else
{
this.ProcessError(e);
}
}
///
<summary>
/// Close
socket in case of failure and throws a SockeException according to
the SocketError.
///
</summary>
///
<param name="e">SocketAsyncEventArg
associated with the failed
operation.</param>
private
void ProcessError(SocketAsyncEventArgs e)
{
Socket s = e.UserToken as
Socket;
if (s.Connected)
{
// close the socket associated with the
client
try
{
s.Shutdown(SocketShutdown.Both);
}
catch (Exception)
{
// throws
if client process has already closed
}
finally
{
if
(s.Connected)
{
s.Close();
}
}
}
// Throw the
SocketException
throw new
SocketException((Int32)e.SocketError);
}
///
<summary>
///
Exchange a message with the host.
///
</summary>
///
<param name="message">Message to
send.</param>
///
<returns>Message sent by the
host.</returns>
internal
String SendReceive(String message)
{
if
(this.connected)
{
// Create a buffer to send.
Byte[] sendBuffer =
Encoding.ASCII.GetBytes(message);
// Prepare arguments for send/receive
operation.
SocketAsyncEventArgs completeArgs = new
SocketAsyncEventArgs();
completeArgs.SetBuffer(sendBuffer, 0,
sendBuffer.Length);
completeArgs.UserToken =
this.clientSocket;
completeArgs.RemoteEndPoint =
this.hostEndPoint;
completeArgs.Completed += new
EventHandler<SocketAsyncEventArgs>(OnSend);
// Start sending asyncronally.
clientSocket.SendAsync(completeArgs);
// Wait for the send/receive
completed.
AutoResetEvent.WaitAll(autoSendReceiveEvents);
// Return data from SocketAsyncEventArgs
buffer.
return
Encoding.ASCII.GetString(completeArgs.Buffer, completeArgs.Offset,
completeArgs.BytesTransferred);
}
else
{
throw new
SocketException((Int32)SocketError.NotConnected);
}
}
#region
IDisposable Members
///
<summary>
///
Disposes the instance of SocketClient.
///
</summary>
public
void Dispose()
{
autoConnectEvent.Close();
autoSendReceiveEvents[SendOperation].Close();
autoSendReceiveEvents[ReceiveOperation].Close();
if
(this.clientSocket.Connected)
{
this.clientSocket.Close();
}
}
#endregion
}
}
//Program.cs
using
System;
namespace
SocketAsyncClient
{
public static class Program
{
public
static void Main(string[] args)
{
try
{
//String host = args[0];
String host="192.168.100.213";
//Int32 port =
Convert.ToInt32(args[1]);
Int32 port = 9900;
Int16 iterations = 1;
//if (args.Length == 3)
//{
//
iterations =
Convert.ToInt16(args[2]);
//}
using (SocketClient sa = new SocketClient(host,
port))
{
sa.Connect();
for (Int32
i = 0; i < iterations; i++)
{
Console.WriteLine(sa.SendReceive("Message #" +
i.ToString()));
}
//Console.Read();
string cmd
= Console.ReadLine().ToUpper();
if (cmd==
"STOP")
{
sa.Disconnect();
}
else
{
Console.WriteLine(sa.SendReceive("Message # ""+cmd
));
}
//Console.WriteLine("Press any key to terminate the client
process...");
//Console.Read();
}
}
catch
(IndexOutOfRangeException)
{
Console.WriteLine("Usage: SocketAsyncClient
<host>
<port> [iterations]");
}
catch
(FormatException)
{
Console.WriteLine("Usage: SocketAsyncClient
<host>
<port> [iterations]." +
"\r\n\t<host> Name of the host to
connect." +
"\r\n\t<port> Numeric value for the
host listening TCP port." +
"\r\n\t[iterations] Number of iterations to the
host.");
}
catch (Exception
ex)
{
Console.WriteLine("ERROR: " +
ex.Message);
}
}
}
}
ps:
步骤1.服务器端监听端口,等待连接
SocketListener sl = new SocketListener(numConnections,
bufferSize);
sl.Start(port);
步骤2 客户端连接至服务器
SocketClient sa = new SocketClient(host, port)
sa.Connect();
http://s13/bmiddle/7744890bgb999007313ac&690SocketAsyncServer 与 SocketAsyncClient" TITLE="[C#] SocketAsyncServer 与 SocketAsyncClient" />
步骤3:客户端发送数据
sa.SendReceive("Message #" + i.ToString())
步骤4 服务器返回数据给客户端:
Byte[] sendBuffer = Encoding.ASCII.GetBytes("Return "+
received); args.SetBuffer(sendBuffer, 0, sendBuffer.Length);
步骤5:断开连接
加载中,请稍候......