C++网络编程学习:跨平台支持Windows、Linux系统
- 使用的语言为C/C++
- 源码支持的平台为:Windows / Linux
一、为何要进行跨平台操作
首先,我是想在网络编程学习渐入佳境后,自己尝试做一个网络方面的项目,其中就必须用到服务器。Linux服务器相比Windows服务器更加稳定且高效,所以对于我来说,学会如何编写出可以在Linux系统下运行的网络程序是必不可少的。
其次,就目前来说,企业中的高性能网络编程都是基于Linux的,学会跨平台的网络编程技能,可以在未来就业方面等有很大的好处。
由此,我决定在网络编程学习的第四小阶段,学习如何进行跨平台的网络编程。
二、关于Win与Linux系统下网络编程的差异
差异一
在Linux环境下,程序的头文件与定义与Win环境下存在差异。
1 |
这是更改后的程序部分。
可以看出:
- Win环境下的特有头文件 <windows.h> 对应Linux环境下的特有头文件 **<unistd.h>**。
- Win环境下的网络头文件 <winSock2.h> 对应Linux环境下的特有头文件 **<arpa/inet.h>**。
- SOCKET为Win环境下的特有数据类型,其原型为unsigned __int64,所以我们在Linux下,需要简单对SOCKET进行定义。
- Linux中同样对INVALID_SOCKET与SOCKET_ERROR也没有定义,所以我们参考Win中的定义,在Linux系统下对其定义。
(此图为Win环境下_socket_types.h头文件中的相关定义)
差异二
在Linux环境下不需要使用WSAStartup与WSACleanup搭建网络环境,这是Win环境特有的。
- 所以我们只需要加上判断即可,当检测到系统环境为Win时执行即可:
1 |
|
差异三
Linux环境与Win环境下,网络通信相关结构体 sockaddr_in和sockaddr 存在差异。
最明显的差异为存储IP的结构不太一样。
- 所以我们这样更改即可:
1
2
3
4
5
_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//想要连接的IP
_sin.sin_addr.s_addr = inet_addr("127.0.0.1");//想要连接的IP
差异四
Linux环境与Win环境下,关闭套接字的函数存在差异。
Win下为**closesocket(),Linux下则简单粗暴为close()**。
- 所以我们这样更改即可:
1
2
3
4
5
6
7
//关闭socket
closesocket(_mysocket);
//关闭socket/LINUX
close(_mysocket);
差异五
Linux环境与Win环境下,服务器的accept连接函数参数存在差异。
Win下的最后一个参数为int型地址,Linux下则为socklen_t型地址。进行一次强制转换即可。
- 所以我们这样更改即可:差异六
1
2
3
4
5
_temp_socket = accept(_mysocket,(sockaddr*)&_clientAddr,&_addr_len);//自身套接字 客户端结构体 结构体大小
_temp_socket = accept(_mysocket,(sockaddr*)&_clientAddr,(socklen_t*)&_addr_len);//自身套接字 客户端结构体 结构体大小
Linux环境与Win环境下,fd_set结构体中的参数出现了变化,不再有储存socket数量的fd_count变量,所以我们需要对源码下select函数的第一个参数进行准确的数据传入。
select函数的第一个参数实际为 所有socket的最大值+1,所以我们新建一个变量,用于储存最大值。在每次对fdread集合进行导入时,找到socket的最大值,随后传入select函数即可。
- 所以我们这样更改即可:差异七
1
2
3
4
5
6
7
8
9
10
11
12SOCKET _maxSock = _mysocket;//最大socket
for(int n=_clients.size()-1; n>=0; --n)//把连接的客户端 放入read集合
{
FD_SET(_clients[n],&_fdRead);
if(_maxSock < _clients[n])
{
_maxSock = _clients[n];//找最大
}
}
//select函数筛选select
int _ret = select(_maxSock+1,&_fdRead,&_fdWrite,&_fdExcept,&_t);
Linux环境与Win环境下,fd_set结构体中的参数出现了变化,不再有储存socket数量的fd_count变量,所以我们需要对源码下面关于遍历socket的逻辑进行改变。
首先遍历 _clients 数组中的所有socket,随后使用FD_ISSET函数判定其是否存在待处理事件,如果有,即可按逻辑进行处理。
- 所以我们这样更改即可:
1
2
3
4
5
6
7
8
9
10
11
12
13
14for(int n=0; n<_clients.size(); ++n)//遍历所有socket
{
if(FD_ISSET(_clients[n],&_fdRead))//看一下是否在待处理事件列表中
{
if(-1 == _handle(_clients[n]))//处理请求 客户端退出的话
{
vector<SOCKET>::iterator iter = _clients.begin()+n;//找到退出客户端的地址
if(iter != _clients.end())//如果是合理值
{
_clients.erase(iter);//移除
}
}
}
}
三、基于笔记三源码进行 跨平台化升级
1.客户端源码
1 |
|
2.服务端源码
1 |
|
四、移植过程中遇到的一些小问题
1.关于IP的问题
如果服务端在本机Windows环境下运行,客户端在VM虚拟机Linux环境下运行,则在Windows命令行上输入ipconfig命令。下面这一块数据下的IPv4地址即为客户端需要连接的IP。
1 | 以太网适配器 VMware Network Adapter VMnet8: |
如果服务端在VM虚拟机Linux环境下运行,客户端在本机Windows环境下运行,则在Linux命令行上输入ifconfig命令。显示出来的数据中网卡的IP即为客户端需要连接的IP。
2.关于端口的问题
如果你的服务端运行正常,客户端运行正常,本机双开客户端和服务端也运行正常,但本机与虚拟机各开一个却连接不上时,可能是服务端的端口未开放导致的。
- Windows环境下会主动提示,点击允许即可,如果还是不行就去网上搜。
- Linux环境下相关命令如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19systemctl status firewalld 查看防火墙状态
systemctl start firewalld 开启防火墙
systemctl stop firewalld 关闭防火墙
service firewalld start 开启防火墙
查看对外开放的8888端口状态 yes/no
firewall-cmd --query-port=8888/tcp
打开8888端口
firewall-cmd --add-port=8888/tcp --permanent
重载端口
firewall-cmd --reload
移除指定的8888端口:
firewall-cmd --permanent --remove-port=8888/tcp