select模型跨平台服务端/客户端

以下是select模型实现的一个简易TCP服务端和客户端,客户端添加了一个命令输入线程

server

#ifdef _WIN32
    //尽量避免早期的宏的引入
    #define WIN32_LEAN_AND_MEAN
    //Windows.h里面的宏和WinSock2里面的宏有重复
    //Windows环境下开发
    #define _WINSOCK_DEPRECATED_NO_WARNINGS
    #include <Windows.h>
    //windows环境下开发网络编程需要引入的socket头文件
    #include <WinSock2.h>
    //windows环境下进行引入动态链接库  WSAStartup
    //在其他系统平台下不能使用  可以将ws2_32.lib 配置到工程 属性 链接器里面
    //#pragma comment(lib, "ws2_32.lib")
#else
    #include<unistd.h>
    #include<arpa/inet.h>
    #include<string.h>
    #define SOCKET int
    #define INVALID_SOCKET  (SOCKET)(~0)
    #define SOCKET_ERROR            (-1)
#endif
#include<stdio.h>
#include<vector>
//内存对齐
struct DataPackage
{
	//long 在64位中是64字节    在32位只有4字节
	int age;
	char name[32];
};
struct DataHeader
{
	short dataLength;
	short cmd;
};
enum CMD
{
	CMD_LOGIN,
	CMD_LOGINOUT,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR
};
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char passWord[32];
};
struct LoginOut : public DataHeader
{
	LoginOut()
	{
		dataLength = sizeof(LoginOut);
		cmd = CMD_LOGINOUT;
	}
	char userName[32];
};
struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 1;
	}
	int result;
};
struct LogoutResult: public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};
struct NewUserJoin : public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock_id = 0;
	}
	int sock_id;
};
std::vector<SOCKET> g_clients;

int process(SOCKET _cSock)
{
	//缓冲区,把消息先放到缓冲区里面
	char szRecv[1024] = {};
	//5接收客户端数据
	int nLen = (int)recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
	DataHeader* header = (DataHeader*)szRecv;
	if (nLen <= 0) {
		printf("客户端<Socket=%d>已退出,任务结束。\n", _cSock);
		return -1;
	}
	switch (header->cmd) {
	case CMD_LOGIN:
	{
		recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		//取到消息   把消息对应到消息体里面
		Login* login = (Login*)szRecv;
		printf("收到客户端<Socket=%d>请求:cmd_login 数据长度:%d, userName=%s, passWd = %s \n", _cSock , login->dataLength, login->userName, login->passWord);
		//忽略判断用户名密码是否正确
		LoginResult loginRet;
		send(_cSock, (char*)&loginRet, sizeof(LoginResult), 0);
	}
	break;
	case CMD_LOGINOUT:
	{
		recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		LoginOut* logout = (LoginOut*)szRecv;
		printf("收到客户端<Socket=%d>请求:cmd_logout 数据长度:%d, userName=%s \n", _cSock , logout->dataLength, logout->userName);

		//忽略判断用户名密码是否正确
		LogoutResult logoutRet;
		send(_cSock, (char*)&logoutRet, sizeof(LogoutResult), 0);
	}
	break;
	default:
		header->cmd = CMD_ERROR;
		header->dataLength = 0;
		send(_cSock, (char*)&header, sizeof(header), 0);
		break;
	}
    return 0;
}

int main()
{
#ifdef _WIN32
	//创建版本号
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
#endif
	//1 建立一个socket
	//2 bind 绑定用于接收客户端连接的网络端口
	//3 listen 监听网络端口
	//4 accept 阻塞等待接受客户端连接
	//5 send 向客户端发送一条数据
	//6 关闭套接字 close socket

	//1 建立一个socket
	//创建一个AF_INET的套接字,代表ipv4
	//SOCK_STREAM 面向数据流的   还可以选择面向蓝牙的等等
	//IPPROTO_TCP面向tcp的   也可以选择udp等
	SOCKET _sock = socket(AF_INET,SOCK_STREAM, IPPROTO_TCP);

	//2 bind 绑定用于接收客户端连接的网络端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);  //host to net unsigned short 字节序转换
	//设置绑定到那个IP地址上   一台机器有很多地址
#ifdef _WIN32
	_sin.sin_addr.S_un.S_addr = INADDR_ANY; //inet_addr("127.0.0.1");
#else
    _sin.sin_addr.s_addr = INADDR_ANY;
#endif
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		printf("ERROR,绑定网络端口失败\n");
	}
	else {
		printf("绑定网络端口成功\n");
	}

	//3 listen 监听网络端口
	//backlog 5  backlog告诉内核使用多大数值创建等待队列,等待请求    一般小于30以内
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		printf("ERROR,监听网络端口失败\n");
	}
	else {
		printf("监听网络端口成功\n");
	}

	while (true) 
	{
		fd_set fdRead;
		fd_set fdWrite;
		fd_set fdExp;
		FD_ZERO(&fdRead);
		FD_ZERO(&fdWrite);
		FD_ZERO(&fdExp);
		FD_SET(_sock, &fdExp);
		FD_SET(_sock, &fdWrite);
		FD_SET(_sock, &fdRead);
                SOCKET maxSock = _sock;
		for (int n = (int)g_clients.size() - 1; n >= 0; n--)
		{
			FD_SET(g_clients[n], &fdRead);
				if(maxSock < g_clients[n])
				{
					maxSock = g_clients[n];
				}
		}
		//select最后一个参数  设置时间t,将select变为非阻塞   查询等待时间,到了t时间没有请求,返回
		//1s 是最大的阻塞时间值,不一定等1s
		timeval t = {1,0};
		//伯克利 socket
		//nfds 第一个参数,是指fd_set集合中所有描述符(socket)范围,socket最大的值加1  在windows下面不产生意义,可以写0,在linux下面代表最大连接数加1
		int ret = select(maxSock+1, &fdRead, &fdWrite, &fdExp, &t);
		if (ret < 0)
		{
			printf("select任务结束。 \n");
			break;
		}
		//判断当前socket是否在当前fdSet集合中
		if (FD_ISSET(_sock, &fdRead)) 
		{
			FD_CLR(_sock, &fdRead);
			//4 accept 阻塞等待接受客户端连接
			//客户端地址
			sockaddr_in clientAddr = {};
			int nAddrLen = (int)sizeof(clientAddr);
			SOCKET _cSock = INVALID_SOCKET;


			//循环accept多次
#ifdef _WIN32
			 _cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);
#else
			_cSock = accept(_sock, (sockaddr*)&clientAddr, (socklen_t *)&nAddrLen);
#endif
			if (_cSock == INVALID_SOCKET)
			{
				printf("ERROR,接收到无效客户端连接\n");
			}
			else
			{
				for (int n = (int)g_clients.size() - 1; n >= 0; n--)
				{
					NewUserJoin userJoin;
					send(g_clients[n], (const char*)&userJoin, sizeof(NewUserJoin), 0);
				}
				g_clients.push_back(_cSock);
				printf("新客户端加入:ip = %s \n", inet_ntoa(clientAddr.sin_addr));
			}
			
		}
// fdRead结构体在windows系统有fd_count
//		for (size_t n = 0; n < fdRead.fd_count; n++) 
//		{
//			if (-1 == process(fdRead.fd_array[n]))
//			{
//				auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);
//				if (iter != g_clients.end())
//				{
//					g_clients.erase(iter);
//				}
//			}
//		}
        for(int n = (int)g_clients.size() - 1; n >= 0; n--)
        {
            if(FD_ISSET(g_clients[n], &fdRead))
            {
                if(-1 == process(g_clients[n]))
                {
                    auto iter = g_clients.begin()+n;
                    if(iter != g_clients.end())
                    {
                        g_clients.erase(iter);
                    }
                }
            }

        }
		//printf("空闲处理其他业务\n");
	}
#ifdef _WIN32
	//8 关闭套接字 close socket
//	for (size_t n = g_clients.size() - 1; n >= 0; n--)
        for (int n = (int)g_clients.size() - 1; n >= 0; n--)
	{

		closesocket(g_clients[n]);
	}
	closesocket(_sock);

	//	清楚windows socket环境
	WSACleanup();
#else
        //size_t 是无符号的,大于等于0是永远true的
        for (int n = (int)g_clients.size() - 1; n >= 0; n--)
        {
 
                close(g_clients[n]);
        }
        close(_sock);
#endif
	getchar();
	return 0;
}

EasyTcpClient.hpp


#ifndef _EasyTcpClient_hpp_
#define _EasyTcpClient_hpp_
	//Windows.h里面的宏和WinSock2里面的宏有重复
	//Windows环境下开发
	#ifdef _WIN32
	//尽量避免早期的宏的引入
	#define WIN32_LEAN_AND_MEAN
	#include <Windows.h>
	//windows环境下开发网络编程需要引入的socket头文件
	#include <WinSock2.h>
	//windows环境下进行引入动态链接库  WSAStartup
	//在其他系统平台下不能使用  可以将ws2_32.lib 配置到工程 属性 链接器里面
	#pragma comment(lib, "ws2_32.lib")
#else
	#include<unistd.h>
	#include<arpa/inet.h>
	#include<string.h>
	#define SOCKET int
	#define INVALID_SOCKET  (SOCKET)(~0)
	#define SOCKET_ERROR            (-1)
#endif

#include<stdio.h>
#include "msg.hpp"

class EasyTcpClient
{
	SOCKET _sock;
public:
	EasyTcpClient()
	{
		_sock = INVALID_SOCKET;
	}
	// 虚析构函数 
	virtual ~EasyTcpClient()
	{
		Close();
	}
	//初始化socket
	void InitSocket()
	{
		//启动Win sock 2.x环境
#ifdef _WIN32
		//创建版本号
		WORD ver = MAKEWORD(2, 2);
		WSADATA dat;
		WSAStartup(ver, &dat);
#endif
		//1 建立socket
		if (_sock != INVALID_SOCKET)
		{
			//关闭之前连接
			printf("关闭旧连接<socket=%d>.... \n", _sock);
			Close();
		}
		_sock = socket(AF_INET, SOCK_STREAM, 0);
		if (_sock == INVALID_SOCKET) {
			printf("error, build socket faild \n");
		}
		else {
			printf("build success \n");
		}
	}
	//连接服务器
	int Connect(const char* ip, unsigned short port)
	{
		if (_sock == INVALID_SOCKET)
		{
			InitSocket();
		}
		//2 连接服务器connect
		sockaddr_in _sin = {};
		_sin.sin_family = AF_INET;
		_sin.sin_port = htons(port);
#ifdef _WIN32
		_sin.sin_addr.S_un.S_addr = inet_addr(ip);
#else
		_sin.sin_addr.s_addr = inet_addr(ip);
#endif
		int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
		if (ret == SOCKET_ERROR) {
			printf("<socket=%d>错误, 连接服务器<%s:%d>失败...... \n", _sock, ip, port);
		}
		else {
			printf("<socket=%d>连接服务器<%s:%d>成功....... \n", _sock, ip, port);
		}
		return ret;
	}

	//4 关闭套接字closesocket
	void Close()
	{
		if (_sock != INVALID_SOCKET)
		{
			//清除Win sock 2.x环境	
#ifdef _WIN32
			closesocket(_sock);
			//清除windows socket环境
			WSACleanup();
#else
			close(_sock);
#endif
			_sock = INVALID_SOCKET;
		}
	}

	//处理网络消息
	bool OnRun()
	{
		if (isRun())
		{
			fd_set fdReader;
			FD_ZERO(&fdReader);
			FD_SET(_sock, &fdReader);
			timeval t = { 1, 0 };
			int ret = select(_sock, &fdReader, 0, 0, &t);
			if (ret < 0)
			{
				printf("<socket=%d>select 任务结束1\n", _sock);
				return false;
			}
			if (FD_ISSET(_sock, &fdReader))
			{
				FD_CLR(_sock, &fdReader);
				if (-1 == RecvData(_sock))
				{
					printf("<socket=%d>select任务结束2\n", _sock);
					return false;
				}
			}
			return true;
		}
		return false;
	}
	//是否工作中
	bool isRun()
	{
		return _sock != INVALID_SOCKET;
	}
	//接收数据 粘包 拆包
	int RecvData(SOCKET _cSock)
	{
		//缓冲区,把消息先放到缓冲区里面
		char szRecv[1024] = {};
		//5接收客户端数据
		int nLen = recv(_cSock, (char*)&szRecv, sizeof(DataHeader), 0);
		DataHeader* header = (DataHeader*)szRecv;
		if (nLen <= 0)
		{
			printf("<socket=%d>与服务器断开连接,任务结束。\n", _cSock);
			return -1;
		}
		recv(_cSock, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);

		OnNetMsg(header);
		return 0;
	}

	//响应网络消息
	void OnNetMsg(DataHeader* header)
	{
		switch (header->cmd) {
		case CMD_LOGIN_RESULT:
		{
								 //基类转换成子类
								 LoginResult* login = (LoginResult*)header;
								 printf("<socket=%d>收到服务端消息:CMD_LOGIN_RESULT 数据长度:%d \n", _sock, login->dataLength);
		}
			break;
		case CMD_LOGOUT_RESULT:
		{
								  LogoutResult* logout = (LogoutResult*)header;
								  printf("<socket=%d>收到服务端消息:CMD_LOGOUT_RESULT 数据长度:%d \n", _sock, logout->dataLength);
		}
			break;
		case CMD_NEW_USER_JOIN:
		{
								  NewUserJoin* user_join = (NewUserJoin*)header;
								  printf("<socket=%d>收到服务端消息:CMD_NEW_USER_JOIN 数据长度:%d \n", _sock, user_join->dataLength);
		}
			break;
		}
	}

	//发送数据
	int SendData(DataHeader* header)
	{
		if (isRun() && header)
		{
			return send(_sock, (const char*)header, header->dataLength, 0);
		}
		return SOCKET_ERROR;
	}
private:

};

#endif

msg.hpp


//防止多次引入 msg.hpp头文件
#ifndef _MSG_HPP_
#define _MSG_HPP_

//内存对齐
struct DataPackage
{
	//long 在64位中是64字节    在32位只有4字节
	int age;
	char name[32];
};
struct DataHeader
{
	short dataLength;
	short cmd;
};
enum CMD
{
	CMD_LOGIN,
	CMD_LOGINOUT,
	CMD_LOGIN_RESULT,
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR
};
struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char passWord[32];
};
struct LoginOut : public DataHeader
{
	LoginOut()
	{
		dataLength = sizeof(LoginOut);
		cmd = CMD_LOGINOUT;
	}
	char userName[32];
};
struct LoginResult : public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
		result = 1;
	}
	int result;
};
struct LogoutResult : public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGOUT_RESULT;
		result = 0;
	}
	int result;
};
struct NewUserJoin : public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(NewUserJoin);
		cmd = CMD_NEW_USER_JOIN;
		sock_id = 0;
	}
	int sock_id;
};

#endif // !_MSG_HPP_

client.cpp


#include "EasyTcpClient.hpp"
//引入c++标准线程库 c++11正式加入标准库  还有第三方线程库pthread
#include<thread>

//传引用写法   在其他平台下会有错误   使用指针
void cmdThread(EasyTcpClient* client)
{
	while (true)
	{
		char cmdBuff[256] = {};
		scanf_s("%s", cmdBuff);
		if (0 == strcmp(cmdBuff, "exit"))
		{
			client->Close();
			printf("退出cmdThread线程 \n");
			break;
		}
		else if (0 == strcmp(cmdBuff, "login"))
		{
			Login login;
			strcpy_s(login.userName, "lanzhibo");
			strcpy_s(login.passWord, "123");
			client->SendData(&login);
		}
		else if (0 == strcmp(cmdBuff, "logout"))
		{
			LoginOut logout;
			strcpy_s(logout.userName, "lanzhibo");
			client->SendData(&logout);
		}
		else
		{
			printf("不支持的命令\n");
		}
	}
}

int main()
{
	EasyTcpClient client;
	client.InitSocket();
	client.Connect("127.0.0.1", 8888);

	//线程 thread scanf是阻塞的   使用线程异步获取输入
	std::thread t1(cmdThread, &client);
	//与主线程分离
	t1.detach();

	while (client.isRun()) {
		client.OnRun();
	}
	client.Close();

	getchar();
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值