1.任务要求
在PC上同时插入多个无线USB网卡,利用5个USB网卡并行通信。
2.实验准备
- 开发环境:VS2015
- 测试工具:wireshark抓包软件(服务器端)
- 硬件条件:多个USB网卡(系统需具备相应驱动),最好是3个以上
3.项目分析
平常我们的PC都是利用一个网卡来通信(无线或者有线),应用程序也只会绑定一个网卡。但现在是实现多个网卡实现并行的收发数据!当多个无线USB网卡接入电脑,都连接同一个AP,怎么才能让自己的程序绑定接入的USB网卡、并行收发数据呢?
- 获取接入PC的USB网卡信息(名称、MAC地址、IP地址等);
- 创建多线程,一个线程绑定一个网卡,依据网卡上的信息来异步执行任务;
- 最后,Windows Socket编程,与服务器建立TCP、UDP连接,收发数据。
4.实验过程
4.1 PIP_ADAPTER_INFO结构体获取网卡信息
结构体ADAPTER_INFO用于于获取PC上网络适配器的IPv4信息,该结构体通过调用GetAdaptersInfo函数获取值。
//PIP_ADAPTER_INFO结构体指针存储本机网卡信息
PIP_ADAPTER_INFO pIpAdapterInfo = new IP_ADAPTER_INFO();
//得到结构体大小,用于GetAdaptersInfo参数
unsigned long stSize = sizeof(IP_ADAPTER_INFO);
//调用GetAdaptersInfo函数,填充pIpAdapterInfo指针变量;其中stSize参数既是一个输入量也是一个输出量
int nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
//记录网卡数量
int netCardNum = 0;
//记录每张网卡上的IP地址数量
int IPnumPerNetCard = 0;
if (ERROR_BUFFER_OVERFLOW == nRel){
//如果函数返回的是ERROR_BUFFER_OVERFLOW
//释放原来的内存空间
delete pIpAdapterInfo;
//重新申请内存空间用来存储所有网卡信息
pIpAdapterInfo = (PIP_ADAPTER_INFO)new BYTE[stSize];
//再次调用GetAdaptersInfo函数,填充pIpAdapterInfo指针变量
nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
}
if (ERROR_SUCCESS == nRel){
//输出网卡信息
getUSBWirelessAdapter(pIpAdapterInfo, IPnumPerNetCard, netCardNum);
}
//释放内存空间
if (pIpAdapterInfo){
delete pIpAdapterInfo;
}
其中getUSBWirelessAdapter(PIP_ADAPTER_INFO pIpAdapterInfo,int IPnumPerNetCard, int netCardNum)函数用与筛选出所需要的网卡和分配到的IP地址等信息,结果如下图。其中依据USB关键字在程序里筛选出USB无线网卡。
4.2 多线程创建
CreateThread函数创建多个线程,利用(LPVOID)LocalIP[i]ip地址作为参数传入每个线程。每个线程要执行的就是各自与服务器端建立TCP,传输数据。多线程创建结果如下图。
HANDLE hthread[5];//创建线程句柄
DWORD threadid[5];//保存线程的编号;
for (i = 0; i < 5; i++)
{
hthread[i] = CreateThread(
NULL,//表示线程内核对象的安全属性;
NULL,//表示线程堆栈空间的大小;
mymsg,//表示新线程所执行的线程函数地址;
(LPVOID)LocalIP[i],//函数的参数;
0,//立刻执行;
&threadid[i]//将返回线程的ID号;
);
}
4.3 socket编程
每个线程绑定到不同的ip地址,各自与服务器进行TCP通信。
DWORD WINAPI mymsg(LPVOID lp)
{
char *ip = (char *)lp;
printf("我是子线程, pid = %d\n", GetCurrentThreadId()); //输出子线程pid
/*********************
*@SOCKET配置
*********************/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0){
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in localaddr;
//要发送的数据
char sendData[] = "this is USB wireless Adapter!";
printf("%s\n", strcat(sendData, ip));
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = inet_addr(ip);//绑定本地ip地址
localaddr.sin_port = 0; // Any local port will do
bind(sclient, (struct sockaddr *)&localaddr, sizeof(localaddr));
if (sclient == INVALID_SOCKET){
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8080);//服务器端的ip地址和端口号
serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.50.155");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR){
printf("connect error !");
closesocket(sclient);
return 0;
}
//循环发送数据,便于观察
for (int i = 0; i < 200; ++i) {
send(sclient, sendData, strlen(sendData), 0);
}
//断开连接
closesocket(sclient);
WSACleanup();
return 0;
}
下图是在服务器端利用wireshark抓包的结果,可见五个网卡同时与服务器建立了连接,异步传输数据 。但后续的传输数据部分没有截图保留结果。
5 实验总结
该实验是项目之前的小测试,需求感觉很奇怪。。。完成只用了一个晚上,算是比较轻松。
程序需要完善的地方有很多!!!
- 多个网卡连接热点需要手动输入密码去连接,对多个网卡来说,太繁琐了。最好在程序里预先定义好,只要插入一个USB网卡就连接到AP,进而创建线程开始数据传输,这样效率更高。
- socket编程只进行了一个TCP数据的发送,没有进行数据的接收。服务器端的程序也没来得及去编写,以后有时间再说吧~~
- 线程只是绑定了网卡上的IP,不知道能不能像Linux系统那样直接绑定到网卡。
- 不对,多个线程对应的是同一个ip地址和端口,在服务器端,数据包的处理还是要排队的,并不是真正的并行。
6 代码
#include <stdio.h>
#include <stdlib.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <process.h>
#include <iostream>
#include <string>
#include <WinSock2.h>
#include <Iphlpapi.h>
using namespace std;
#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"Iphlpapi.lib") //需要添加Iphlpapi.lib库
char LocalIP[5][16];
int ipadd = 0;
DWORD WINAPI mymsg(LPVOID lp)
{
char *ip = (char *)lp;
printf("我是子线程, pid = %d\n", GetCurrentThreadId()); //输出子线程pid
/*********************
*@SOCKET配置
*********************/
WORD sockVersion = MAKEWORD(2, 2);
WSADATA data;
if (WSAStartup(sockVersion, &data) != 0)
{
return 0;
}
SOCKET sclient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
struct sockaddr_in localaddr;
char sendData[] = "this is USB wireless Adapter!";
printf("%s\n", strcat(sendData, ip));
localaddr.sin_family = AF_INET;
localaddr.sin_addr.s_addr = inet_addr(ip);
localaddr.sin_port = 0; // Any local port will do
bind(sclient, (struct sockaddr *)&localaddr, sizeof(localaddr));
if (sclient == INVALID_SOCKET)
{
printf("invalid socket !");
return 0;
}
sockaddr_in serAddr;
serAddr.sin_family = AF_INET;
serAddr.sin_port = htons(8080);
serAddr.sin_addr.S_un.S_addr = inet_addr("192.168.50.155");
if (connect(sclient, (sockaddr *)&serAddr, sizeof(serAddr)) == SOCKET_ERROR){
printf("connect error !");
closesocket(sclient);
return 0;
}
//发送数据
for (int i = 0; i < 200; ++i) {
send(sclient, sendData, strlen(sendData), 0);
}
//接收服务器返回字符串
/* char recData[255];
int ret = recv(sclient, recData, 255, 0);
if (ret > 0)
{
recData[ret] = 0x00;
printf(recData);
}*/
closesocket(sclient);
WSACleanup();
return 0;
}
void getUSBWirelessAdapter(PIP_ADAPTER_INFO pIpAdapterInfo,int IPnumPerNetCard, int netCardNum) {
//可能有多网卡,因此通过循环去判断
while (pIpAdapterInfo)
{
//识别当前接入的USB网卡
if (strstr(pIpAdapterInfo->Description, "USB")) {
cout << "网卡描述:" << pIpAdapterInfo->Description << endl;
//输出网卡物理地址
cout << "网卡MAC地址:";
for (int i = 0; i < pIpAdapterInfo->AddressLength; i++)
if (i < pIpAdapterInfo->AddressLength - 1)
{
printf("%02X-", pIpAdapterInfo->Address[i]);
}
else {
printf("%02X\n", pIpAdapterInfo->Address[i]);
}
cout << "网卡IP地址如下:" << endl;
//可能网卡有多IP,因此通过循环去判断
IP_ADDR_STRING *pIpAddrString = &(pIpAdapterInfo->IpAddressList);
do
{
cout << "IP 地址:" << pIpAddrString->IpAddress.String << endl;
strcpy(LocalIP[IPnumPerNetCard], pIpAddrString->IpAddress.String);
cout << "该网卡上的IP数量:" << ++IPnumPerNetCard << endl;
cout << "子网掩码:" << pIpAddrString->IpMask.String << endl;
cout << "网关地址:" << pIpAdapterInfo->GatewayList.IpAddress.String << endl;
pIpAddrString = pIpAddrString->Next;
} while (pIpAddrString);
cout << "--------------------------------------------------------------------" << endl;
}
netCardNum++;
pIpAdapterInfo = pIpAdapterInfo->Next;
}
cout << "网络设备:" << netCardNum << " 台" << endl;
}
int main(int argc, char* argv[])
{
/*********************
*@多线程配置
*********************/
int i;
HANDLE hthread[5];
DWORD threadid[5];//保存线程的编号;
/*
STARTUPINFO si = { sizeof(STARTUPINFO) };//在产生子进程时,子进程的窗口相关信息
PROCESS_INFORMATION pi; //子进程的ID/线程相关信息
DWORD returnCode;//用于保存子程进的返回值;
*/
/*********************
*@网卡信息获取
*********************/
//PIP_ADAPTER_INFO结构体指针存储本机网卡信息
PIP_ADAPTER_INFO pIpAdapterInfo = new IP_ADAPTER_INFO();
//得到结构体大小,用于GetAdaptersInfo参数
unsigned long stSize = sizeof(IP_ADAPTER_INFO);
//调用GetAdaptersInfo函数,填充pIpAdapterInfo指针变量;其中stSize参数既是一个输入量也是一个输出量
int nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
//记录网卡数量
int netCardNum = 0;
//记录每张网卡上的IP地址数量
int IPnumPerNetCard = 0;
if (ERROR_BUFFER_OVERFLOW == nRel)
{
//如果函数返回的是ERROR_BUFFER_OVERFLOW
//则说明GetAdaptersInfo参数传递的内存空间不够,同时其传出stSize,表示需要的空间大小
//这也是说明为什么stSize既是一个输入量也是一个输出量
//释放原来的内存空间
delete pIpAdapterInfo;
//重新申请内存空间用来存储所有网卡信息
pIpAdapterInfo = (PIP_ADAPTER_INFO)new BYTE[stSize];
//再次调用GetAdaptersInfo函数,填充pIpAdapterInfo指针变量
nRel = GetAdaptersInfo(pIpAdapterInfo, &stSize);
}
if (ERROR_SUCCESS == nRel)
{
//输出网卡信息
getUSBWirelessAdapter(pIpAdapterInfo, IPnumPerNetCard, netCardNum);
}
//释放内存空间
if (pIpAdapterInfo)
{
delete pIpAdapterInfo;
}
for (int i = 0; i < 5; i++) {
//当前获取到的IP地址
puts(LocalIP[i]);
}
for (i = 0; i < 5; i++)
{
hthread[i] = CreateThread(
NULL,//表示线程内核对象的安全属性;
NULL,//表示线程堆栈空间的大小;
mymsg,//表示新线程所执行的线程函数地址;
(LPVOID)LocalIP[i],//函数的参数;
0,//立刻执行;
&threadid[i]//将返回线程的ID号;
);
}
//mymsg(NULL);
for (i = 0; i < 5; i++) {
CloseHandle(hthread[i]);
}
system("pause");
return 0;
}