包含头文件
网络编程在Windows平台上有俩个主要版本:Winsock1和Winsock2
#include <WinSock.h>
#pragma comment(lib, "WSock32.Lib")
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")
//获取处理计算机硬件设备信息
#include <IPHlpApi.h>
#pragma comment(lib, "IPHLPAPI.lib")
//自定义的报文结构体
typedef struct INFO_SCAN_t{
unsigned short header;
unsigned short dev_number;
unsigned short dev_ver;
unsigned short expand_len;
}INFO_SCAN;
#define SCANPORT 6000 自定义端口号
代码:
WORD socketVersion = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(socketVersion, &wsaData) == 0){
qDebug()<<"确认网络模块加载成功,可以进行网络通信";
}
WORD:typedef unsigned short
MAKEWORD(2,2)://MAKEWORD的作用是将十进制数转成2进制,再按位拼接到一起形成:00000010 00000010;返回值(10 00000010)514
struct WSAData {//Windows Sockets数据
WORD wVersion;//Windows sockets DLL将使用的版本;高位字节存储副版本号,低位字节存储主版本号;可以用WORD MAKEWORD(BYTE,BYTE)返回这个值
WORD wHighVersion;//这个DLL能够支持的Windows sockets规范的最高版本,基本与wVersion相同
char szDescription[WSADESCRIPTION_LEN+1];//以null结尾的ASCLL字符串,DLL将Windows sockets实现的描述拷贝到这个字符串
char szSystemStatus[WSASYS_STATUS_LEN+1];//...把有关的状态或配置信息拷贝到该字符串
unsigned short iMaxSockets;//单个进程能够打开的socket的最大数目
unsigned short iMaxUdpDg;//应用程序能够发送或接收的最大的用户数据包协议(UDP)的数据包大小(字节),默认0;
char* lpVendorInfo;//制造商信息(没用)
} WSADATA
WSAStartup:载入合适的Winsock动态链接库,向操作系统说明我们要用那个库文件,加载套接字库,返回0加载成功
//获取本地IP地址
char szText[256];
int iRet;
iRet = gethostname(szText, 256);
HOSTENT *host = gethostbyname(szText);
char *p = host->h_addr_list[0];
in_addr ip_addr;//
memcpy(&(ip_addr.S_un.S_addr), p, host->h_length);
string ip = ::inet_ntoa(ip_addr);//本地IP地址
qDebug()<<"local IP:"<<QString::fromStdString(ip);
int gethostname(char*name,int namelen); //得到本机主机名或域名,参数是:来存放主机名的变量,缓冲区的大小
struct hostent gethostbyname( const char * name); //*name是域名或主机名,返回值是hostent结构,错误返回NULL;
struct hostent {
char * h_name;//主机规范名
char ** h_aliases;//主机别名(可以有多个)
short h_addrtype;//主机IP类型(ipv4(AF_INET),ipv6(AF_INET6))
short h_length;//主机IP地址的长度
char **h_addr_list;//主机IP地址,以网络自序存储的,打印需要调用inet_ntop()/inet_ntoa()
#define h_addr h_addr_list[0]//
};
表示一个32位的IPv4地址,
struct in_addr {
union { //255. 255. 255. 0
struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { USHORT s_w1,s_w2; } S_un_w;
ULONG S_addr;//按照网络字节顺序存储的IP地址
} S_un;
//通过本机IP->mark地址->广播地址
in_addr mask_addr;
string mask = GetMaskFromIp(ip);//获取该地址的子网掩码
mask_addr.S_un.S_addr = inet_addr(mask.c_str());//将string子网掩码地址,转成in_addr(将点分制IP:192.168.0.1转成结构体所需的32位二进制方式的IP(0xC0A80001))
ULONG domain_ul = ip_addr.S_un.S_addr & mask_addr.S_un.S_addr;
//网络顺序的IP广播地址:192.168.0.255
ULONG addr_ul = (~(mask_addr.S_un.S_addr)) | domain_ul;
//.分广播IP,测试用
in_addr m_ip;
m_ip.S_un.S_addr=addr_ul;
ip = ::inet_ntoa(m_ip);
qDebug()<<"m_local IP:"<<QString::fromStdString(ip);
string Widget::GetMaskFromIp(const string &ip)
{
string ret;
PIP_ADAPTER_INFO pAdapterInfo;
PIP_ADAPTER_INFO pAdapter = NULL;
ULONG ulOutBufLen = sizeof (IP_ADAPTER_INFO);
pAdapterInfo = (IP_ADAPTER_INFO *)malloc( sizeof(IP_ADAPTER_INFO) );
if( ERROR_BUFFER_OVERFLOW == GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) )
{
free(pAdapterInfo);
pAdapterInfo = (IP_ADAPTER_INFO *)malloc( ulOutBufLen );
}
if( NO_ERROR == GetAdaptersInfo(pAdapterInfo, &ulOutBufLen) )
{
pAdapter = pAdapterInfo;
while(pAdapter)
{
if(ip == pAdapter->IpAddressList.IpAddress.String)
{
ret = pAdapter->IpAddressList.IpMask.String;
break;
}
pAdapter = pAdapter->Next;
}
}
if( pAdapterInfo )
{
free( pAdapterInfo );
}
return ret;
}
//设置对方的IP,端口
struct sockaddr_in sin;
sin.sin_family = AF_INET;//协议族IPv4
sin.sin_port = htons(SCANPORT);//htons将主机字节序转为网络字节序,端口号
sin.sin_addr.S_un.S_addr = addr_ul;//网络顺序的广播IP地址
struct sockaddr_in {
short sin_family;//指代协议族 AF_INET
USHORT sin_port;//端口号(网络字节序)
IN_ADDR sin_addr;//存储IP地址,使用in_addr数据结构
CHAR sin_zero[8];//为了让sockaddr和sockaddr_in俩个数据结构保持大小相同而保留的空字节
}
//指定本机IP,端口
struct sockaddr_in sinlocal;
sinlocal.sin_family = AF_INET;//协议族IPv4
sinlocal.sin_port = htons(SCANPORT);//htons将主机字节序转为网络字节序,端口号
sinlocal.sin_addr = (ip_addr);
//要发送的数据进行初始化
char sendData[128];
memset(sendData, 0x0, sizeof(sendData));//数组清空
INFO_SCAN info_scan;//报文头结构体赋值(发送的数据)
info_scan.header = 0xAA55;
info_scan.dev_number = 0x0001;
info_scan.dev_ver = 0x0001;
info_scan.expand_len = 0x0;
memcpy(sendData, &info_scan, sizeof(info_scan));
//将数据发送出去sendto
int sclient = socket(AF_INET, SOCK_DGRAM, 0);//SOCK_DGRAM指定为UDP
iRet = bind(sclient, (struct sockaddr*)&sinlocal, sizeof(struct sockaddr_in));//绑定本机信息
iRet = sendto(sclient, sendData, 128, 0, (sockaddr *)&sin, sizeof(sin));
// 创建一个能够进行网络通信的套接字
int socket(int domain,int type,int protocol);
domain协议族:AF_UNIX(本机通信)、AF_INET(TCP/IP-IPv4)、AF_INET6(TCP/IP-IPv6)
type套接字类型:SOCK_STREAM(TCP流)、SOCK_DGRAM(UDP数据报)、SOCK_RAM(原始套接字)
protocol:一般设为0,在domain参数未知的情况下,可以确定协议的种类
// 调用socket函数创建套接字后,bind函数负责将套接字与本机地址和端口等信息相连
int bind(int sockfd,const struct sockaddr*my_addr,socklen_t addrlen);
sockfd:调用socket函数后返回的文件描述符(返回值)
my_addr:指向sockaddr结构体的指针(该结构体中保存有端口和IP地址信息)
addrlen:结构体sockaddr的长度
//用来将数据由指定的socket传给对方主机(专用于UDP)
int sendto(int s,const void*msg,int len,unsigned int flags,const struct sockaddr*to,int tolen);
s:为已建好连线的socket,如果利用UDP协议则不需要经过连线操作,
mag:指向欲连线的数据内容
flags:一般设0
to:用来指定欲传送的网络地址
tolen:sockaddr的结果长度
//接收部分
struct sockaddr_in sin_recv;//定义接收时的协议,端口等
int sclient_recv = socket(AF_INET, SOCK_DGRAM, 0/*UDP*/);
sin_recv.sin_family = AF_INET;
sin_recv.sin_port = htons(SCANPORT+1);
sin_recv.sin_addr.s_addr = htonl(INADDR_ANY);//设置接收时来者不拒所有IP都收
//非阻塞模式
ULONG iMode = 1;
ioctlsocket(sclient_recv,FIONBIO,&iMode);
//将网络设置为非阻塞模式
int ioctlsocket(SOCKET s,long cmd,ulong*argp);
s:一个标识套接口的描述字
cmd:对套接口s的操作命令;FIONBIO(允许或禁止套接口s的非阻塞模式)、FIONREAD(确定套接口s自动读入的数据量)、SIOCATMARK(确定是否所有的带外数据都已被读入)
argp:指向cmd命令所带参数的指针(0控制为阻塞方式,1控制为非阻塞方式)
//绑定接收套接字的信息
iRet = bind(sclient_recv, (struct sockaddr*)&sin_recv, sizeof(struct sockaddr_in));
fd_set l_reads;//要接收的IP信息集合,最多64个
FD_ZERO(&l_reads);//将l_reads清空
FD_SET(sclient_recv, &l_reads);//将sclient加入l_reads集合
//控制等待时间
timeval l_timeout;
l_timeout.tv_sec = 5;
l_timeout.tv_usec = 0;
FD_ZERO(&set);//清空
FD_SET(fd,&set);//将fd加入set集合
FD_CLR(fd,&set);//将fd从set集合中清除
FD_ISSET(fd,&set);//测试fd是否在set集合中
//接收信息
while(1){
//检查fd_set里的socket是否有信号到来
int l_nErr = select(sclient_recv+1, &l_reads, NULL, NULL, &l_timeout);
if(l_nErr<=0) break;
if(FD_ISSET(sclient_recv, &l_reads)){
// FD_ISSET判断fd_set里的具体ip,socket是哪个
struct sockaddr_in sin;//记录接收到远程数据的计算机的IP
int len = sizeof(sin);
char recvData[128];
memset(recvData,0,128);
//获取到的数据包解析出,数据,发送端IP
iRet = recvfrom(sclient_recv, recvData, 128, 0, (sockaddr *)&sin, &len); //0 flags MSG_DONTWAIT //操作不会被阻塞 MSG_WAITALL //要求阻塞操作 or
if(iRet==-1){
break;
}
//将接收到的数据解析给结构体
INFO_SCAN info_scan;
memset(&info_scan, 0x0, sizeof(info_scan));
memcpy(&info_scan, recvData, sizeof(info_scan));
if(info_scan.header == 0x55AA && info_scan.dev_number == 0x0001 && info_scan.dev_ver == 0x0001){
in_addr m_ip;
m_ip=sin.sin_addr;
ip = ::inet_ntoa(m_ip);
qDebug()<<"receive data success"<<QString::fromStdString(ip);
QString str=QString::fromStdString(ip);
}
}
}
qDebug()<<"success";
// 测试指定的fd可读?可写?有异常条件待处理?判断有没有set中的socket信息接收到
int select(int nfds,fd_set*readset,fd_set*writeset,fd_set*exceptset,struct timeval*timeout);
nfds:需要检查的文件描述字个数(检查到fd_set的第几位)
readset:用来检查可读性的一组文件描述字
writeset:用来检查可写性的一组文件描述字
exceptset:用来检查是否有异常条件出现的文件描述字(错误不包括在异常条件内)
timeout:NULL(阻塞,直到有一个fd位被置为1函数才返回)
timeout所指向的结构设为非零时间,等待固定时间,有一个fd位被置为1或者时间耗尽,函数均返回
timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
返回值:返回对应位扔为1的fd的总数