You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
556 lines
18 KiB
556 lines
18 KiB
/*
|
|
Sog 游戏基础库
|
|
2016 by zouwei
|
|
*/
|
|
|
|
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
|
|
|
|
namespace SogClient
|
|
{
|
|
public class TcpSession :NetSession
|
|
{
|
|
const int TCP_BUFFER_SIZE = 1024 * 64;
|
|
|
|
private bool m_recvAsyncPending;
|
|
private bool m_sendAsyncPending;
|
|
private object m_recvAsyncLocker;
|
|
private object m_sendAsyncLocker;
|
|
private SocketAsyncEventArgs m_recvAsyncEventArgs;
|
|
private SocketAsyncEventArgs m_sendAsyncEventArgs;
|
|
|
|
// todo shutdown和dontlinger有什么区别?
|
|
private bool needShutdown; // 只是为了判断是否需要socket.shutdown
|
|
|
|
public TcpSession(Socket socket):base(socket)
|
|
{
|
|
m_recvBuffer = new byte[TCP_BUFFER_SIZE];
|
|
m_sendBuffer = new byte[TCP_BUFFER_SIZE];
|
|
|
|
InitRecvSendAsyncObject();
|
|
m_remoteEndPoint = (IPEndPoint)m_socket.RemoteEndPoint;
|
|
m_localEndPoint = (IPEndPoint)m_socket.LocalEndPoint;
|
|
// socket.Connected表示最近一次IO操作时的socket状态
|
|
// 这里Connected说明是server.accept成功时
|
|
if (m_socket.Connected)
|
|
{
|
|
needShutdown = true;
|
|
}
|
|
}
|
|
|
|
protected override void OnBindSocket()
|
|
{
|
|
InitRecvSendAsyncObject();
|
|
m_remoteEndPoint = (IPEndPoint)m_socket.RemoteEndPoint;
|
|
m_localEndPoint = (IPEndPoint)m_socket.LocalEndPoint;
|
|
needShutdown = true;
|
|
}
|
|
|
|
private void InitRecvSendAsyncObject()
|
|
{
|
|
m_recvAsyncLocker = new object();
|
|
m_sendAsyncLocker = new object();
|
|
|
|
m_recvAsyncEventArgs = new SocketAsyncEventArgs();
|
|
m_recvAsyncEventArgs.Completed += OnRecvAsyncCallback;
|
|
|
|
m_sendAsyncEventArgs = new SocketAsyncEventArgs();
|
|
m_sendAsyncEventArgs.Completed += OnSendAsyncCallback;
|
|
}
|
|
|
|
protected override void CloseSocket()
|
|
{
|
|
try
|
|
{
|
|
// 在调用Accept和Connect之前访问socket.RemoteEndPoint会抛出异常
|
|
TraceLog.Trace("TcpSession.CloseSocket remote {0}", m_remoteEndPoint);
|
|
|
|
if (m_socket != null && needShutdown)
|
|
{
|
|
needShutdown = false;
|
|
m_socket.Shutdown(SocketShutdown.Both);
|
|
}
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
TraceLog.Trace("TcpSession.CloseSocket SocketException errcode {0} msg {1}"
|
|
, ex.ErrorCode, ex.Message);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TraceLog.Exception(ex);
|
|
}
|
|
finally
|
|
{
|
|
if(m_socket != null)
|
|
{
|
|
m_socket.Close();
|
|
m_socket = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void CheckSocketAndSendKeepAlive()
|
|
{
|
|
if (m_socket == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//发送0字节是没有效果的,这里发一个空包,这个功能增大网络流量,可以将时间设置大点
|
|
//需要客户端兼容
|
|
TimeSpan span = DateTime.Now - LastDataTime;
|
|
if(span.TotalSeconds > 10)
|
|
{
|
|
// LastSendDataTimeSecond = AppTime.GetNowSysSecond();
|
|
|
|
LastDataTime = DateTime.Now;
|
|
|
|
try
|
|
{
|
|
if (m_socket.Poll(0, SelectMode.SelectWrite))
|
|
{
|
|
m_noWriteableCheckCount = 0;
|
|
|
|
//有数据不能发送,会破坏结构
|
|
if (m_sendDataLeft == 0)
|
|
{
|
|
m_socket.Send(m_zeroLengthBytes, 0, m_zeroLengthBytes.Length, SocketFlags.None);
|
|
}
|
|
}
|
|
// else
|
|
// {
|
|
// CheckNoWriteableSocket();
|
|
// }
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
TraceLog.Error("TcpSession.CheckSocketAndSendKeepAlive remote {0} SocketException err {1}"
|
|
, m_remoteEndPoint, ex.SocketErrorCode);
|
|
|
|
if (ex.SocketErrorCode != SocketError.WouldBlock)
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TraceLog.Error("TcpSession.CheckSocketAndSendKeepAlive remote {0} SocketException message {1}"
|
|
, m_remoteEndPoint, ex.Message);
|
|
|
|
Close();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CheckNoWriteableSocket()
|
|
{
|
|
if (m_socket == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 通过send来判断socket是否还有效,万一成功时m_sendDataLeft!=0,就把消息打断了
|
|
// 改为count++; 后续只要socket有一次成功的recv/send就把计数清0
|
|
|
|
m_noWriteableCheckCount++;
|
|
|
|
TraceLog.Trace("TcpSession.CheckNoWriteableSocket socket no writeable, remote {0}, noWriteableCheckCount {1}"
|
|
, m_remoteEndPoint, m_noWriteableCheckCount);
|
|
|
|
// LastSendDataTimeSecond = AppTime.GetNowSysSecond();
|
|
|
|
//如果连续多个5秒socket不可写,可以认为是断线了
|
|
if (m_noWriteableCheckCount >= 5)
|
|
{
|
|
TraceLog.Error("TcpSession.CheckNoWriteableSocket socket no writeable, need close it remote {0}, noWriteableCheckCount {1} to much"
|
|
, m_remoteEndPoint, m_noWriteableCheckCount);
|
|
|
|
Close();
|
|
}
|
|
}
|
|
|
|
|
|
public override void RecvReadableSocket()
|
|
{
|
|
if(m_socket == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
try
|
|
{
|
|
int iRecvLen = m_socket.Receive(m_recvBuffer, m_alreadyRecvLength, m_recvBuffer.Length - m_alreadyRecvLength,SocketFlags.None);
|
|
if(iRecvLen > 0)
|
|
{
|
|
TotalRecvLength += iRecvLen;
|
|
// LastRecvDataTimeSecond = AppTime.GetNowSysSecond();
|
|
m_alreadyRecvLength += iRecvLen;
|
|
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.RecvReadableSocket sessionid {0} try recv from remote {1} to local {4},realy recv length {2} left length {3} "
|
|
, SessionID, m_remoteEndPoint, iRecvLen, m_alreadyRecvLength, m_localEndPoint);
|
|
}
|
|
|
|
TryReadMessageFromBuffer(false,false);
|
|
}
|
|
}
|
|
catch(SocketException socketex)
|
|
{
|
|
TraceLog.Debug("TcpSession.RecvReadableSocket socket session {0} receive error HResult {1} message {2}"
|
|
, SessionID, socketex.HResult, socketex.Message);
|
|
|
|
Close();
|
|
}
|
|
catch(Exception ex)
|
|
{
|
|
TraceLog.Error("TcpSession.RecvReadableSocket socket session {0} receive unkonw error, message {1}"
|
|
, SessionID, ex.Message);
|
|
|
|
Close();
|
|
}
|
|
}
|
|
|
|
|
|
public override void WriteWriteableSocket()
|
|
{
|
|
if (m_socket == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_noWriteableCheckCount = 0;
|
|
|
|
try
|
|
{
|
|
if(m_sendDataLeft == 0)
|
|
{
|
|
CopySendBuffer();
|
|
}
|
|
|
|
if(m_sendDataLeft > 0)
|
|
{
|
|
int requestSendLength = m_sendDataLeft;
|
|
|
|
int iSendBytes = m_socket.Send(m_sendBuffer, m_sendDataPos, requestSendLength, SocketFlags.None);
|
|
if(iSendBytes > 0)
|
|
{
|
|
TotalSendLength += iSendBytes;
|
|
// LastSendDataTimeSecond = AppTime.GetNowSysSecond();
|
|
|
|
m_sendDataLeft -= iSendBytes;
|
|
m_sendDataPos += iSendBytes;
|
|
|
|
if(m_sendDataLeft == 0)
|
|
{
|
|
m_sendDataPos = 0;
|
|
}
|
|
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.WriteWriteableSocket sessionid {5} try send from local : {6} to remote :{0} length {1},realy send length {2} left length {3} dataLengthInSendQueue {4}"
|
|
, m_remoteEndPoint, requestSendLength, iSendBytes, m_sendDataLeft, m_dataLengthInSendQueue, SessionID, m_localEndPoint);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.WriteWriteableSocket send no data length 0 from local : {6} to remote {0} request length {1} send length {2} left length {3}"
|
|
, m_remoteEndPoint, requestSendLength, iSendBytes, m_sendDataLeft, m_localEndPoint);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.WriteWriteableSocket send from local : {1} to remote {0} no data", m_remoteEndPoint, m_localEndPoint);
|
|
}
|
|
}
|
|
}
|
|
catch(SocketException ex)
|
|
{
|
|
// 正常
|
|
if (ex.SocketErrorCode == SocketError.WouldBlock)
|
|
{
|
|
TraceLog.Trace("TcpSession.WriteWriteableSocket session {0} send erro, errorcode WouldBlock"
|
|
, SessionID);
|
|
}
|
|
else
|
|
{
|
|
TraceLog.Error("TcpSession.WriteWriteableSocket session {0} send erro, errorcode {1} message {2}"
|
|
, SessionID, ex.SocketErrorCode, ex.Message);
|
|
|
|
Close();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TraceLog.Error("TcpSession.WriteWriteableSocket socket session {0} send erro, message {1}", SessionID, ex.Message);
|
|
|
|
Close();
|
|
}
|
|
}
|
|
|
|
|
|
public override void StartSendAsync()
|
|
{
|
|
if (m_socket == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TraceLog.Trace("TcpSession.StartSendAsync socket session {0}", SessionID);
|
|
|
|
lock (m_sendAsyncLocker)
|
|
{
|
|
if (m_sendAsyncPending)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_sendAsyncPending = true;
|
|
}
|
|
|
|
DoSendAsync();
|
|
}
|
|
|
|
private void DoSendAsync()
|
|
{
|
|
try
|
|
{
|
|
if (m_sendDataLeft == 0)
|
|
{
|
|
CopySendBuffer();
|
|
}
|
|
|
|
if (m_sendDataLeft > 0)
|
|
{
|
|
int requestSendLength = m_sendDataLeft;
|
|
TraceLog.Trace("TcpSession.DoSendAsync session {0} remote {1} requestSendLength {2}"
|
|
, SessionID, m_remoteEndPoint, requestSendLength);
|
|
|
|
m_sendAsyncEventArgs.SetBuffer(m_sendBuffer, m_sendDataPos, requestSendLength);
|
|
bool pending = m_socket.SendAsync(m_sendAsyncEventArgs);
|
|
if (!pending)
|
|
{
|
|
OnSendAsyncCallback(null, m_sendAsyncEventArgs);
|
|
}
|
|
}// keep alive
|
|
else if((DateTime.Now - LastDataTime).TotalSeconds >= 60)
|
|
{
|
|
TraceLog.Trace("TcpSession.DoSendAsync socket session {0} keep alive, send empty message", SessionID);
|
|
|
|
// LastSendDataTimeSecond = AppTime.GetNowSysSecond();
|
|
|
|
m_sendAsyncEventArgs.SetBuffer(m_zeroLengthBytes, 0, m_zeroLengthBytes.Length);
|
|
bool pending = m_socket.SendAsync(m_sendAsyncEventArgs);
|
|
if (!pending)
|
|
{
|
|
OnSendAsyncCallback(null, m_sendAsyncEventArgs);
|
|
}
|
|
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.DoSendAsync send remote {0} no data", m_remoteEndPoint);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lock (m_sendAsyncLocker)
|
|
{
|
|
m_sendAsyncPending = false;
|
|
}
|
|
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.DoSendAsync send remote {0} no data"
|
|
, m_remoteEndPoint);
|
|
}
|
|
}
|
|
}
|
|
catch (SocketException ex)
|
|
{
|
|
TraceLog.Error("TcpSession.DoSendAsync socket session {0} send erro, errorcode {1} message {2}"
|
|
, SessionID, ex.SocketErrorCode, ex.Message);
|
|
|
|
Close();
|
|
|
|
lock (m_sendAsyncLocker)
|
|
{
|
|
m_sendAsyncPending = false;
|
|
}
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TraceLog.Error("TcpSession.DoSendAsync socket session {0} send erro, message {1}", SessionID, ex.Message);
|
|
|
|
Close();
|
|
|
|
lock (m_sendAsyncLocker)
|
|
{
|
|
m_sendAsyncPending = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnSendAsyncCallback(object sender, SocketAsyncEventArgs args)
|
|
{
|
|
int iSendBytes = args.BytesTransferred;
|
|
|
|
// TraceLog.Trace("TcpSession.OnSendAsyncCallback sessionid {0} send length {1} "
|
|
// , SessionID, iSendBytes);
|
|
|
|
if (args.SocketError != SocketError.Success || args.BytesTransferred <= 0)
|
|
{
|
|
TraceLog.Trace("TcpSession.OnSendAsyncCallback session {0} receive error {1}"
|
|
, SessionID, args.SocketError);
|
|
|
|
Close();
|
|
|
|
lock (m_sendAsyncLocker)
|
|
{
|
|
m_sendAsyncPending = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
m_noWriteableCheckCount = 0;
|
|
// LastSendDataTimeSecond = AppTime.GetNowSysSecond();
|
|
|
|
if(args.Buffer == m_sendBuffer)
|
|
{
|
|
TotalSendLength += iSendBytes;
|
|
|
|
m_sendDataLeft -= iSendBytes;
|
|
m_sendDataPos += iSendBytes;
|
|
|
|
if (m_sendDataLeft == 0)
|
|
{
|
|
m_sendDataPos = 0;
|
|
}
|
|
}
|
|
else if(args.Buffer == m_zeroLengthBytes)
|
|
{
|
|
//keep alive
|
|
}
|
|
|
|
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.OnSendAsyncCallback sessionid {5} try send remote {0} length {1},realy send length {2} left length {3} dataLengthInSendQueue {4}"
|
|
, m_remoteEndPoint, iSendBytes, iSendBytes, m_sendDataLeft, m_dataLengthInSendQueue, SessionID);
|
|
}
|
|
|
|
DoSendAsync();
|
|
}
|
|
|
|
|
|
public override void StartRecvAsync()
|
|
{
|
|
if (m_socket == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TraceLog.Trace("TcpSession.StartRecvAsync socket session {0}" , SessionID);
|
|
|
|
lock (m_recvAsyncLocker)
|
|
{
|
|
if (m_recvAsyncPending)
|
|
{
|
|
return;
|
|
}
|
|
|
|
m_recvAsyncPending = true;
|
|
}
|
|
|
|
DoRecvAsync();
|
|
}
|
|
|
|
private void DoRecvAsync()
|
|
{
|
|
try
|
|
{
|
|
TraceLog.Trace("TcpSession.DoRecvAsync socket session {0} m_alreadyRecvLength {1}", SessionID, m_alreadyRecvLength);
|
|
|
|
m_recvAsyncEventArgs.SetBuffer(m_recvBuffer, m_alreadyRecvLength, m_recvBuffer.Length - m_alreadyRecvLength);
|
|
|
|
bool pending = m_socket.ReceiveAsync(m_recvAsyncEventArgs);
|
|
if (!pending)
|
|
{
|
|
OnRecvAsyncCallback(null, m_recvAsyncEventArgs);
|
|
}
|
|
}
|
|
catch (SocketException socketex)
|
|
{
|
|
TraceLog.Trace("TcpSession.DoRecvAsync socket session {0} receive error HResult {1} message {2}"
|
|
, SessionID, socketex.HResult, socketex.Message);
|
|
|
|
Close();
|
|
|
|
lock (m_recvAsyncLocker)
|
|
{
|
|
m_recvAsyncPending = false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
TraceLog.Error("TcpSession.DoRecvAsync socket session {0} receive unkonw error, message {1}"
|
|
, SessionID, ex.Message);
|
|
|
|
Close();
|
|
|
|
lock (m_recvAsyncLocker)
|
|
{
|
|
m_recvAsyncPending = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void OnRecvAsyncCallback(object sender, SocketAsyncEventArgs args)
|
|
{
|
|
int iRecvLen = args.BytesTransferred;
|
|
|
|
if (args.SocketError != SocketError.Success || args.BytesTransferred <= 0)
|
|
{
|
|
TraceLog.Trace("TcpSession.OnRecvAsyncCallback socket session {0} SocketError {1}"
|
|
, SessionID, args.SocketError);
|
|
|
|
Close();
|
|
|
|
lock (m_recvAsyncLocker)
|
|
{
|
|
m_recvAsyncPending = false;
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
TotalRecvLength += iRecvLen;
|
|
// LastRecvDataTimeSecond = AppTime.GetNowSysSecond();
|
|
m_alreadyRecvLength += iRecvLen;
|
|
|
|
if (WriteSendRecvLog)
|
|
{
|
|
TraceLog.Trace("TcpSession.OnRecvAsyncCallback sessionid {0} try recv remote {1},realy recv length {2} m_alreadyRecvLength {3} "
|
|
, SessionID, m_remoteEndPoint, iRecvLen, m_alreadyRecvLength);
|
|
}
|
|
|
|
TryReadMessageFromBuffer(false,true);
|
|
|
|
//继续接收消息
|
|
DoRecvAsync();
|
|
}
|
|
|
|
}
|
|
}
|