5.1---将客户端代码进行跨平台移植(Windows、Unix)

一、跨平台移植概述
前面的文章我们的代码都是在Windows下进行编译运行的,现在我们修改代码,使其在Windows、Ubuntu、Mac OS系统上都可以运行
因为Linux和Mac OS底层都是使用Unix内核,因此将代码修改之后,在Linux和Mac OS上面都可以进行编译并运行,不需要单独设计两份
二、代码修订
代码修订1
头文件修订:Windows与Unix下套接字使用的头文件不同,因此需要修订
一些常量定义:Windows下有SOCKET、、INVALID_SOCKET、SOCKET_ERROR等宏的定义,但是Unix没有。通过查看WinSock2.h头文件源码,可以看到有如下几张图片的定义,因此我们当在Unix系统中运行时,我们也手动在程序中进行了宏定义
在这里插入图片描述

修改代码如下
#ifdef _WIN32
    define WIN32_LEAN_AND_MEAN
    #define _WINSOCK_DEPRECATED_NO_WARNINGS //for inet_pton()
    #define _CRT_SECURE_NO_WARNINGS
 
    #include <windows.h>
    #include <WinSock2.h>
    #pragma comment(lib, "ws2_32.lib")
#else
    #include <unistd.h>
	#include <sys/socket.h>
	#include <sys/types.h>
	#include <arpa/inet.h>
	#include <netinet/in.h>
	#include <sys/select.h>
    //在Unix下没有这些宏,为了兼容,自己定义
    #define SOCKET int
    #define INVALID_SOCKET  (SOCKET)(~0)
    #define SOCKET_ERROR            (-1)
#endif

代码修改2
Windows需要开启套接字网络环境,但是Unix不需要,因此还需要进行以下的一些修改

//在Windows下需要开启套接字环境
#ifdef _WIN32
    WORD ver = MAKEWORD(2, 2);
    WSADATA dat;
    WSAStartup(ver, &dat);
#endif
 
//...省略中间代码
 
//关闭套接字与清除套接字环境等
#ifdef _WIN32
    closesocket(_sock);
    WSACleanup();
#else
    close(_sock);
#endif

代码修订3
另外,Windows与Unix下struct sockaddr_in的定义也不同,因此还需要进行如下的修改
备注:Windows与Unix的IP地址不同,需要分别设定

//声明要连接的服务端地址(注意,不同平台的服务端IP地址也不同)
struct sockaddr_in _sin = {};
#ifdef _WIN32
    _sin.sin_addr.S_un.S_addr = inet_addr("192.168.0.105");
#else
    _sin.sin_addr.s_addr = inet_addr("192.168.0.104");
#endif
_sin.sin_family = AF_INET;
_sin.sin_port = htons(4567);

一些其他修改
在Mac OS上的X code编译器上recv函数返回size_t类型,因此X code编译的时候给出了一个警告。但是在Windows下和Ubuntu的g++编译器下都没有给出警告。因此如果需要,可以将代码中的recv()函数的返回值转换为int类型,这样X code编译器就不会给出警告了
下面的代码没有修改,读者如果需要可以自己去修改

最终代码
#ifdef _WIN32
	#define WIN32_LEAN_AND_MEAN
	#define _WINSOCK_DEPRECATED_NO_WARNINGS //for inet_pton()
	#define _CRT_SECURE_NO_WARNINGS    
 
	#include <windows.h>
	#include <WinSock2.h>
	#pragma comment(lib, "ws2_32.lib")
#else
        #include <unistd.h>
	#include <sys/socket.h>
	#include <sys/types.h>
	#include <arpa/inet.h>
	#include <netinet/in.h>
	#include <sys/select.h>
 
	//在Unix下没有这些宏,为了兼容,自己定义
	#define SOCKET int
	#define INVALID_SOCKET  (SOCKET)(~0)
	#define SOCKET_ERROR            (-1)
#endif
 
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <thread>
 
using namespace std;
 
enum CMD
{
	CMD_LOGIN,       
	CMD_LOGIN_RESULT, 
	CMD_LOGOUT,      
	CMD_LOGOUT_RESULT,
	CMD_NEW_USER_JOIN,
	CMD_ERROR   
};
 
struct DataHeader
{
	short cmd;       
	short dataLength;
};
 
struct Login :public DataHeader
{
	Login() {
		cmd = CMD_LOGIN;
		dataLength = sizeof(Login);
	}
	char userName[32]; 
	char PassWord[32]; 
};
 
struct LoginResult :public DataHeader
{
	LoginResult() :result(0) {
		cmd = CMD_LOGIN_RESULT;
		dataLength = sizeof(LoginResult);
	}
	int result;
};
 
struct Logout :public DataHeader
{
	Logout() {
		cmd = CMD_LOGOUT;
		dataLength = sizeof(Logout);
	}
	char userName[32];
};
 
struct LogoutResult :public DataHeader
{
	LogoutResult():result(0){
		cmd = CMD_LOGOUT_RESULT;
		dataLength = sizeof(LogoutResult);
	}
	int result;
};
 
struct NewUserJoin :public DataHeader
{
	NewUserJoin(int _cSocket = 0) :sock(_cSocket) {
		cmd = CMD_NEW_USER_JOIN;
		dataLength = sizeof(LogoutResult);
	}
	int sock;
};
 
int processor(SOCKET _cSock);
 
void cmdThread(SOCKET sock);
 
bool g_bRun = true; //表示客户端已经结束,让主循环while为false,关闭客户端套接字
 
int main()
{
#ifdef _WIN32
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
#endif
	
	//建立socket
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (INVALID_SOCKET == _sock) {
		std::cout << "ERROR:建立socket失败!" << std::endl;
	}
	else {
		std::cout << "建立socket成功!" << std::endl;
	}
 
	//声明要连接的服务端地址(注意,不同平台的服务端IP地址也不同)
	struct sockaddr_in _sin = {};
#ifdef _WIN32
	_sin.sin_addr.S_un.S_addr = inet_addr("192.168.0.105");
#else
	_sin.sin_addr.s_addr = inet_addr("192.168.0.104");
#endif
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);
 
	//连接服务端
	int ret = connect(_sock, (struct sockaddr*)&_sin, sizeof(_sin));
	if (SOCKET_ERROR == ret) {
		std::cout << "ERROR:连接服务端失败!" << std::endl;
	}
	else {
		std::cout << "连接服务端成功!" << std::endl;
	}
 
	//启动线程,线程执行函数的参数传入_sock
	std::thread t1(cmdThread, _sock);
	t1.detach();//分离线程
	/*为什么要分离线程:
		在cmdThread()中如果我们输入exit,那么会将全局变量g_bRun设置为false,
		设置为false之后,下面的while主循环就会终止,从而导致主线程结束,但是此时我们的t1线程可能还没有
		完全退出,因此需要将t1线程分理出主线程,否则客户端在输入exit的时候可能会抛出异常
	*/
 
	while (g_bRun)
	{
		fd_set fdRead;
		FD_ZERO(&fdRead);
		FD_SET(_sock, &fdRead);
 
		struct timeval t = { 1,0 };
		int ret = select(_sock + 1, &fdRead, NULL, NULL, &t);
		if (ret < 0)
		{
			std::cout << "select出错!" << std::endl;
			break;
		}
		if (FD_ISSET(_sock, &fdRead)) //如果服务端有数据发送过来,接收显示数据
		{
			FD_CLR(_sock, &fdRead);
			if (-1 == processor(_sock))
			{
				std::cout << "数据接收失败,或服务端已断开!" << std::endl;
				break;
			}
		}
		//Sleep(1000); 可以让发送与接受速度延迟1秒
		//std::cout << "空闲时间,处理其他业务..." << std::endl;
	}
 
	//关闭服务端套接字
#ifdef _WIN32
	closesocket(_sock);
	WSACleanup();
#else
	close(_sock);
#endif
 
	std::cout << "客户端停止工作!" << std::endl;
	getchar();  //防止程序一闪而过
	return 0;
}
 
int processor(SOCKET _cSock)
{
	//设置接收缓冲区,并接收命令
	char szRecv[1024];
	int _nLen = recv(_cSock, szRecv, sizeof(DataHeader), 0);
	if (_nLen < 0) {
		std::cout << "recv函数出错!" << std::endl;
		return -1;
	}
	else if (_nLen == 0) {
		std::cout << "服务端已关闭!" << std::endl;
		return -1;
	}
 
	//在此处还应该判断少包黏包的问题,但是现在处于单机处理状态,后面介绍到复杂的消息通信时再介绍
 
	//获取消息中头部中的信息
	DataHeader* header = (DataHeader*)szRecv;
	switch (header->cmd)
	{
		case CMD_LOGIN_RESULT:   //如果返回的是登录的结果
		{
			recv(_cSock, (char*)szRecv + sizeof(DataHeader), header->dataLength + sizeof(DataHeader), 0);
			LoginResult* loginResult = (LoginResult*)szRecv;
			std::cout << "收到服务端数据:CMD_LOGIN_RESULT,数据长度:" << loginResult->dataLength << ",结果为:" << loginResult->result << std::endl;
		}
		break;
		case CMD_LOGOUT_RESULT:  //如果是退出的结果
		{
			recv(_cSock, (char*)szRecv + sizeof(DataHeader), header->dataLength + sizeof(DataHeader), 0);
			LogoutResult* logoutResult = (LogoutResult*)szRecv;
			std::cout << "收到服务端数据:CMD_LOGOUT_RESULT,数据长度:" << logoutResult->dataLength << ",结果为:" << logoutResult->result << std::endl;
		}
		break;
		case CMD_NEW_USER_JOIN:  //有新用户加入
		{
			recv(_cSock, (char*)szRecv + sizeof(DataHeader), header->dataLength + sizeof(DataHeader), 0);
			NewUserJoin* newUserJoin = (NewUserJoin*)szRecv;
			std::cout << "收到服务端数据:CMD_NEW_USER_JOIN,数据长度:" << newUserJoin->dataLength << ",新用户Socket为:" << newUserJoin->sock << std::endl;
		}
		break;
	}
 
	return 0;
}
 
void cmdThread(SOCKET sock)
{
	char cmdBuf[256] = {};
	while (true)
	{
		std::cin >> cmdBuf;
 
		if (0 == strcmp(cmdBuf, "exit"))
		{
			std::cout << "客户端退出" << std::endl;
			g_bRun = false; //设置客户端已经停止运行
			break;
		}
		else if (0 == strcmp(cmdBuf, "login"))
		{
			Login login;
			strcpy(login.userName, "dongshao");
			strcpy(login.PassWord, "123456");
			send(sock, (const char*)&login, sizeof(login), 0);
		}
		else if (0 == strcmp(cmdBuf, "logout"))
		{
			Logout logout;
			strcpy(logout.userName, "dongshao");
			send(sock, (const char*)&logout, sizeof(logout), 0);
		}
		else {
			std::cout << "命令不识别,请重新输入" << std::endl;
		}
	}
}

下面是在Ubuntu 14.04上的编译结果,可以看到编译成功

g++ -g client.cpp -o client -std=c++11 -pthread

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Understand是一种功能强大的软件开发工具,它提供了多种语言和平台的静态代码分析功能,用于帮助开发人员理解和改进他们的代码质量。 5.1是Understand的一个特定版本号,它表示 Understad软件的第5个大版本中的一个小更新。 Windows64表示这个Understand版本适用于运行在Windows操作系统的64位机器上。 通过使用Understand 5.1-windows64,开发人员可以对代码进行全面的分析和评估,以确保代码的质量和可维护性。它可以帮助开发人员识别潜在的代码缺陷、冗余代码和性能问题,并提供解决方案来改善代码质量。 该版本还具备了适用于Windows 64位机器的优化和兼容性,以确保在这样的机器上能够运行和运行分析任务的平稳性。 总之,Understand 5.1-windows64是一款适用于在Windows 64位操作系统上运行的强大的软件开发工具,它为开发人员提供了全面的代码分析功能,帮助他们提高代码质量和可维护性。 ### 回答2: understand 5.1-windows64 指的是一个软件的版本和系统要求。Understand 是一款用于源代码分析的软件工具,用于帮助开发人员理解和浏览复杂的代码5.1 是这个软件的版本号,表示该软件经过一系列的更新和改进。windows64 表示该软件支持 64 位的 Windows 操作系统。 对于开发人员来说,使用 Understand 5.1-windows64 有几个优势。首先,它可以帮助他们更好地理解和分析源代码。该软件提供了多种功能,如交互式图表、图形视图和搜索功能,可以帮助开发人员深入了解代码的结构和关系,从而更高效地调试和优化代码。 其次,Understand 5.1-windows64 具有良好的兼容性,可以在 Windows 64 位系统上正常运行。64 位操作系统相比于32位操作系统具有更大的内存容量和更快的处理速度,这对于处理大型代码库和复杂的分析任务非常重要。使用 64 位系统可以提高软件工具的性能和效率。 此外,Understand 5.1-windows64 的更新版本可能还会解决了一些 bug,增加了新功能或提高了软件的稳定性。开发人员可以从该软件的版本迭代中获益。 总之,Understand 5.1-windows64 是一款功能强大的源代码分析工具,适用于 Windows 64 位系统。它可以帮助开发人员更好地理解和分析源代码,提高开发效率,并且与 64 位操作系统相对应,能够更好地支持大型代码库和复杂的分析任务。 ### 回答3: understand5.1-windows64是一款用于软件开发的工具,它是在Windows操作系统下工作的64位版本。Understand是一个功能强大的代码理解工具,它可以帮助程序员对代码进行深入的分析和理解。 使用understand5.1-windows64,程序员可以对源代码进行静态分析,了解代码的结构、功能和相互关系。它支持多种编程语言,包括C、C++、C#、Java等。程序员可以快速地浏览代码,并通过各种视图和图表来可视化分析结果。 此外,understand5.1-windows64还提供了全面的代码检查和度量功能,帮助程序员评估代码的质量和健壮性。它可以检测潜在的错误、重复的代码和性能问题,并生成报告供程序员参考和改进。 另外,understand5.1-windows64还支持代码重构和搜索功能。程序员可以通过重构工具对代码进行重构,提高代码的可读性和可维护性。搜索功能可以帮助程序员快速定位特定的代码片段,提高开发效率。 总而言之,understand5.1-windows64是一款功能强大且易于使用的代码理解工具。它提供了丰富的分析和度量功能,帮助程序员更好地理解和改进源代码。无论是初学者还是经验丰富的开发人员,都可以从中受益并提高代码开发的效率和质量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值