#include "stdafx.h"
#include "TCPClient.h"
#include "CritSection.h"

#define BUF_LEN 8192

CTCPClient::CTCPClient()
{
	Global::Init();
	m_socket = INVALID_SOCKET;
	m_sin = {};
	m_port = 0;
}

CTCPClient::~CTCPClient()
{
	DisConnect();
	WSACleanup();
}

// 初始化WinSock 2.2
bool CTCPClient::InitSocket()
{
	WSADATA wsaData;
	return (NO_ERROR == WSAStartup(MAKEWORD(2, 2), &wsaData));
}

bool CTCPClient::Connect(std::string ip, int port)
{
	DisConnect();
	m_ip = ip;
	m_port = port;
	// 创建套接字;
	m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_socket == INVALID_SOCKET)
	{
		Global::WriteTextLog(_T("创建套接字失败：%d"), WSAGetLastError());
		return false;
	}

	// 连接服务器;
	m_sin.sin_family = AF_INET;
	m_sin.sin_port = htons(port);
	if (-1 == inet_pton(AF_INET, ip.c_str(), &m_sin.sin_addr))
	{
		Global::WriteTextLog(_T("地址转换失败：%d"), WSAGetLastError());
		return false;
	} 

	if (connect(m_socket, (LPSOCKADDR)& m_sin, sizeof(m_sin)) == SOCKET_ERROR)
	{
		Global::WriteTextLog(_T("连接服务端失败：%d"), WSAGetLastError());
		closesocket(m_socket);
		return false;
	}

	// 设置收发超时;
	int nNetTimeout = 3000; //1秒
	//发送时限
	setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)& nNetTimeout, sizeof(int));
	//接收时限
	setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)& nNetTimeout, sizeof(int));
	
	// 设置缓存大小;
	int bufSize = BUF_LEN;
	setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, (char*)& bufSize, sizeof(int));
	setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, (char*)& bufSize, sizeof(int));

	return true;
}

bool CTCPClient::SelectConnect(std::string ip, int port, int time_out)
{
	DisConnect();
	m_ip = ip;
	m_port = port;
	// 设置收发超时;
	int nNetTimeout = 3000; //1秒

	// 创建套接字;
	m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (m_socket == INVALID_SOCKET)
	{
		Global::WriteTextLog(_T("创建套接字失败：%d"), WSAGetLastError());
		return false;
	}

	// 连接服务器;
	m_sin.sin_family = AF_INET;
	m_sin.sin_port = htons(port);
	if (-1 == inet_pton(AF_INET, ip.c_str(), &m_sin.sin_addr))
	{
		Global::WriteTextLog(_T("地址转换失败：%d"), WSAGetLastError());
		return false;
	}

	// 首先设置socket为非阻塞模式;
	unsigned long um = 1;
	int nRet = ioctlsocket(m_socket, FIONBIO, &um);
	if (nRet != NO_ERROR)
	{
		// 设置失败;
		Global::WriteTextLog(_T("设置非阻塞模式失败：%d"), WSAGetLastError());
		return false;
	}

	bool bRet = false;
	nRet = connect(m_socket, (LPSOCKADDR)& m_sin, sizeof(m_sin));
	if (nRet == NO_ERROR)
	{
		bRet = true;
		goto end;
	}

	// 因为是非阻塞的，这个时候错误码应该是WSAEWOULDBLOCK，Linux下是EINPROGRESS
	if (nRet < 0 && WSAGetLastError() != WSAEWOULDBLOCK)
	{
		bRet = false;
		goto end;
	}

	TIMEVAL Timeout;
	Timeout.tv_sec = time_out;
	Timeout.tv_usec = 0;

	// 检测是否连接成功;
	fd_set write, err;
	FD_ZERO(&write);
	FD_ZERO(&err);
	FD_SET(m_socket, &write);
	FD_SET(m_socket, &err);

#if 0
	nRet = select(0, NULL, &write, &err, &Timeout);
	if (FD_ISSET(m_socket, &write))
	{
		bRet = true;
	}
#else
	nRet = select(0, NULL, &write, &err, &Timeout);
	if (nRet == 0)
	{
		Global::WriteTextLog(_T("连接服务端超时"));
	}
	else if (nRet < 0)
	{
		Global::WriteTextLog(_T("连接服务端出错：%d"), WSAGetLastError());
	}
	else
	{
		int nError = -1;
		int nLen = sizeof(int);
		// 使用getsockopt来检查套接字是否异常;
		getsockopt(m_socket, SOL_SOCKET, SO_ERROR, (char*)& nError, &nLen);
		if (nError == 0)
		{
			bRet = true;
		}
		else
		{
			bRet = false;
			Global::WriteTextLog(_T("连接服务端出错：%d"), nError);
		}
	}
#endif

end:
	um = 0;
	ioctlsocket(m_socket, FIONBIO, &um);
	if (!bRet)
	{
		Global::WriteTextLog(_T("连接服务端失败：%d"), WSAGetLastError());
		closesocket(m_socket);
	}
	else {
		//发送时限
		setsockopt(m_socket, SOL_SOCKET, SO_SNDTIMEO, (char*)& nNetTimeout, sizeof(int));
		//接收时限
		setsockopt(m_socket, SOL_SOCKET, SO_RCVTIMEO, (char*)& nNetTimeout, sizeof(int));
		// 设置缓存大小;
		int bufSize = BUF_LEN;
		setsockopt(m_socket, SOL_SOCKET, SO_SNDBUF, (char*)& bufSize, sizeof(int));
		setsockopt(m_socket, SOL_SOCKET, SO_RCVBUF, (char*)& bufSize, sizeof(int));
	}

	return bRet;
}

void CTCPClient::DisConnect()
{
	if (m_socket != INVALID_SOCKET)
	{
		shutdown(m_socket, SD_BOTH);
		closesocket(m_socket);
	}
}

bool CTCPClient::Send(std::string s_data, std::string &r_data)
{
	static ThreadSection _critSection;
	AutoThreadSection aSection(&_critSection);

	r_data.clear();
	if (m_socket == INVALID_SOCKET)
	{
		Global::WriteTextLog(_T("SOCKET未创建, 请创建并连接服务器"));
		return false;
	}

	// 向服务器发送数据;
	int nRet = send(m_socket, s_data.c_str(), s_data.size(), 0);
	if (nRet < 0 )
	{
		DWORD dwError = WSAGetLastError();
		Global::WriteTextLog(_T("发送数据失败：%d"), dwError);
		ReConnect(dwError);
		return false;
	}

	// 接收服务器返回的数据;
	char recbuf[64] = { 0 };
	for (int i = 0; i < 10; i++ )
	{
		memset(recbuf, 0, 64);
		nRet = recv(m_socket, recbuf, 64, 0);
		if (nRet < 0)
		{
			DWORD dwError = WSAGetLastError();
			Global::WriteTextLog(_T("接收数据失败：%d"), dwError);
			ReConnect(dwError);
			return false;
		}

		// 存储结果;
		if ( nRet > 0)		
			r_data.append(recbuf, nRet);

		if ( checkEOM(r_data) || !_tcsicmp(r_data.c_str(), _T("OK\n")) )
			break;
	}

	return true;
}

void CTCPClient::ReConnect(DWORD dwError)
{
	bool bReConnect = false;
	// send/recv error;
	if (dwError == WSAENETRESET)
	{
		bReConnect = true;
		Global::WriteTextLog(_T("由于保持活动活动在操作正在进行时检测到故障，因此连接已断开。"));
	}
	else if (dwError == WSAECONNABORTED)
	{
		bReConnect = true;
		Global::WriteTextLog(_T("由于超时或其他故障，虚电路终止。应用程序应关闭套接字，因为它不再可用。"));
	}
	else if (dwError == WSAECONNRESET)
	{
		bReConnect = true;		
		Global::WriteTextLog(_T("虚拟电路由远程端执行硬关闭或中止关闭重置。对于UDP套接字，远程主机无法传送先前发送的UDP数据报，并使用“端口无法访问”ICMP数据包进行响应。应用程序应关闭套接字，因为它不再可用。"));
	}
	else if (dwError == WSAETIMEDOUT)
	{//由于网络故障或另一端的系统在没有通知的情况下发生故障，连接已被丢弃。
		bReConnect = true;
		Global::WriteTextLog(_T("由于超时或其他故障，虚电路终止。应用程序应关闭套接字，因为它不再可用。"));
	}

	if ( bReConnect )
	{
		DisConnect();
		Connect(m_ip, m_port);
	}
}

bool CTCPClient::checkEOM(std::string& data)
{
	if (data.size() < 5)
		return false;

	if (_tcsicmp("{\n", data.substr(0, 2).c_str()) != 0)
		return false;

	if (_tcsicmp("\n}\n", data.substr(data.size() - 3, 3).c_str()) != 0)
		return false;

	// 并返回去掉花括号的内容;
	//data = data.substr(2, data.size() - 5);

	return true;
}
