寫自己的Socket框架(一),socket框架
本系列僅介紹可用於生產環境的C#異步Socket框架,如果您在其他地方看到類似的代碼,不要驚訝,那可能就是我在參考開源代碼時,直接“剽竊”過來的。
1、在腦海裡思考一下整個socket的鏈接的處理流程,於是便有了下圖。

2、首先就開始監聽,代碼如下:

![]()
public override bool Start()
{
this._socket = new System.Net.Sockets.Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//設置KeeyAlive,如果客戶端不主動發消息時,Tcp本身會發一個心跳包,來通知服務器,這是一個保持通訊的鏈接。
//避免等到下一次通訊時,才知道鏈接已經斷開。
this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);
this._socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontLinger, true);
try
{
this._socket.Bind(base.SocketConfig.Point);
this._socket.Listen(base.SocketConfig.Backlog);
this._socket_args = new SocketAsyncEventArgs();
this._socket_args.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptSocketCompleted);
//在鏈接過來的時候,如果IO沒有掛起,則AcceptAsync為False,表明同步完成。
if (!this._socket.AcceptAsync(this._socket_args))
{
AcceptSocketCompleted(this._socket, this._socket_args);
}
return true;
}
catch (Exception ex)
{
return false;
}
}
void AcceptSocketCompleted(object sender, SocketAsyncEventArgs e)
{
System.Net.Sockets.Socket socket = null;
if (e.SocketError != SocketError.Success)
{
return;
}
else
{
socket = e.AcceptSocket;
}
e.AcceptSocket = null;
bool willRaiseEvent = false;
try
{
//繼續監聽該端口,在處理邏輯時,不影響其他鏈接的數據傳送。
willRaiseEvent = this._socket.AcceptAsync(e);
}
catch (Exception ex)
{
willRaiseEvent = true;
}
if (socket != null)
OnNewClientAccepted(socket, null);
if (!willRaiseEvent)
AcceptSocketCompleted(null, e);
}
View Code
3、這個時候鏈接過來了,就要開始入隊列了,如果沒有這方面的需求,這一步可以忽略,代碼如下:

![]()
public class SocketProxy
{
public System.Net.Sockets.Socket Client;
public DateTime Timeout = DateTime.Now;
}
public class SocketConnectionQueue : IDisposable
{
private Queue<SocketProxy> _queue;
private readonly object _syncObject = new object();
private bool _isStop = false;
private Thread _thread;
public Action<SocketProxy> Connected;
public SocketConnectionQueue()
{
if (_queue == null)
{
_queue = new Queue<SocketProxy>();
}
if (_thread == null)
{
_thread = new Thread(Thread_Work)
{
IsBackground = true,
Priority = ThreadPriority.Highest
};
_thread.Start();
}
}
public void Push(SocketProxy connect)
{
lock (_syncObject)
{
if (_queue != null)
{
_queue.Enqueue(connect);
}
}
}
public void Thread_Work()
{
while (!_isStop)
{
SocketProxy[] socketConnect = null;
lock (_syncObject)
{
if (_queue.Count > 0)
{
socketConnect = new SocketProxy[_queue.Count];
_queue.CopyTo(socketConnect, 0);
_queue.Clear();
}
}
if (socketConnect != null && socketConnect.Length > 0)
{
foreach (var client in socketConnect)
{
if (Connected != null)
{
Connected.Invoke(client);
}
}
}
Thread.Sleep(10);
}
}
public void Dispose()
{
_isStop = true;
if (_thread != null)
{
_thread.Join();
}
}
}
View Code
4、入完隊列,就要開始從鏈接池子裡面分配資源了,你也可以不做鏈接池,在每次請求過來的時候去實例化一個鏈接,然後將這個鏈接入池,我的做法是在程序初始化的時候就分配好一定的資源,代碼如下:

![]()
public class SocketConnectionPool : IDisposable
{
private ServerConfig _serverConfig;
public IAppServer AppServer;
private ConcurrentStack<SocketConnection> _connectPool;
private long connect_id = 0;
private byte[] _buffer;
private readonly object _syncObject = new object();
private SocketConnectionQueue _queue;
public Action<System.Net.Sockets.Socket, SocketConnection> Connected;
public long GenerateId()
{
if (connect_id == long.MaxValue)
{
connect_id = 0;
}
connect_id++;
return connect_id;
}
public SocketConnectionPool(IAppServer server)
{
this.AppServer = server;
this._serverConfig = server.AppConfig;
}
public void Init()
{
var connects = new List<SocketConnection>(this._serverConfig.MaxConnectionNumber);
_buffer = new byte[this._serverConfig.BufferSize];
SocketAsyncEventArgs arg;
for (var i = 0; i < this._serverConfig.MaxConnectionNumber; i++)
{
arg = new SocketAsyncEventArgs();
arg.SetBuffer(_buffer, 0, _buffer.Length);
connects.Add(new SocketConnection(arg, this));
}
_connectPool = new ConcurrentStack<SocketConnection>(connects);
if (_queue == null)
{
_queue = new SocketConnectionQueue();
}
_queue.Connected = OnConnected;
}
public void Push(System.Net.Sockets.Socket socket)
{
SocketProxy proxy = new SocketProxy()
{
Client = socket
};
_queue.Push(proxy);
}
public void OnConnected(SocketProxy proxy)
{
//如果發現隊列裡面的鏈接,在Timeout時間內,都沒有分配到資源,則關掉鏈接並丟棄。
int timeout = (int)(DateTime.Now - proxy.Timeout).TotalSeconds;
if (timeout >= this._serverConfig.Timeout)
{
proxy.Client.Close();
return;
}
else
{
//沒有分配到資源重新入列。
SocketConnection connect = this.GetConnectionFromPool();
if (connect == null)
{
_queue.Push(proxy);
}
else
{
if (this.Connected != null)
{
this.Connected(proxy.Client, connect);
}
}
}
}
/// <summary>
/// 從鏈接池去取鏈接(LIFO)
/// </summary>
/// <returns></returns>
public SocketConnection GetConnectionFromPool()
{
//_queue.Push();
SocketConnection connect;
if (!_connectPool.TryPop(out connect))
{
return null;
}
lock (_syncObject)
{
long connect_id = this.GenerateId();
connect.ConnectId = connect_id;
}
return connect;
}
/// <summary>
/// 釋放鏈接,並放回鏈接池
/// </summary>
/// <param name="connect"></param>
public void ReleaseConnection(SocketConnection connect)
{
_connectPool.Push(connect);
LogHelper.Debug(connect.ConnectId + "放回ConnectPool");
}
public void Dispose()
{
_queue.Dispose();
}
}
View Code
在Init()裡面初始化了很多個SocketConnection,這個就是我們用來管理具體的單個鏈接的class,代碼如下:

![]()
public class SocketConnection
{
public SocketFlag Flag { get; private set; }
public SocketConnectionPool Pool { get { return _pool; } private set { } }
private SocketConnectionPool _pool;
public SocketAsyncEventArgs RecevieEventArgs { get; set; }
public long ConnectId { get; set; }
public SocketConnection()
{
this.Flag = SocketFlag.Error;
}
public SocketConnection(SocketAsyncEventArgs args, SocketConnectionPool pool)
{
RecevieEventArgs = args;
RecevieEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(SocketEventArgs_Completed);
this.Flag = SocketFlag.Busy;
this._pool = pool;
}
void SocketEventArgs_Completed(object sender, SocketAsyncEventArgs e)
{
var socketSession = e.UserToken as SocketSession;
if (socketSession == null)
{
this.Flag = SocketFlag.Error;
this.Close();
return;
}
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
socketSession.ReceiveData(e);
break;
default:
break;
}
}
public void Initialise(SocketSession session)
{
this.RecevieEventArgs.UserToken = session;
this.Flag = SocketFlag.Busy;
session.Closed += () =>
{
this.Close();
};
}
public void Reset()
{
//ConnectId = 0;
this.RecevieEventArgs.UserToken = null;
this.Flag = SocketFlag.Idle;
}
private void Close()
{
this.Reset();
LogHelper.Debug(ConnectId + " reset");
this._pool.ReleaseConnection(this);
}
}
View Code
利用socket進行面向連接的網絡編程程序框架給我寫一個吧
這個。。。。。你要源代碼?
一點對於socket的問題
第一:2次調CLOSE是第2個CLOSE是通信完後才調用的,第一個是出錯的時候關閉的。
第二:fork返回BOOL值。
SOCKET寫的程序可以跨操作系統用,都支持。而WINSOCK是WINDOWS自己實現的網絡編程接口,寫出來的程序只能在WINDOWS平台使用。
C#中的SOCKET是有.net框架結構限制的,編程更容易些,很多線程的問題都給你解決了。
另外,初期的時候用VC++寫網絡編程會非常困難,建議你有2年VC開發經驗之後再來做網絡的底層編程。
如果你是四川的給我發百度消息吧,我有一些C#和VC程序開發的經驗和自己做的東西,可以給你。