Windows网络通信

包含头文件

网络编程在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后立即返回)

    返回值:返回对应位扔为1fd的总数

发布了104 篇原创文章 · 获赞 22 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/qq_41672557/article/details/103303024
今日推荐