服务端与客户端——移植到linux上

1、将client.cpp和service.cpp放到linux上
在这里插入图片描述
出现问题,所以这里使用linux下的头文件

1、通过一个宏来判断是win还是linux

#ifdef _WIN32
	#include<windows.h>
	#include<WinSock2.h>
#else
	#include<unistd.h>  	//unix std  前面表示unix操作系统,后面表示正常的std
	#include<arpa/inet.h>
#endif

再次编译一下,发现socket没有定义
在这里插入图片描述

定义socket

Linux系统中,没有对SOCKET类型进行定义。但其实SOCKET类型就是简单的无符号整形变量。

#define SOCKET int;

同样的,包括INVALID_SOCKET、SOCKET_ERROR

#define INVALID_SOCKET  (SOCKET)(~0)
#define SOCKET_ERROR            (-1)

上面这些在win上都是这样定义的
修改后为

在这里插入图片描述
此外,在linux看来,socket就是普通文件,所以直接通过close关掉
在这里插入图片描述

关掉Windows Socket启动和结束环境的代码

下面还有几个需要加上判定
在这里插入图片描述

修改sockaddr_in

在这里插入图片描述

其他问题

在这里插入图片描述
这里在linux中要添加string.h的头,但是下面还是报错,所以要么只使用一个头文件
要么使用两个头文件 <string.h>
在这里插入图片描述
对于这个弄了好久还是不行,那么只能返回到不安全的strcpy了。

修改对应的ip地址
在这里插入图片描述

修改后的代码


#define _WINSOCK_DEPRECATED_NO_WARNINGS
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN	
#include<windows.h>
#include<WinSock2.h>
#else
#include<unistd.h>  	//unix std 这里使用unix的api
#include<arpa/inet.h>   //对应上面的第二个 使用unix的socket的函数定义
#include<cstring>
#define SOCKET int
#define INVALID_SOCKET  (SOCKET)(~0)
#define SOCKET_ERROR            (-1)
#endif
#include<string>
#include<iostream>

#include <thread>
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库


enum CMD
{
	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGINOUT,
	CMD_LOGOUT_RESULT,
	CMD_ERROR,
	CMD_NEWUSERJOIN,
};




//消息头
struct DataHeader
{
	short dataLength;    //数据长度 32767字节
	short cmd;
};

struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char Password[32];
};
struct Logout :public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGINOUT;
	}
	char userName[32];
};
struct LoginResult :public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
	}
	int result;
};
struct LogoutResult :public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
	}
	int result;
};

struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_NEWUSERJOIN;
		result = 0;
	}
	int sockId;
	int result;
};

int processor(SOCKET _sock)
{
	char* szRecv = new char[1024];
	//5 首先接收数据包头
	int nlen = recv(_sock, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象
	if (nlen <= 0)
	{
		//客户端退出
		cout << "客户端:Socket = " << _sock << " 与服务器断开连接,任务结束" << endl;
		return -1;
	}
	DataHeader* header = (DataHeader*)szRecv;
	switch (header->cmd)
	{
	case CMD_NEWUSERJOIN:
	{
		NewUserJoin _userJoin;
		recv(_sock, (char*)&_userJoin + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		cout << "收到服务器消息: CMD_NEWUSERJOIN:" << _userJoin.sockId << endl;
	}break;
	case CMD_LOGIN_RESULT:
	{
		LoginResult _lgRes;
		recv(_sock, (char*)&_lgRes + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		cout << "收到服务器消息: CMD_LOGIN_RESULT:" << _lgRes.result << endl;
	}break;
	case CMD_LOGOUT_RESULT:
	{
		LogoutResult _lgRes;
		recv(_sock, (char*)&_lgRes + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		cout << "收到服务器消息: CMD_LOGINOUT_RESULT:" << _lgRes.result << endl;
	}break;
	default:
	{
		header->cmd = CMD_ERROR;
		header->dataLength = 0;
		send(_sock, (char*)&header, sizeof(DataHeader), 0);
	}
	break;
	}
	return 0;
}

bool g_bRun = true;

void cmdThread(SOCKET _sock)
{

	while (true)
	{
		// 3 输入请求命令
		char cmdBuf[128] = {};
		cout << "输入命令: ";
		cin >> cmdBuf;
		// 4 处理请求
		if (strcmp(cmdBuf, "exit") == 0)
		{
			cout << "退出cmdThread线程" << endl;
			g_bRun = false;
			return;
		}
		else if (0 == strcmp(cmdBuf, "login"))
		{
			Login _login;
#ifdef _WIN32
			strcpy_s(_login.userName, "Evila");
			strcpy_s(_login.Password, "Evila_Password");
#else
			strcpy(_login.userName, "Evila");
			strcpy(_login.Password, "Evila_Password");
#endif
			// 5 向服务器发送请求命令
			send(_sock, (const char*)&_login, _login.dataLength, 0);
		}
		else if (0 == strcmp(cmdBuf, "logout"))
		{
			Logout _logout;
#ifdef _WIN32
			strcpy_s(_logout.userName, "Evila");
#else
			strcpy(_logout.userName, "Evila");
#endif
			//5 向服务器发送请求命令
			send(_sock, (const char*)&_logout, _logout.dataLength, 0);
		}
		else
		{
			cout << "不受支持的命令" << endl;
		}
	}
	return;
}


int main()
{
#ifdef _WIN32
	//启动 windows socket 2.x 环境
	WORD versionCode = MAKEWORD(2, 2);	//创建一个版本号 
	WSADATA data;
	WSAStartup(versionCode, &data);  //启动Socket网络API的函数
#endif									 ///


	//(1) 用Socket API建立简易的TCP客户端

	//	1. 建立一个Socket  下面第三个参数不需要指定
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, 0); // ipv4 面向字节流的 tcp协议
	if (INVALID_SOCKET == _sock)
	{
		cout << "错误,建立socket失败" << endl;
	}
	else
	{
		cout << "成功建立客户端socket" << endl;
	}
	//	2. 连接服务器 connect
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567); //端口号 host to net  sockaddr_in中port是USHORT类型
								 //网络中port是 unsigend short类型 因此需要Htons进行转换
	//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  //服务器绑定的IP地址  127.0.0.1是本地地址
#ifdef _WIN32
	_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
#else
	_sin.sin_addr.s_addr = inet_addr("127.0.0.1");
#endif


	int ret = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));
	if (SOCKET_ERROR == ret)
	{
		cout << "错误,connect失败" << endl;
	}
	else
	{
		cout << "成功,connect 成功" << endl;
	}

	//3 接收服务器数据 resv
	char recvBuf[256] = {};

	//启动线程
	std::thread t1(cmdThread, _sock);
	t1.detach();


	while (g_bRun)
	{
		fd_set fdReads;
		FD_ZERO(&fdReads);
		FD_SET(_sock, &fdReads);
		timeval t = { 1,0 };
		int ret = select(_sock + 1, &fdReads, NULL, NULL, &t);
		if (ret < 0)
		{
			cout << "select任务结束" << endl;
			break;
		}
		if (FD_ISSET(_sock, &fdReads))	//如果_sock在fdRead里面,表明有需求等待处理
		{
			FD_CLR(_sock, &fdReads);
			if (processor(_sock) == -1)
			{
				cout << "Select任务已结束2" << endl;
				break;
			}
		}
	}


#ifdef _WIN32
	//	6. 关闭socket
	closesocket(_sock);
	// 清除Windows socket环境
	WSACleanup();
#else
	close(_sock);
#endif

	getchar();//防止一闪而过
	return 0;
}

服务端的移植

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#ifdef _WIN32
#include<Windows.h>
#include<WinSock2.h>
#else
#include<unistd.h>  	//unix std 这里使用unix的api
#include<arpa/inet.h>   //对应上面的第二个 使用unix的socket的函数定义
#include<cstring>
#define SOCKET int
#define INVALID_SOCKET  (SOCKET)(~0)
#define SOCKET_ERROR            (-1)
#endif
#include<iostream>
#include<string>
#include<vector>
using namespace std;
#pragma comment(lib,"ws2_32.lib") //windows socket2 32的lib库


enum CMD  //消息枚举
{

	CMD_LOGIN,
	CMD_LOGIN_RESULT,
	CMD_LOGINOUT,
	CMD_LOGOUT_RESULT,
	CMD_ERROR,
	CMD_NEWUSERJOIN,
};
//消息头
struct DataHeader
{
	short dataLength;    //数据长度 32767字节
	short cmd;
};


struct Login : public DataHeader
{
	Login()
	{
		dataLength = sizeof(Login);
		cmd = CMD_LOGIN;
	}
	char userName[32];
	char Password[32];
};
struct Logout :public DataHeader
{
	Logout()
	{
		dataLength = sizeof(Logout);
		cmd = CMD_LOGINOUT;
	}
	char userName[32];
};
struct LoginResult :public DataHeader
{
	LoginResult()
	{
		dataLength = sizeof(LoginResult);
		cmd = CMD_LOGIN_RESULT;
	}
	int result;
};
struct LogoutResult :public DataHeader
{
	LogoutResult()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_LOGOUT_RESULT;
	}
	int result;
};
struct NewUserJoin :public DataHeader
{
	NewUserJoin()
	{
		dataLength = sizeof(LogoutResult);
		cmd = CMD_NEWUSERJOIN;
		result = 0;
	}
	int sockId;
	int result;
};

int processor(SOCKET _cSocket)
{
	//使用一个缓冲区接收数据 暂定最大收发1024个字节		后续会改进大文件的传输
	char* szRecv = new char[1024];

	//5 首先接收数据包头
	int nlen = (int)recv(_cSocket, szRecv, sizeof(DataHeader), 0); //接受客户端的数据 第一个参数应该是客户端的socket对象

	if (nlen <= 0)
	{
		//客户端退出
		cout << "客户端已退出,任务结束" << endl;
		return -1;
	}
	//用一个指针指向这个头部
	DataHeader* header = (DataHeader*)szRecv;
	switch (header->cmd)
	{
	case CMD_LOGIN:
	{
		Login* _login;
		//读取Header->dataLength的数据长度  将数据继续读入saRecv这个块里面
		recv(_cSocket, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		_login = (Login*)szRecv;
		cout << "收到命令:CMD_LOGIN" << " 数据长度 = " << header->dataLength << " UserName = " << _login->userName << " Password = " << _login->Password << endl;
		//忽略了判断用户名密码是否正确的过程
		LoginResult _loginres;
		_loginres.result = 200;
		send(_cSocket, (char*)&_loginres, sizeof(LoginResult), 0);
	}break;
	case CMD_LOGINOUT:
	{
		Logout* _logout;
		recv(_cSocket, szRecv + sizeof(DataHeader), header->dataLength - sizeof(DataHeader), 0);
		_logout = (Logout*)szRecv;
		cout << "收到命令:CMD_LOGOUT" << " 数据长度 = " << header->dataLength << " UserName = " << _logout->userName << endl;
		LogoutResult _logoutres;
		_logoutres.result = 200;
		send(_cSocket, (char*)&_logoutres, sizeof(LogoutResult), 0);
	}break;
	default:
	{
		header->cmd = CMD_ERROR;
		header->dataLength = 0;
		send(_cSocket, (char*)&header, sizeof(DataHeader), 0);
	}
	break;
	}
}


std::vector<SOCKET>g_clinets;

int main()
{

#ifdef _WIN32
	//启动 windows socket 2.x 环境
	WORD versionCode = MAKEWORD(2, 2);	//创建一个版本号 
	WSADATA data;
	WSAStartup(versionCode, &data);  //启动Socket网络API的函数
#endif    									 ///


	//(1) 用Socket API建立简易的TCP服务端

	//	1. 建立一个Socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // ipv4 面向字节流的 tcp协议

	//	2. 绑定接受客户端连接的端口 bind

	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567); //端口号 host to net  sockaddr_in中port是USHORT类型
								 //网络中port是 unsigend short类型 因此需要Htons进行转换
	//_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");  //服务器绑定的IP地址  127.0.0.1是本地地址
#ifdef _WIN32
	_sin.sin_addr.S_un.S_addr = INADDR_ANY; //不限定访问该服务端的IP
#else
	_sin.sin_addr.s_addr = INADDR_ANY;
#endif
	if (bind(_sock, (sockaddr*)&_sin, sizeof(_sin)) == SOCKET_ERROR)  //sockaddr 不利于编码 
	{
		cout << "ERROR: 绑定用于接受客户端连接的网络端口失败..." << endl;
	}
	else
	{
		cout << "SUCCESS: 绑定端口成功..." << endl;
	}
	//	3. 监听网络端口 listen

	if (listen(_sock, 5) == SOCKET_ERROR)//第二个参数 backbag 最大允许连接数量
	{
		cout << "ERROR: 监听用于接受客户端连接的网络端口失败..." << endl;
	}
	else
	{
		cout << "SUCCESS: 监听端口成功..." << endl;
	}
	fd_set fdRead;
	fd_set fdWrite;
	fd_set fdExpect;

	while (true)
	{


		FD_ZERO(&fdRead);		//清空fd集合的数据
		FD_ZERO(&fdWrite);
		FD_ZERO(&fdExpect);
		//这个宏的功能是 将服务端的_sock 放到fdRead这个集合中 
		//当socket在listen状态,如果已经接收一个连接请求,这个socket会被标记为可读,例如一个accept会确保不会阻塞的完成
		//对于其他的socket,可读性意味着队列中的数据适合读,当调用recv后不会阻塞。
		FD_SET(_sock, &fdRead);  //将服务端的socket放入可读列表,确保accept不阻塞
		FD_SET(_sock, &fdWrite);
		FD_SET(_sock, &fdExpect);
		SOCKET maxSock = _sock;

		for (size_t n = 0; n < g_clinets.size(); n++)
		{
			FD_SET(g_clinets[n], &fdRead);		//所有连入的客户端放入可读列表 保证recv不阻塞
			if (maxSock < g_clinets[n])
			{
				maxSock = g_clinets[n];
			}
		}

		//nfds第一个参数 是一个整数值 是指fd_set集合中所有socket值的范围 不是数量    。。
		int ret = select(maxSock + 1, &fdRead, &fdWrite, &fdExpect, NULL);
		if (ret < 0)
		{
			cout << "select任务结束" << endl;
			break;
		}
		if (FD_ISSET(_sock, &fdRead))	//判断_sock是否在fdRead中
		{
			FD_CLR(_sock, &fdRead);
			//	4. 等待接受客户端连接 accept
			sockaddr_in _clientAddr = {};
			int cliendAddrLen = sizeof(_clientAddr);
			SOCKET _clientSock = INVALID_SOCKET; // 初始化无效的socket 用来存储接入的客户端
#ifdef _WIN32
			_clientSock = accept(_sock, (sockaddr*)&_clientAddr, &cliendAddrLen);//当客户端接入时 会得到连入客户端的socket地址和长度
#else
			_clientSock = accept(_sock, (sockaddr*)&_clientAddr, (socklen_t*)&cliendAddrLen);//当客户端接入时 会得到连入客户端的socket地址和长度
#endif

			if (INVALID_SOCKET == _clientSock) //接受到无效接入
			{
				cout << "ERROR: 接受到无效客户端SOCKET..." << endl;
			}
			else
			{
				cout << "新Client加入:" << "socket = " << _clientSock << " IP = " << inet_ntoa(_clientAddr.sin_addr) << endl;  //inet_ntoa 将ip地址转换成可读的字符串
			}
			for (int n = g_clinets.size() - 1; n >= 0; n--)
			{
				NewUserJoin userJoin;
				userJoin.cmd = CMD_NEWUSERJOIN;
				userJoin.sockId = _clientSock;
				send(g_clinets[n], (const char*)&userJoin, userJoin.dataLength, 0);
			}


			g_clinets.push_back(_clientSock);


		}
#ifdef _WIN32
		for (size_t n = 0; n < fdRead.fd_count; n++)
		{
			if (processor(fdRead.fd_array[n]) == -1)//processor函数是处理命令的逻辑 recv接到的数据并做出相应的判断和输出日志
			{
				auto it = find(g_clinets.begin(), g_clinets.end(), fdRead.fd_array[n]);
				if (it != g_clinets.end())
					g_clinets.erase(it);
			}
		}
#else		
		for (int n = (int)g_clinets.size() - 1; n >= 0; n--)
		{
			if (FD_ISSET(g_clinets[n], &fdRead))
			{
				if (-1 == processor(g_clinets[n]))
				{
					auto iter = g_clinets.begin(); //std::vector<SOCKET>::iterator
					if (iter != g_clinets.end())
					{
						g_clinets.erase(iter);
					}
				}
			}
		}

#endif

	}





#ifdef _WIN32
	//	6. 关闭socket
	for (int n = (int)g_clinets.size() - 1; n >= 0; n--)
	{
		closesocket(g_clinets[n]);
	}


	closesocket(_sock);
	// 清除Windows socket环境
	WSACleanup();
#else	
	for (int n = (int)g_clinets.size() - 1; n >= 0; n--)
	{
		close(g_clinets[n]);
	}
	close(_sock);
#endif
	return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

贪睡的蜗牛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值