跨平台网络编程

Posted JohnKing_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了跨平台网络编程相关的知识,希望对你有一定的参考价值。

  程序员也三年了,工作以来,一直都在写游戏逻辑,刚好最近闲下来,难得有时间,自己研究一些新的东西。因此赶紧看看关于网络方面的东西,总结一下,好记心不如烂键盘。

  其实总的来说,网络也就那么回事,比我原本想像中要简单很多,只有简单的几个接口。

  一开始关于大数据,我还想复杂了,原本以为需要自己分包,再合包,而且我一开始也是这么做的,都已经做好了,再问别人才发现,这是没有必要的。(伤心!!) 

  网络就是一个数据流,发送的时候,可以分多次send,接收时,也可以多次recv , 因为有缓存机制。

  首先,需要做的只是给每条消息加上消息头,定义好包的大小及消息类型等其他业务相关的数据。

  第二步, 发送消息时,先加上包头,后面跟数据。

  第三步,接收消息时,先接收包头,再决定后面接收多大的数据。

 

以下直接贴代码:

客户端:

SocketClient.h

#ifndef __NET_SOCKET_CLIENT_H__
#define __NET_SOCKET_CLIENT_H__

#ifdef WIN32
#include <WinSock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#include <unistd.h>

#ifndef typedef_socket_
#define typedef_socket_
typedef int SOCKET;
#endif
//typedef unsigned int SOCKET;
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
#endif

#include "ByteArray.h"
#include <thread>
#include <mutex>
#include <list>
#include <string>


#define  MsgPack_ptr std::shared_ptr<MsgPack>

class SocketClient {
private:
	SOCKET m_sock;
	std::shared_ptr<std::thread> m_thread;
	bool m_connected;
	std::mutex m_mutex;
	std::list<MsgPack*> m_requests;
	std::list<MsgPack_ptr> m_responseList;
	bool	m_running;
	std::string	m_callback;
	int32_t		m_recvTimeout;
public:
	SocketClient();
	~SocketClient();
	bool	_connect_timeout(std::string ip, unsigned short port, int timeout);
	bool	_connect_block(std::string ip, unsigned short port);
	bool	connect(std::string ip, unsigned short port, int timeout);
	int		send(const char* buf, int len);
	int		recv(char* buf, int len);
	int		close();
	void	startRecvThread();
	bool	isConnected() const { return m_connected; }
	void	setRecvTimeout(int32_t ms){ m_recvTimeout = ms; }

	bool sendRequest(MsgPack* reqest);
	MsgPack* recvMessage();

	std::list<MsgPack_ptr>&	getResponseList() { return m_responseList; }
	void lockMutex(); 
	void unlockMutex();

protected:
	void runRecv();
};


#endif

  

SocketClient.cpp

#include "SocketClient.h"
#include <iostream>
#include "bytearray.h"
#include "msg.h"
//#include "../Utils/Utils.h"
#include "cocos2d.h"
USING_NS_CC;
#define NETLOG CCLOG

#ifdef WIN32
#pragma comment(lib, "wsock32")
class SocketIniter {
public:
	SocketIniter() {
		WSADATA wsaData;
		WORD version = MAKEWORD(2, 0);
		int rt = WSAStartup(version, &wsaData);
		if (rt) {
			std::cerr << "Initilize winsock error !" << std::endl;
		}
	}

	~SocketIniter() {
		WSACleanup();
	}
};

static SocketIniter s_socketIniter;
#endif


SocketClient::SocketClient() {
	m_connected = false;
	m_running = true;
	m_recvTimeout = 5;
}

SocketClient::~SocketClient() {
	close();
	m_running = false;
}

int SocketClient::close() {
	m_connected = false;
	::shutdown(m_sock, 2);
#ifdef WIN32
	::closesocket(m_sock);
#else
	::close(m_sock);
#endif

	m_mutex.lock();
//	for (auto i : m_responseList)
//		delete i;
	m_responseList.clear();
	m_mutex.unlock();

	return 0;
}

bool SocketClient::_connect_timeout(std::string ip, unsigned short port, int timeout)
{
	if (m_connected) {
		return true;
	}

	unsigned  long  non_blocking = 1;
	unsigned  long  blocking = 0;
	bool ret = false;

	int tryTimes = timeout / 2;
	for (int i = 0; i<tryTimes; ++i)
	{
		close();

		m_sock = socket(AF_INET, SOCK_STREAM, 0);

		int rcv_size = 1024 * 1024;
		setsockopt(m_sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcv_size, sizeof(int));

		bool bKeppAlive = true;
		setsockopt(m_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeppAlive, sizeof(bKeppAlive));

#ifdef WIN32
		if (-1 == ioctlsocket(m_sock, FIONBIO, (unsigned long*)&non_blocking))
			ret = false;
#else
		ioctl(m_sock, FIONBIO, &non_blocking);
#endif

		struct sockaddr_in sock_addr;
		sock_addr.sin_family = AF_INET;
		sock_addr.sin_addr.s_addr = inet_addr(ip.c_str());
		sock_addr.sin_port = htons(port);
		int rt = ::connect(m_sock, (struct sockaddr*)&sock_addr, sizeof(sock_addr));
		if (rt != 0) {
			struct timeval tv = { 2, 0 };
			fd_set writefds;
			FD_ZERO(&writefds);
			FD_SET(m_sock, &writefds);
			if (select(m_sock + 1, 0, &writefds, 0, &tv) > 0) {
#ifdef WIN32
				int error;
				int err_len = sizeof(error);
				getsockopt(m_sock, SOL_SOCKET, SO_ERROR, (char*)&error, &err_len);
#else
				int error;
				socklen_t err_len = sizeof(error);
				getsockopt(m_sock, SOL_SOCKET, SO_ERROR, &error, &err_len);
#endif
				if (error == 0) {
					ret = true;
				}
				else {
					ret = false;
				}
			}
			else {
				ret = false;
			}
		}
		else {
			ret = true;
		}
		if (ret)
			break;
	}

	//if (!ret)
	//    return false;

#ifdef WIN32
	ioctlsocket(m_sock, FIONBIO, (unsigned long*)&blocking);
#else
	ioctl(m_sock, FIONBIO, &blocking);
#endif	

#if(CC_TARGET_PLATFORM==CC_PLATFORM_WIN32)
	DWORD tv2 = m_recvTimeout * 1000;
#else
	struct timeval tv2 = { m_recvTimeout, 0 };
#endif
	//接收不设置超时
	//setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv2, sizeof(tv2));

	m_connected = ret;
	m_requests.clear();

	if (ret)
		startRecvThread();

	MsgPack* msg = new MsgPack(this->isConnected() ? 1 : 2);//conn return
	if (msg) {
		NETLOG("connect msg: %d", msg->getMsgID());
		m_mutex.lock();
		m_responseList.push_back(MsgPack_ptr(msg));
		m_mutex.unlock();
	}
	/*
	cocos2d::Director::getInstance()->getScheduler()->performFunctionInCocosThread([&, this]{
	std::string sout;
	g_pLuaSystem->callLuaFunc(this->m_callback, this->isConnected()?1:0, sout);
	});
	*/

	//int ret = CCLuaEngine::defaultEngine()->getLuaStack()->executeFunctionByHandler(_nStartReconnectHandler, 0);

	return ret;

}

bool SocketClient::connect(std::string ip, unsigned short port, int timeout)
{
	auto t = std::thread(&SocketClient::_connect_timeout, this, ip, port, timeout);
	t.detach();
	return true;
}

bool SocketClient::_connect_block(std::string ip, unsigned short port) {
	//std::lock_guard<std::mutex> lock(m_mutex);
	if (m_connected) {
		return true;
	}
	close();// add close
	m_mutex.lock();
//	for (auto i : m_responseList)
//		delete i;
	m_responseList.clear();
	m_mutex.unlock();

	m_sock = socket(AF_INET, SOCK_STREAM, 0);
	{
		/*
		#if(CC_TARGET_PLATFORM==CC_PLATFORM_WIN32)
		DWORD tv = 10000;
		DWORD tv2 = 10000;
		#else
		struct timeval tv = {10,0};
		struct timeval tv2 = {10,0};
		#endif
		setsockopt(m_sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&tv, sizeof(tv));
		setsockopt(m_sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&tv2, sizeof(tv2));
		*/

		bool bKeppAlive = true;
		setsockopt(m_sock, SOL_SOCKET, SO_KEEPALIVE, (char*)&bKeppAlive, sizeof(bKeppAlive));

		int rcv_size0;
		int optLen = sizeof(rcv_size0);
		//getsockopt(m_sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcv_size0, &optLen);
		//NETLOG("socket-recv-buffer= %d %d", rcv_size0, optLen);

		int rcv_size = 1024 * 1024;
		setsockopt(m_sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcv_size, sizeof(int));

		//getsockopt(m_sock, SOL_SOCKET, SO_RCVBUF, (char*)&rcv_size0, &optLen);
		//NETLOG("socket-recv-buffer= %d %d", rcv_size0, optLen);

	}

	struct sockaddr_in svraddr;
	svraddr.sin_family = AF_INET;
	svraddr.sin_addr.s_addr = inet_addr(ip.c_str());
	svraddr.sin_port = htons(port);
	int rt = ::connect(m_sock, (struct sockaddr*)&svraddr, sizeof(svraddr));
	if (rt == SOCKET_ERROR) {
		return false;
	}
	m_requests.clear();
	m_connected = true;

	startRecvThread();

	return true;
}
/*
static const std::string key = "P%2BViyZLtO^gRT2";
bool encryptByAES(std::string in, std::string& out, bool isEncrypt) {
std::string iv;
iv.resize(AES_BLOCK_SIZE, 0);
AES_KEY aesKey;
int enc(AES_ENCRYPT);
if (isEncrypt) {
AES_set_encrypt_key((const unsigned char*)key.c_str(),
AES_BLOCK_SIZE * 8, &aesKey);
} else {
AES_set_decrypt_key((const unsigned char*)key.c_str(),
AES_BLOCK_SIZE * 8, &aesKey);
enc = AES_DECRYPT;
}
short inLen = in.length();
short block = (inLen + AES_BLOCK_SIZE - 1) / AES_BLOCK_SIZE;
in.resize(AES_BLOCK_SIZE * block);
out.resize(inLen);
short begin = 0;
try {
while (true) {
int len = inLen - begin >= AES_BLOCK_SIZE ?
AES_BLOCK_SIZE : inLen - begin;
AES_cbc_encrypt((unsigned char*)&inStr[begin], (unsigned char*)&outStr[begin], AES_BLOCK_SIZE, &aesKey,
(unsigned char*)&iv[0], enc);
begin += AES_BLOCK_SIZE;
if (begin >= inLen) {
break;
}
}
} catch (...) {
SYLAR_LOG_ERROR(g_logger) << "Bad aes buffer";
return false;
}
out.resize(inLen);
return true;
}*/

int SocketClient::send(const char* buf, int len){
	if (!m_connected) {
		return -1;
	}
	int bytes = 0;
	int count = 0;
	while (count < len) {
		bytes = ::send(m_sock, buf + count, len - count, 0);
		if (bytes == -1 || bytes == 0) {
			m_connected = false;
			NETLOG("socket send false");
			return -1;
		}
		count += bytes;
	}
	return count;
}

int SocketClient::recv(char* buf, int len) {
	if (!m_connected) {
		return -1;
	}
	return ::recv(m_sock, buf, len, MSG_WAITALL);
/*	
	int bytes = 0;
	int count = 0;
	while (count < len) {
		bytes = ::recv(m_sock, buf + count, len - count, MSG_WAITALL);
		if (bytes < 0) {
			m_connected = false;
			NETLOG("socket recv false");
			return count;
		}
		count += bytes;
	}
	return count;
	*/
	
}

void SocketClient::startRecvThread() {
	if (m_thread) {
		//CCLOG("Recv Thread is Running");
		return;
	}
	m_thread.reset(new std::thread(&SocketClient::runRecv, this));
	if (!m_thread->joinable())
		m_thread->join();
}

bool SocketClient::sendRequest(MsgPack* reqest)
{
	MsgPack* buf = reqest;
	int len = buf->getLen();

	return send(buf->getData(), len) > 0;
}

//char recbuff[4096];

MsgPack* SocketClient::recvMessage()
{
	MsgHeader header;

	if (recv((char *)&header, sizeof(MsgHeader)) <= 0)
		return 0;

	MsgPack* buff = new MsgPack(header);
	int lenLeft = recv(buff->getData(), header.nPkgSize);
	NETLOG("-----recv-end   = %d", lenLeft);
	if (lenLeft <= 0)
	{
		delete buff;
		return 0;
	}
	//msg read begin
	buff->setCur(0);
	return buff;

}

void SocketClient::runRecv() {
	while (m_running) {
#ifdef WIN32
		Sleep(1);
#else
		usleep(1);
#endif

		try {
			while (m_connected)
			{
				MsgPack* msg = recvMessage();
				if (msg) {
					NETLOG("is in runRecv msgid:%d msgLen:%d ", msg->getMsgID(), msg->getMax());
					MsgHeader &header = msg->getHeader();
					if (header.nPkgTotall > 1)
					{
						NETLOG("%d / %d", header.nPkgIndex, header.nPkgTotall);
					}
					m_mutex.lock();
					m_responseList.push_back(MsgPack_ptr(msg));
					m_mutex.unlock();
				}
			}
		}
		catch (std::bad_alloc& e) {
			NETLOG("recv-exception %s", e.what());
		}
		catch (...) {
			NETLOG("recv-exception unkown.");
		}
	}
	NETLOG("exit thread runRecv.");
}


void SocketClient::lockMutex()
{
	m_mutex.lock();
}
void SocketClient::unlockMutex()
{
	m_mutex.unlock();
}

  以下我的定义的消息头,当然其实,有的东西其实是没必要的

typedef struct
{
	int32_t nId; //消息ID
	int32_t nPkgTotall; //总包数
	int32_t nPkgIndex;  //当前包
	int32_t nPkgSize; //当前包内容大小
} MsgHeader;

MsgHeader* createMsgHeader(int32_t msgId, int32_t pkgTotal, int32_t pkgIndex, int32_t pkgSz);

  

 

服务端:

NetServer.h

#pragma once
//服务器

#define _MAX_HEAD 64				//最大消息头大小
#define _MAX_MSGSIZE 8 * 1024		// 暂定一个消息最大为16k
#define BLOCKSECONDS	3			// INIT函数阻塞时间
#define INBUFSIZE	(64*1024)		//?	具体尺寸根据剖面报告调整  接收数据的缓存
#define OUTBUFSIZE	(4*1024-64)		//? 具体尺寸根据剖面报告调整。 发送数据的缓存,当不超过8K时,FLUSH只需要SEND一次

//Server.cpp
#include <iostream>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")
DWORD WINAPI ClientThread(LPVOID lpParameter);
DWORD WINAPI server_main(PVOID pParam);
void CALLBACK startServer(HWND hWnd, UINT nMsg, UINT nTimerid, DWORD dwTime);

bool SendMsg(int msgId ,void* pBuf, int nSize, SOCKET &m_sockClient);

 NetServer.cpp

//Server.cpp
#include "stdafx.h"
#include "Server.h"
#include "WinServerDlg.h"
#include <fstream>
#include <vector>
#include "msg.h"
#include "Business.h"
using namespace std;

#define PORT 9001
#define IP_ADDRESS "127.0.0.1"

bool GetLocalIP(char* ip)
{
	//1.初始化wsa
	WSADATA wsaData;
	int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (ret != 0)
	{
		return false;
	}
	//2.获取主机名
	char hostname[256];
	ret = gethostname(hostname, sizeof(hostname));
	if (ret == SOCKET_ERROR)
	{
		return false;
	}
	//3.获取主机ip
	HOSTENT* host = gethostbyname(hostname);
	if (host == NULL)
	{
		return false;
	}
	//4.转化为char*并拷贝返回
	strcpy(ip, inet_ntoa(*(in_addr*)*host->h_addr_list));
	return true;
}


DWORD WINAPI ClientThread(LPVOID lpParameter)
{
	SOCKET CientSocket = (SOCKET)lpParameter;
	int Ret = 0;
	char RecvBuffer[_MAX_MSGSIZE];
	int headLen = sizeof(MsgHeader);

	//包队列,出现多包时,合包用
	MsgBigCache* vecMsg[100];
	memset(vecMsg, 0, sizeof(vecMsg));

	while (true)
	{
		memset(RecvBuffer, 0x00, sizeof(RecvBuffer));
		Ret = recv(CientSocket, RecvBuffer, _MAX_MSGSIZE, 0);
		if (Ret == 0 || Ret == SOCKET_ERROR)
		{
			Log("客户端退出!");
			break;
		}

		MsgHeader *header = reinterpret_cast<MsgHeader*> (RecvBuffer);
		if (header->nPkgTotall > 1)
		{
			CString str;
			str.Format("此消息共%d ,当前%d", header->nPkgTotall, header->nPkgIndex);
			Log(str);
		}
//		Log("接收到客户信息为:%s", RecvBuffer+headLen);
		if (1 == header->nPkgTotall)
		{
			progressMsg(RecvBuffer + headLen, CientSocket, header);
		}
		else if (header->nPkgTotall > 1000 || header->nPkgTotall < 0)
		{
			return 0;
		}
		else
		{
			MsgBigCache *msg = new MsgBigCache();
			char *buff = new char[header->nPkgSize];
			memset(buff, 0, header->nPkgSize);
			memcpy(buff, RecvBuffer + headLen, header->nPkgSize);
			msg->header = *header;
			msg->buff = buff;
			vecMsg[header->nPkgIndex] = msg;
			
			bool allRec = true;
			for (int i = 1; i <= header->nPkgTotall; i++)
			{
				if (!vecMsg[i])
				{
					allRec = false;
					break;
				}
			}

			if (allRec)
			{
				ofstream f("1.png" , ios_base::binary);
				for (int i = 1; i <= header->nPkgTotall; i++)
				{
					f.write((char *)vecMsg[i]->buff, vecMsg[i]->header.nPkgSize);
					delete vecMsg[i]->buff;
					delete vecMsg[i];
				}
				memset(vecMsg,0,sizeof(vecMsg));
			}
		}
	}

	return 0;
}

DWORD WINAPI server_main(PVOID pParam)
{
	WSADATA  Ws;
	SOCKET ServerSocket, ClientSocket;
	struct sockaddr_in LocalAddr, ClientAddr;
	int Ret = 0;
	int AddrLen = 0;
	HANDLE hThread = NULL;
	
	char buffIp[128];
	char buffPort[20];
	ifstream f("config.ini");
	if (f.fail())
	{
		GetLocalIP(buffIp);
		strcpy(buffPort, "9001");
	}
	else
	{
		f.getline(buffIp, sizeof(buffIp));
		f.getline(buffPort, sizeof(buffPort));
	}
	int port = atoi(buffPort);
	//Init Windows Socket
	if (WSAStartup(MAKEWORD(2, 2), &Ws) != 0)
	{
		Log("Init Windows Socket Failed::%d" , GetLastError());
		return -1;
	}

	//Create Socket
	ServerSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (ServerSocket == INVALID_SOCKET)
	{
		Log ("Create Socket Failed::%d" ,GetLastError());
		return -1;
	}

	LocalAddr.sin_family = AF_INET;
	LocalAddr.sin_addr.s_addr = inet_addr(buffIp);
	LocalAddr.sin_port = htons(port);
	memset(LocalAddr.sin_zero, 0x00, 8);

	//Bind Socket
	Ret = ::bind(ServerSocket, (struct sockaddr*)&LocalAddr, sizeof(LocalAddr));
	if (Ret != 0)
	{
		Log("Bind Socket Failed::%d", GetLastError());
		return -1;
	}
	//listen
	Ret = listen(ServerSocket, 10);
	if (Ret != 0)
	{
		Log("listen Socket Failed::", GetLastError());
		return -1;
	}

	Log("服务端启动成功!");
	Log(CString("ip:") + buffIp);
	Log(CString("port:") + buffPort);

	while (true)
	{
		AddrLen = sizeof(ClientAddr);
		ClientSocket = accept(ServerSocket, (struct sockaddr*)&ClientAddr, &AddrLen);
		if (ClientSocket == INVALID_SOCKET)
		{
			Log ("Accept Failed::%d" , GetLastError());
			break;
		}
		CString str;
		str.Format("客户端连接:: %s : %d", inet_ntoa(ClientAddr.sin_addr), ClientAddr.sin_port);
		Log(str);
		sendConnSuc(ClientSocket);

		hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)ClientSocket, 0, NULL);
		if (hThread == NULL)
		{
			Log("Create Thread Failed!");
			break;
		}

		CloseHandle(hThread);
	}

	closesocket(ServerSocket);
	closesocket(ClientSocket);
	WSACleanup();

	return 0;
}

bool g_start = false;


void CALLBACK startServer(HWND hWnd, UINT nMsg, UINT nTimerid, DWORD dwTime)
{
	if (g_start)
		return;
//	server_main(0);
	auto hThread = CreateThread(NULL, 0, server_main, 0, 0, NULL);
//	if (hThread == NULL)
//	{
//		Log("Create Thread Failed!");
//	}
//	else
		g_start = true;
	KillTimer(hWnd, nTimerid);
}

////////////////////////////////////////发消息给客户端

bool hasError()
{
#ifdef WIN32
	int err = WSAGetLastError();
	if (err != WSAEWOULDBLOCK) {
#else
	int err = errno;
	if (err != EINPROGRESS && err != EAGAIN) {
#endif
		return true;
	}

	return false;
}

char m_bufOutput[_MAX_MSGSIZE];
int m_nOutbufLen;
bool SendMsg(int msgid, void* pBuf, int nSize, SOCKET &m_sockClient)
{
	if (pBuf == 0 || nSize <= 0) {
		return false;
	}

	if (m_sockClient == INVALID_SOCKET) {
		return false;
	}
	char *contentBuff = (char *)pBuf;
	MsgHeader header;
	header.nId = msgid;
	header.nPkgIndex = 1;
	header.nPkgTotall = 1;// ceil(1.0 * nSize / OUTBUFSIZE);
	int headLen = sizeof(header);

	header.nPkgSize = nSize;
	m_nOutbufLen = nSize + headLen;
	CString str;
	str.Format("回复消息%d !大小 %d 个字节!", header.nId, header.nPkgSize);
	Log(str);
	
	// 发送一段数据
	int	outsize;
	outsize = send(m_sockClient, (char*)&header, headLen, 0);
	if (outsize <= 0)
	{
		if (hasError())
		{
			Log("发送失败,中断!");
			return false;
		}
	}
	outsize = send(m_sockClient, (char*)pBuf, nSize, 0);
	if (outsize <= 0)
	{
		if (hasError())
		{
			Log("发送失败,中断!");
			return false;
		}
	}

	return true;
}

  

以上是关于跨平台网络编程的主要内容,如果未能解决你的问题,请参考以下文章

蓝懿IOS网络服务器与客户端

网络编程

acl 是一个跨平台的网络通信库及服务器编程框架

移动平台游戏网络重连方案(转)

新开源报道 52苹果开源跨平台低阶非阻塞网络应用框架 SwiftNIO

一个非常优秀的跨平台物联网开发常用的网络请求库libcurl