0
点赞
收藏
分享

微信扫一扫

项目(百万并发网络通信架构)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系统中运行时,我们也手动在程序中进行了宏定义

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_#define

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_#include_02

  • 修订代码如下:

#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;
}
}
}

四、在Windows下进行编译演示


  • 下面是在VS Studio下的编译结果,编译成功

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_#include_03

  • 备注:连接服务端失败是因为我们没有开启服务器



演示效果

  • 下面开启一个服务端(注意客户端代码中的服务端IP地址一定要正确),然后在Windows下开启一个客户端,可以看到成功

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_#define_04


五、在Ubuntu下进行编译演示


编译如下

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

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

 项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_#define_05



演示效果

  • 备注:客户端代码中的服务端IP一定要设置正确
  • 此处演示时,Windows端的IP地址为192.168.0.104,Ubuntu的IP地址为192.168.0.105

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_服务端_06

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_#include_07

  • 在Windows下开启服务端,然后在Ubuntu下启动客户端。之后进行交互,可以看到一切正常

项目(百万并发网络通信架构)5.1---将客户端代码进行跨平台移植(Windows、Unix)_服务端_08


六、在Mac OS下进行编译演示

  • 原理与Ubuntu相同,由于身边没有Mac OS系统,待续


举报

相关推荐

0 条评论