使用Select实现C/S通讯之服务端

// ServerBySelect_Test1.cpp : 定义控制台应用程序的入口点。
//

#include “stdafx.h”
#include < WinSock2.h>
#include < iostream>
#include < stdio.h>
#include < queue>
#include < mutex>
using namespace std;

#pragma comment(lib,“ws2_32.lib”)

//FD_SETSIZE是在winsocket2.h头文件里定义的,这里windows默认最大为64
//在包含winsocket2.h头文件前使用宏定义可以修改这个值

const int nMessageWordMaxSize = 1023; //发送消息的文字信息最大的字节数
//TCP消息封装结构
struct TCPMessage
{
bool bIsHeartBeat; //是否为心跳报文
char chMessage[nMessageWordMaxSize]; //发送的文字信息(这里为定长的大小) //如果不要设置定长,则需要使用流模式去传递!!!

TCPMessage()
{
	bIsHeartBeat = true;
	memset(chMessage, 0, nMessageWordMaxSize);
}

};

const int nSize = sizeof(char) * sizeof(TCPMessage);

#define PORT 8888
#define SRV_IP “192.168.15.112”

int g_nSockConn = 0;//请求连接的数目

struct ClientInfo
{
SOCKET sockClient;
SOCKADDR_IN addrClient;
};

ClientInfo g_Client[FD_SETSIZE];

//发送消息给客户端的消息队列
std::queue m_qeSendMessage;
std::mutex mutex_sendMsg;

void ReceiveInput(VOID)
{
char input[nMessageWordMaxSize] = { 0 };
while (true)
{
cin.getline(input, nMessageWordMaxSize); //字符串的“空格”也会获取!!
if (input[0] != ‘\0’)
{
//mutex_sendMsg.lock();
m_qeSendMessage.push(input);
//mutex_sendMsg.unlock();
memset(input, 0, nMessageWordMaxSize);
}

	//Sleep(1000);
}

}

//发心跳报文
void SendHeartMsg(char* recvData, int i, fd_set& fdTemp, fd_set& fdRead)
{
TCPMessage* tcpMessage = (TCPMessage*)recvData;
memset(tcpMessage->chMessage, 0, nMessageWordMaxSize);
tcpMessage->bIsHeartBeat = true;
strcpy_s(tcpMessage->chMessage, 7, “Hello!”);

int nSendLength = 0;
while (nSendLength != nSize)
{
	int curLength = 0;
	curLength = send(g_Client[i].sockClient, recvData, nSize, 0);
	if (curLength <= 0 || (curLength == SOCKET_ERROR))
	{
		cout << "Client " << inet_ntoa(g_Client[i].addrClient.sin_addr) << "closed" << endl;
		//如果关闭相关的socket,则必须clear掉相关socket
		FD_CLR(g_Client[i].sockClient, &fdTemp);
		FD_CLR(g_Client[i].sockClient, &fdRead);
		closesocket(g_Client[i].sockClient);

		if (i < g_nSockConn - 1)
		{
			//将失效的sockClient剔除,用数组的最后一个补上去
			g_Client[i--].sockClient = g_Client[--g_nSockConn].sockClient;
		}
		else
		{
			g_Client[i].sockClient = 0;
			g_nSockConn--;
		}

		break;
	}

	nSendLength += curLength;
}

}

//发送自己的消息给客户端
void SendMsgToClient(char* recvData, int i, fd_set& fdTemp, fd_set& fdRead, string strMsg)
{
//发自己的消息
TCPMessage* tcpMessage = (TCPMessage*)recvData;
memset(tcpMessage->chMessage, 0, nMessageWordMaxSize);
tcpMessage->bIsHeartBeat = false;

//因为发送的文字信息为定长,所以,str.length()不能超过1024字节!!!
if (strMsg.length() >= nMessageWordMaxSize)
{
	memcpy(tcpMessage->chMessage, strMsg.c_str(), nMessageWordMaxSize);
}
else
	memcpy(tcpMessage->chMessage, strMsg.c_str(), strMsg.length());

int nSendLength = 0;
while (nSendLength != nSize)
{
	int curLength = 0;
	curLength = send(g_Client[i].sockClient, recvData, nSize, 0);
	if (curLength <= 0 || (curLength == SOCKET_ERROR))
	{
		cout << "Client " << inet_ntoa(g_Client[i].addrClient.sin_addr) << "closed" << endl;
		//如果关闭相关的socket,则必须clear掉相关socket
		FD_CLR(g_Client[i].sockClient, &fdTemp);
		FD_CLR(g_Client[i].sockClient, &fdRead);
		closesocket(g_Client[i].sockClient);

		if (i < g_nSockConn - 1)
		{
			//将失效的sockClient剔除,用数组的最后一个补上去
			g_Client[i--].sockClient = g_Client[--g_nSockConn].sockClient;
		}
		else
		{
			g_Client[i].sockClient = 0;
			g_nSockConn--;
		}

		break;
	}

	nSendLength += curLength;
}

if (nSendLength == sizeof(TCPMessage))
{
	cout << "Server: sendto " << inet_ntoa(g_Client[i].addrClient.sin_addr) << ": " << tcpMessage->chMessage << endl;
}

}

bool RecvClientMsg(char* recvData, int i, fd_set& fdTemp, fd_set& fdRead)
{
int nRecvLength = 0;
while (nRecvLength != nSize)
{
int curLength = 0;
curLength = recv(g_Client[i].sockClient, recvData, nSize, 0);
if (curLength <= 0 || (curLength == SOCKET_ERROR))
{
cout << "Client " << inet_ntoa(g_Client[i].addrClient.sin_addr) << “closed” << endl;
//如果关闭相关的socket,则必须clear掉相关socket
FD_CLR(g_Client[i].sockClient, &fdTemp);
FD_CLR(g_Client[i].sockClient, &fdRead);
closesocket(g_Client[i].sockClient);

		if (i < g_nSockConn - 1)
		{
			//将失效的sockClient剔除,用数组的最后一个补上去
			g_Client[i--].sockClient = g_Client[--g_nSockConn].sockClient;
		}
		else
		{
			g_Client[i].sockClient = 0;
			g_nSockConn--;
		}

		return false;
	}

	nRecvLength += curLength;
}

if (nRecvLength == nSize)
{
	return true;
}

return false;

}

int main()
{
//基本步骤就不解释了,网络编程基础那篇博客里讲的很详细了
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);

/*
SOCKET PASCAL FAR socket( int af, int type, int protocol);
af:一个地址描述。目前仅支持AF_INET格式,也就是说ARPA Internet地址格式。
type:新套接口的类型描述。
protocol:套接口所用的协议。如调用者不想指定,可用0指定,表示缺省。
*/

//创建一个套接字,错误返回-1
SOCKET sockListen = socket(AF_INET, SOCK_STREAM, 0);
if (sockListen == SOCKET_ERROR)
{
	cout << "sockListen == -1" << endl;
	return 0;
}
else
{
	cout << "服务器自己的监听socket: " << sockListen << endl;
}

//设置服务器的端口重启后能立马正常使用而不被占用
bool option = true;
int optlen = sizeof(option);
setsockopt(sockListen, SOL_SOCKET, SO_REUSEADDR, (char*)&option, optlen);


//绑定IP和端口号的准备工作
struct sockaddr_in my_addr;	//sin_port和 sin_addr 必须是网络字节顺序(Network Byte Order)!
my_addr.sin_family = AF_INET; //* host byte order */ 本机字节排序   
//这里也有要注意的几件事情。my_addr.sin_port 是网络字节顺序, my_addr.sin_addr.s_addr 也是的
my_addr.sin_port = htons(PORT); /* short, network byte order */  //将“本机字节排序”转换成“网络字节排序的short类型(2字节)”
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);  //INADDR_ANY表示本机IP地址(此处先用本机进行,如果使用指定的IP,则为my_addr.sin_addr.s_addr = inet_addr(“指定IP”);)
//my_addr.sin_addr.s_addr = inet_addr(SRV_IP); 
memset(&(my_addr.sin_zero), 0, sizeof(8)); /* zero the rest of the struct */

//将此套接字绑定IP和端口号
//在你调用 bind() 的时候,你要小心的另一件事情是:不要采用小于 1024的端口号。所有小于1024的端口号都被系统保留!你可以选择从1024 到65535的端口(如果它们没有被别的程序使用的话)。 
//你要注意的另外一件小事是:有时候你根本不需要调用它。如果你使 用 connect() 来和远程机器进行通讯,你不需要关心你的本地端口号(就象 你在使用 telnet 的时候),你只要简单的调用 connect() 就可以了,它会检 查套接字是否绑定端口,如果没有,它会自己绑定一个没有使用的本地端 口。
int nBind = ::bind(sockListen, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));	//一定要加“::”,否则bind为其它库的bind,返回值就不是int类型了!!!
if (-1 == nBind)
{
	cout << "m_sck: " << sockListen << endl;
	cout << "GetLastError(): " << GetLastError() << endl;
	cout << "bind不成功!!!" << endl;
	return 0;
}

//int listen(int sockfd, int backlog);
//sockfd 是调用 socket() 返回的套接字文件描述符。backlog 是在进入 队列中允许的连接数目。什么意思呢? 进入的连接是在队列中一直等待直 到你接受 (accept() 请看下面的文章)连接。它们的数目限制于队列的允许。 大多数系统的允许数目是20,你也可以设置为5到10。
//和别的函数一样,在发生错误的时候返回 - 1,并设置全局错误变量 errno。
int nListenOk = listen(sockListen, 64);	//64也就是“多少等待连接控制”的意思
if (nListenOk == -1)
{
	cout << "m_sck: " << sockListen << endl;
	cout << "GetLastError(): " << GetLastError() << endl;
	cout << "nListenOk == -1" << endl;
	//assert(false);
	return 0;
}

SOCKET sockClient;
SOCKADDR_IN addrClient;
int nLenAddrClient = sizeof(SOCKADDR);

fd_set fdRead;
fd_set fdTemp;
FD_ZERO(&fdRead);
FD_SET(sockListen, &fdRead);

int nRet = 0;
char* recvData = (char*)malloc(nSize);

//创建一个线程,接收玩家输入信息
std::thread thrdInput(&ReceiveInput);
thrdInput.detach();

while (true)
{
	TIMEVAL tv;//设置超时等待时间
	//每次调用完select后,必须tv会被重置,所以,必须重新赋值!!
	tv.tv_sec = 5;
	tv.tv_usec = 0;
	fdTemp = fdRead;

	//只处理read事件,不过后面还是会有读写消息发送的
	//select成功时返回0, 失败时返回-1 
	nRet = select(1, &fdTemp, NULL, NULL, &tv);	//windows下的select函数的第一个参数是没意义的,linux下为socket描述符的总数(linux下socket描述符也是文件描述符)

	if (nRet == -1)
	{
		cout << "select() error: " << GetLastError() << endl;
		break;
	}
	//else if (nRet == 0)
	//{
	//	//没有连接或者没有读事件
	//	Sleep(100);
	//	continue;
	//}

	if (FD_ISSET(sockListen, &fdTemp))
	{
		//接收连接
		sockClient = accept(sockListen, (SOCKADDR*)&addrClient, &nLenAddrClient);

		if (sockClient != INVALID_SOCKET)
		{
			g_Client[g_nSockConn].addrClient = addrClient;//保存连接端地址信息
			g_Client[g_nSockConn].sockClient = sockClient;//加入连接者队列
			FD_SET(g_Client[g_nSockConn].sockClient, &fdRead);
			g_nSockConn++;
			//输出连接者的地址信息
			cout << inet_ntoa(addrClient.sin_addr) << ":" << ntohs(addrClient.sin_port) <<":" << sockClient << "连接成功 !" << endl;
		}
	}
	
	for (int i = 0; i < g_nSockConn; i++)
	{
		if (FD_ISSET(g_Client[i].sockClient, &fdTemp))
		{
			memset(recvData, 0, nSize);
			//接收来自客户端的数据
			bool bRecvOk = false;
			bRecvOk = RecvClientMsg(recvData, i, fdTemp, fdRead);

			if (bRecvOk)
			{
				//显示接收自客户端的消息
				TCPMessage* recvMsg = (TCPMessage*)recvData;
				if (recvMsg->bIsHeartBeat == false)
				{
					cout << "Client " << inet_ntoa(g_Client[i].addrClient.sin_addr) << ": " << recvMsg->chMessage << endl;
				}

				//发送信息给客户端
				int nSendLength = 0;
				if (m_qeSendMessage.empty())
				{
					//发心跳报文
					//SendHeartMsg(recvData, i, fdTemp, fdRead);
				}
				else
				{
					//SendMsgToClient(recvData, i, fdTemp, fdRead);
				}
			}

		}

	}

	if (!m_qeSendMessage.empty())
	{
		//给所有已连接IP发自己输入的内容
		//mutex_sendMsg.lock();
		string strMsg = m_qeSendMessage.front();
		//mutex_sendMsg.unlock();
		for (int i=0; i<g_nSockConn; i++)
		{
			SendMsgToClient(recvData, i, fdTemp, fdRead, strMsg);
		}

		//mutex_sendMsg.lock();
		m_qeSendMessage.pop();
		//mutex_sendMsg.unlock();
	}

}

closesocket(sockListen);
WSACleanup();

return 0;

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值