1. 程式人生 > >Windows下TCP程式設計的一些工具函式——應付大部分的Windows TPC程式設計場景。

Windows下TCP程式設計的一些工具函式——應付大部分的Windows TPC程式設計場景。

首先是這些函式的定義:

#define INTERNAL_LOG_ON  0
#define INTERNAL_LOG_OFF 1

#define INTERNAL_LOG_MODE INTERNAL_LOG_OFF //or  INTERNAL_LOG_ON
//#define INTERNAL_LOG_MODE INTERNAL_LOG_ON //or INTERNAL_LOG_OFF

namespace WinTCP
{

void internalLog(const char* format, ...);

//windows 下載入元件
bool initWSADATA();

//windows 下解除安裝元件
void uninitWSADATA();

//建立TCP套接字
//return: socket_fd
int createWinTCPSocket();

//關閉套接字
void closeWinTCPSocket(int socket_fd);

//伺服器繫結
//socket_fd 為伺服器套接字fd
//port 為伺服器要繫結的埠號
//return 返回繫結成功與否
bool hostBind(int socket_fd, int port);


//伺服器監聽
//socket_fd 為服務端套接字
//return 返回客戶端套接字fd
int hostListen(int socket_fd);


//連線服務端
//socket_fd 為客戶端fd
//addr 為服務端的ip地址
//addr 為所連服務端的埠
//return : 成功或者失敗
bool connectToHost(int socket_fd, int addr, int port);

//讀取資料
//socket_fd 為所要讀取的socket_fd
//data 為緩衝區
//size 為緩衝區大小
//timeout 為超時時間(msec)
//try_count 為失敗嘗試的次數
//return 實際讀取到的資料長度
int readData(int socket_fd, char* data, int size, int timeout, int try_count = 2);

//寫入資料
//socket_fd 為所要寫入的socket_fd
//data 為緩衝區
//size 為緩衝區大小
//timeout 為超時時間(msec)
//try_count 為失敗嘗試的次數
//return 實際讀寫入的資料長度
int writeData(int socket_fd, char* data, int size, int timeout, int try_count = 2);

}

然後是這些函式的實現:


#include <stdio.h>
#include <stdarg.h>
#include <WinSock2.h>

#pragma comment(lib,"ws2_32.lib")

namespace WinTCP
{

static bool hasInitWSADATA = false;

void internalLog(const char* format, ...)
{
	if(INTERNAL_LOG_MODE == INTERNAL_LOG_OFF)
		return;


	va_list argList;
	va_start(argList, format);
	vfprintf(stdout, format, argList);
	va_end(argList);
	
}

bool initWSADATA()
{
	
	if(!hasInitWSADATA)
	{
		WSADATA wsa_data;

		hasInitWSADATA = ::WSAStartup(MAKEWORD(2,2), &wsa_data) == 0;
		if(!hasInitWSADATA)
		{
			int errorCode = ::WSAGetLastError();
			internalLog("initWSADATA: hasInit(%d) errorCode(%d).\n", hasInitWSADATA, errorCode);
		}
	}
	return hasInitWSADATA;
}

void uninitWSADATA()
{
	hasInitWSADATA = false;
	::WSACleanup();
}

int createWinTCPSocket()
{
	SOCKET socket_fd;
	socket_fd = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	

	return socket_fd;
}

void closeWinTCPSocket(int socket_fd)
{
	if(0>::closesocket(socket_fd))
	{
		int errorCode = ::WSAGetLastError();
		internalLog("closeWinTCPSocket: error(%d).\n", errorCode);
	}
}

bool hostBind(int socket_fd, int port)
{
	SOCKADDR_IN server_addr; //伺服器配置

	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port); //埠號範圍: 0 ~65535
	server_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //INADDR_ANY意思是接受所有IP的連線
	if(0>::bind(socket_fd,(SOCKADDR *)&server_addr,sizeof(SOCKADDR))) //繫結ip域socket,出錯返回SOCKET_ERROR
	{
		int errorCode = ::WSAGetLastError();
		internalLog("hostBind: fd(%d) errorCode(%d).\n", socket_fd, errorCode);

		return false;
	}
	internalLog("hostBind: fd(%d) port(%d).\n", socket_fd, port);
	return true;
}

int hostListen(int socket_fd)
{
	if(0 > ::listen(socket_fd, 5))
	{
		int errorCode = ::WSAGetLastError();
		internalLog("hostListen: fd(%d) listen error(%d).\n", socket_fd, errorCode);
	}

	SOCKADDR_IN client_addr; //儲存客戶端資訊
	SOCKET fd_client; //客戶端套接字
	int addr_len = sizeof(SOCKADDR);
	fd_client = ::accept(socket_fd,(SOCKADDR *)&client_addr,&addr_len);

	if(0>= fd_client)
	{
		int errorCode = ::WSAGetLastError();
		internalLog("hostListen: fd(%d) accept error(%d).\n", socket_fd, errorCode);
	}


	internalLog("hostListen: fd(%d) accept client(%d).\n", socket_fd, fd_client);
	return fd_client;
}

bool connectToHost(int socket_fd, int addr, int port)
{
	SOCKADDR_IN server_addr;

	server_addr.sin_family = AF_INET; //Internet協議

	server_addr.sin_port = htons(port);

	server_addr.sin_addr.S_un.S_addr = addr;//inet_addr("127.0.0.1");

	if(0>::connect(socket_fd,(SOCKADDR *)&server_addr,sizeof(SOCKADDR))) //連線失敗則返回SOCKET_ERROR
	{
		int errorCode = ::WSAGetLastError();
		internalLog("connectToHost: fd(%d) errorCode(%d).\n", socket_fd, errorCode);
		return false;
	}
	internalLog("connectToHost: fd(%d) addr(%d) port(%d).\n", socket_fd, addr, port);

	return true;
}

int readData(int socket_fd, char* data, int size, int timeout, int try_count /*= 2*/)
{
	timeout *= 1000;

	FD_SET read_fds;
	FD_SET except_fds;
	timeval t = {0, timeout};
	int recv_size = 0;
	u_long blacked_on  = 0;
	u_long blacked_off = 1;

	if(0>::ioctlsocket(socket_fd, FIONBIO, &blacked_off))
	{
		int errorCode = ::WSAGetLastError();
		internalLog("readData: fd(%d) set socket to blacked_off error(%d).\n", socket_fd, errorCode);
		return recv_size;
	}

	for(int i = 0; i < try_count; i++)
	{
		internalLog("readData: fd(%d) try(%d/%d) read data...\n", socket_fd, i, try_count);

		FD_ZERO(&read_fds);
		FD_ZERO(&except_fds);
		FD_SET(socket_fd, &read_fds);
		FD_SET(socket_fd, &except_fds);

		int res = ::select(0, &read_fds, 0, &except_fds, &t); //windows 中nfds沒有意義
		if(res < 0)
		{
			int errorCode = ::WSAGetLastError();
			internalLog("readData: fd(%d) select error(%d).\n", socket_fd, errorCode);
		}
		else if(res == 0)
		{
			internalLog("readData: fd(%d) timeout...\n", socket_fd);
		}
		else
		{
			if(FD_ISSET(socket_fd, &except_fds))
			{
				int errorCode = ::WSAGetLastError();
				internalLog("readData: fd(%d) exception(%d).\n", socket_fd, errorCode);
			}
			else if(FD_ISSET(socket_fd, &read_fds))
			{
				int ret = ::recv(socket_fd, data + recv_size, size - recv_size, 0);
				if(ret > 0)
				{
					recv_size += ret;
				}
			}
		}

		if(recv_size == size) //*讀取到夠了的資料,返回
			break;
	}


	if(0>::ioctlsocket(socket_fd, FIONBIO, &blacked_on))
	{
		int errorCode = ::WSAGetLastError();
		internalLog("readData: fd(%d) set socket to blacked_on error(%d).\n", socket_fd, errorCode);
		return recv_size;
	}

	return recv_size;
}

int writeData(int socket_fd, char* data, int size, int timeout, int try_count /*= 2*/)
{
	FD_SET write_fds;
	FD_SET except_fds;
	timeval t = {0, timeout};
	int write_size = 0;
	u_long blacked_on  = 0;
	u_long blacked_off = 1;

	if(0>::ioctlsocket(socket_fd, FIONBIO, &blacked_off))
	{
		int errorCode = ::WSAGetLastError();
		internalLog("writeData: fd(%d) set socket to blacked_off error(%d).\n", socket_fd, errorCode);
		return write_size;
	}

	for(int i = 0; i < try_count; i++)
	{
		internalLog("writeData: fd(%d) try(%d/%d) write data...\n", socket_fd, i, try_count);

		FD_ZERO(&write_fds);
		FD_ZERO(&except_fds);
		FD_SET(socket_fd, &write_fds);
		FD_SET(socket_fd, &except_fds);

		int res = ::select(0, 0, &write_fds, &except_fds, &t); //windows 中nfds沒有意義
		if(res < 0)
		{
			int errorCode = ::WSAGetLastError();
			internalLog("writeData: fd(%d) select error(%d).\n", socket_fd, errorCode);
		}
		else if(res == 0)
		{
			internalLog("writeData: fd(%d) timeout...\n", socket_fd);
		}
		else
		{
			if(FD_ISSET(socket_fd, &except_fds))
			{
				int errorCode = ::WSAGetLastError();
				internalLog("writeData: fd(%d) exception(%d).\n", socket_fd, errorCode);
			}
			else if(FD_ISSET(socket_fd, &write_fds))
			{
				int ret = ::send(socket_fd, data + write_size, size - write_size, 0);
				if(ret > 0)
				{
					write_size += ret;
				}
			}
		}

		if(write_size == size) //*讀取到夠了的資料,返回
			break;
	}


	if(0>::ioctlsocket(socket_fd, FIONBIO, &blacked_on))
	{
		int errorCode = ::WSAGetLastError();
		internalLog("writeData: fd(%d) set socket to blacked_on error(%d).\n", socket_fd, errorCode);
		return write_size;
	}

	return write_size;
}

}

使用這些函式能應付幾乎全部的Windows TPC程式設計場景。