C++:网络通讯遇到的几个问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/dashumak/article/details/84451119

将网络通讯之间的几个问题记录下来,希望能帮助大家

一、udp的recvfrom无法接收数据

最近遇到一个很奇怪的问题,现象如下:

  • 本地电脑(win10)的客户端和服务器之间可以通过udp通讯
  • 当客户端和服务器不在一个电脑上,程序会卡在udp的recvfrom函数上,但是调试助手是可以转到包的
  • 换了一台win7的电脑recvfrom函数可以收到服务器发来的数据
    这个问题不是程序的问题,毕竟调试助手不是收不到包的,应该把防火墙关闭然后重新测试一下程序。
    开始我只退了杀毒软件,然后也在防火墙里允许了程序通过,但还是不行,最后把防火墙彻底关闭(杀毒软件可不退)才可以的,防火墙对这种自己写的通信程序(感觉针对的是一些端口)影响很大。

二、sin_addr.s_addr和sin_addr.S_un.S_addr

.....
    bool isSelected=TRUE;
    int OLCSock = socket(AF_INET, SOCK_STREAM, 0);//AF_INET代表IPv4 Internet协议;SOCK_STREAM代表TCP流
    if (OLCSock < 0) {
        AfxMessageBox("创建IP4TCP套接字失败");
        exit(1);
    }
    struct sockaddr_in  OLCRec;
    OLCRec.sin_family = AF_INET;//地址族  
    OLCRec.sin_port = htons(2668);//设置端口号
    if ( isSelected)
        OLCRec.sin_addr.s_addr = htonl(INADDR_ANY);//事实上绑定的是所有分配给本地的Ip地址(一个网卡可以分配到多个IP,一台电脑也可以有多个网卡)
    else {
        CString OLCIp="192.168.0.125";//设置IP地址
        OLCRec.sin_addr.S_un.S_addr = inet_addr(OLCIp);//绑定指定IP(就是说分配到本地的多个Ip中特定的一个,也就是192.168.0.125)
    }
    DWORD   optval = 1;
    setsockopt(OLCSock, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval));
    bind(OLCSock, (LPSOCKADDR)&OLCRec, sizeof(OLCRec));

当时看到这段代码的时候很疑惑,if条件语句之后为指定IP,什么一个指定是OLCRec.sin_addr.s_addr,一个指定的是OLCRec.sin_addr.S_un.S_addr。后来在msdn查结构体sockaddr_in,定义如下

struct sockaddr_in {
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};

这里边有一个结构体变量sin_addr,所属结构体是in_addr,而结构体in_addr的定义是

typedef struct in_addr {  
       union {    
                 struct {      u_char s_b1,s_b2,s_b3,s_b4;    } S_un_b;    
                 struct {      u_short s_w1,s_w2;    } S_un_w;    
                 u_long S_addr;  
                } S_un;
} in_addr;

当然这些不重要,重要的在这里

#define s_addr  S_un.S_addr   /* can be used for most tcp & ip code */
#define s_host  S_un.S_un_b.s_b2   /* host on imp */
#define s_net   S_un.S_un_b.s_b1  /* network */
#define s_imp   S_un.S_un_w.s_w2     /* imp */
#define s_impno S_un.S_un_b.s_b4      /* imp # */
#define s_lh    S_un.S_un_b.s_b3    /* logical host */

看到第一行了没,S_un.S_addrs_addr的宏定义,这两个是一个东西(注意大小写呦,MMP~~)


三、借助IPHLPAPI函数添加/删除临时Ip4地址

#pragma comment(lib, "IPHLPAPI.lib") //用到的库,这是一种从代码中配置库的方法
#include <winsock2.h>
#include <iphlpapi.h>
#include <stdio.h>
CString BaseIp =“192.168.0.”
CString str=“1”;
void AddIP4Function()//添加Ip
{
    //**************找到适配器
    PMIB_IPADDRTABLE pIPAddrTable;//一张Ipv4地址入口表,也就是与网络适配器的IP4页面对应
    DWORD dwSize = 0;
    DWORD dwRetVal = 0;//用于检错,装载函数AddIPAddress()返回值
    pIPAddrTable = (MIB_IPADDRTABLE *)malloc(sizeof(MIB_IPADDRTABLE));//为结构分配内存
    
    //***********调用GetIpAddrTable获取适当的dwSize变量大小
    if (GetIpAddrTable(pIPAddrTable, &dwSize, 0) == ERROR_INSUFFICIENT_BUFFER) {
        //第一次调用GetIpAddrTable函数是有意的失败调用,用以确定足够的dwSize缓冲区大小,以缓冲返回至pIPAddrTable的所有数据。这种方法是此类结构和函数的常用编程模式。
        free(pIPAddrTable);
        pIPAddrTable = (MIB_IPADDRTABLE *)malloc(dwSize);
    }
    if ((dwRetVal = GetIpAddrTable(pIPAddrTable, &dwSize, 0)) == NO_ERROR) {
  		  //第二次调用GetIpAddrTable,如果成功才可以添加ip4地址

    } else {
    	    //错误信息返回至DWORD变量dwRetVal,可用于更多高级的错误检查。
      	    CString tcp_error_str;
            tcp_error_str.Format("创建%s号IP失败!错误码:%d", str, dwRetVal);
            AfxMessageBox(tcp_error_str);
       	     if (pIPAddrTable)
           	 free(pIPAddrTable);
       	     return ;
    }
    
    //*****************添加Ip4地址
    CString AddIp = BaseIP + str;
    UINT iaIPAddress = inet_addr(AddIp);
    UINT imIPMask = inet_addr("255.255.255.0");//创建的子网掩码
    if ((dwRetVal = AddIPAddress(iaIPAddress,//要添加的Ip地址
                                 imIPMask,//IP地址的子网掩码
                                 pIPAddrTable->table[0].dwIndex, //增加IP地址的适配器,实际值为MIB_IPADDRTABLE.table(适配器编号).dwIndex
                                 & ((CVDPUApp *)AfxGetApp())->NTEContext[SelNode],//添加IP地址成功则指向一个与这个IP地址关联的网络表接口(Net Table Entry:NTE)ULONG变量。调用者可以在稍后使用这个关系到调用DeleteIPAddress。
                                 & ((CVDPUApp *)AfxGetApp())->NTEInstance[SelNode])) == NO_ERROR) { //NTEInstance:[输出]成功则指向这个IP地址的网络表接口(Net Table Entry:NTE)
        //返回值dwRetVal:成功,返回0;失败,返回错误代码。
    } else {
        if (dwRetVal != 5010) { //错误码:5010 ERROR_OBJECT_ALREADY_EXISTS 表示需要添加的IP地址在函数执行之前已经存在
            CString tcp_error_str;
            tcp_error_str.Format("创建%s号IP失败!错误码:%d", str, dwRetVal);
            AfxMessageBox(tcp_error_str);
            if (pIPAddrTable)
                free(pIPAddrTable);
            return;
        }
    }
    if (pIPAddrTable)
        free(pIPAddrTable);
    //********************************
 }
void DeleteIP4Function()//删除创建的IP
{
    int SelNode = atoi(str);
    DWORD dwRetVal = 0;
    if ((dwRetVal = DeleteIPAddress(((CVDPUApp *)AfxGetApp())->NTEContext[SelNode])) == NO_ERROR) {
    //NTEContext:IP地址关联的网络表接口,这个关联是之前用AddIPAddress所创建的,在调用函数GetAdaptersInfo后,从获得的IP_ADAPTER_INFO. IpAddressList. Context 中可获得这个参数的值
    //返回值dwRetVal:成功,返回0;失败,返回错误代码。
    } else {
        AfxMessageBox("删除" + str + "号IP失败!请从文件中删除!");
    }
}

备注:

  • 增加的IP是临时的,当系统重新启动或者发生其它的PNP事件的时候这个IP就不存在了,比如将网卡禁用,然后启用,就会发现之前调用函数AddIPAddress增加的的IP地址不存在了。
  • 调用函数AddIPAddress,可能造成网络出错、系统Arp映射错误等,但可以禁用\启用网卡恢复成正常状态。
  • 函数DeleteIPAddress只能删除由函数AddIPAddress创建的IP地址。
  • 检查Ip4地址方式:网络连接>>更改设配器选项>>找到对应的适配器,“左键”双击>>详细信息可以看到。
    在这里插入图片描述
  • 增加的IP地址是不能在平常改ip4地址的界面(包括高级)中看到的,那里边记录的都是手动人为添加的地址,而且禁用\启用网卡也不会自动删除,只能通过(3)查看
    在这里插入图片描述
  • 如果想进一步理解,可以参看这两个链接 Iphlpapi获取网络信息不重起Windows直接更改IP地址以及msdn对这两个函数的官方解释AddIPAddressDeleteIPAddress。常见的错误码宏定义对应的数字如下表所示:
  • AddIPAddress返回值
宏对应的十进制数 解释
NO_ERROR 0 函数成功
ERROR_DEV_NOT_EXIST 55 IfIndex参数指定的适配器不存在
ERROR_DUP_DOMAINNAME 1221 要在Address参数中指定的要添加的IPv4地址已存在
ERROR_GEN_FAILURE 31 Address参数取值不合适返回此错误,例如添加的IPv4地址是当前网段的广播地址时
ERROR_INVALID_HANDLE 6 尝试进行函数调用的用户不是管理员,即权限不够
ERROR_INVALID_PARAMETER 87 一个或多个参数无效。 如果NTEContext或NTEInstance参数为NULL,则返回此错误。 当Address参数中指定的IP地址与IfIndex参数中指定的接口索引不一致时,也会返回此错误(例如,非环回接口上的环回地址)
ERROR_NOT_SUPPORTED 50 运行它的Windows版本不支持函数调用
ERROR_OBJECT_ALREADY_EXISTS 5010 这个也是说IP4地址已存在*(不太明白和ERROR_DUP_DOMAINNAME有啥区别*)
  • DeleteIPAddress返回值
宏对应的十进制数 解释
ERROR_ACCESS_DENIED 5 访问被拒绝
ERROR_INVALID_PARAMETER 87 输入参数无效,未执行任何操作
ERROR_NOT_SUPPORTED 50 未在本地设备上配置IPv4传输

这些宏定义都在winerror.h文件里

四、写一个解析报文的小例子

void CDateBaseView::OnDataConstraint()
{
    int nLength = 0;
    char *pBuf = new char[50];
    char *pPoint = pBuf;
    char *tBuf = new char[50];
    char *tPoint = tBuf;

    //发报文
    //(1)
    CString order = "ABD"; //此处改为可以多次强制
    strcpy(pBuf, order); 
    pBuf = pBuf + 3;
    nLength += 3;
    //(2)
    *((unsigned int *)pBuf) = pObj->GetPosInf(pObj->m_nPosDes, 0); //pBuf(2)——站号
    pBuf += sizeof(unsigned int);
    nLength += sizeof(unsigned int);
    //(3)
    *((int *)pBuf) = 1; //pBuf(3)——数据类型
    pBuf += sizeof(int);
    nLength += sizeof(int);
    //(4)
    *((unsigned int *)pBuf) = pObj->m_nPosDes; //pBuf(4)——ID号,好像也是索引号
    pBuf += sizeof(unsigned int);
    nLength += sizeof(unsigned int);
    //(5)
    *((float *)pBuf) = dlg.m_ConVal; //pBuf(5)——强制值,curval
    pBuf += sizeof(float);
    nLength += sizeof(float);
    //(6)
    *((int *)tBuf) = nLength + sizeof(int); //tBuf(1)——tBuf的总长length
    tBuf += sizeof(int);
    //(0)
    memcpy(tBuf, pPoint, nLength); //tBuf(1)之后紧跟整个pBuf
    nLength += sizeof(int);

    //////////////////////////////////////////////////////////////////////////
    char *p_printf = tPoint;
    printf("nLength=%d\n", nLength);

    //解析报文
    //(0)
    printf("1-%d int型 tPoint长度=%d\n", sizeof(int), (*(int *)p_printf));
    p_printf += sizeof(int); //字节长度
    //(1)
    printf("%d char型 命令字1=%c\n", sizeof(int) + 1, *p_printf);
    p_printf += 1; //命令字
    //(2)
    printf("%d char型 命令字2=%c\n", sizeof(int) + 2, *p_printf);
    p_printf += 1; //命令字
    //(3)
    printf("%d char型 命令字3=%c\n", sizeof(int) + 3, *p_printf);
    p_printf += 1; //命令字
    //(4)
    printf("%d-%d uint型 tPoint站号=%u\n", sizeof(int) + 4, sizeof(int) + sizeof(unsigned int) + 3, *(unsigned int *)p_printf);
    p_printf += sizeof(unsigned int); //站号
    //(5)
    printf("%d-%d int型 tPoint数据类型=%d\n", sizeof(int) + sizeof(unsigned int) + 4, sizeof(int) + sizeof(unsigned int) + sizeof(int) + 3, *(int *)p_printf);
    p_printf += sizeof(int); //数据类型
    //(6)
    printf("%d-%d uint型 tPointID号=%u\n", sizeof(int) + sizeof(unsigned int) + sizeof(int) + 4, sizeof(int) + sizeof(unsigned int) + sizeof(int) + sizeof(unsigned int) + 3, *(unsigned int *)p_printf);
    p_printf += sizeof(unsigned int); //ID号
    //(7)
    printf("%d-%d f型 tPoint强制值=%f\n", sizeof(int) + sizeof(unsigned int) + sizeof(int) + sizeof(unsigned int) + 4, sizeof(int) + sizeof(unsigned int) + sizeof(int) + sizeof(unsigned int) + sizeof(float) + 3, *(float *)p_printf);
    p_printf += sizeof(float); //强制值

猜你喜欢

转载自blog.csdn.net/dashumak/article/details/84451119
今日推荐