极客工坊

 找回密码
 注册

QQ登录

只需一步,快速开始

查看: 25721|回复: 2

ENC28J60 RJ45库运行时DNS解析超长及browseUrl返回512B内容

[复制链接]
发表于 2013-7-9 22:48:37 | 显示全部楼层 |阅读模式
本帖最后由 冰炭寒酒 于 2013-7-9 22:54 编辑

使用arduino/mircro-arduino的ENC28J60及RJ45网口模块时, 一般采用 EtherCard 库 ,在IDE环境中增加库后,无需其他配置,就可以试着运行EtherCard中的sample示例,比如 getDHCPandDNS范例。但在一些环境下,程序运行时有下面两个问题:

问题1.执行到ether.dnsLookup(website)即解析给定的域名时,有些环境DNS解析这句大约超过30秒,甚至失败而执行 Serial.println("DNS failed"); 或此后的ether.printIp("Server: ", ether.hisip) 打印解析的ip为0.0.0.0   
有些同学尝试把ether.dnsLookup(website) 改在loop代码中不断去取,其实这是有副作用的,并不可取,因为库文件注释里写道:
// use during setup, as this discards all incoming requests until it returns
bool EtherCard::dnsLookup (prog_char* name, bool fromRam)
也就是说,dnsLookup调用会压制其他的收发包处理。所以最好在setup中执行。

那么经过跟踪,最终查到问题在于:
库文件里,HOME_PATH\Arduino\libraries\ethercard\dns.cpp中,有一段如下代码:

bool EtherCard::dnsLookup (prog_char* name, bool fromRam) {

  word start = millis();
  while (!isLinkUp() || clientWaitingGw()) {
    packetLoop(packetReceive());
    if ((word) (millis() - start) >= 30000){
                   return false;
          }
  }
...
}


跟踪while中isLinkUp和clientWaitingGw这两个方法,分别是:1.不断测试物理端口状态,2.DHCP收到的网关IP地址通过ARP获取mac地址。如果这两步没有完成,将会一直循环,处理packet,直到30秒超时。 就是这里往往产生超时,返回错误。
奇怪的是,这两个方法有相关性,在一起执行时往往时间很长,分开执行,时间很快。

所以将上面代码修改一下,变为:
...
while(!isLinkUp()){
packetLoop(packetReceive());
    if ((word) (millis() - start) >= 30000){
                   return false;
          }

}

while(!clientWaitingGw()){
packetLoop(packetReceive());
    if ((word) (millis() - start) >= 30000){
                   return false;
          }

}
...



如果不想改库文件,其实,在setup中判断一下这两个方法的状态,当都这两个方法都执行结束之后再调用dnsLoopUp就可以了。


问题2.   还是这个示例程序getDHCPandDNS, loop方法中的 ether.browseUrl(),每次在callback中返回的长度总是小于512字节(包含http head总共小于512B),即使访问的页面大于512B.
原因在于:

还是库文件 HOME_PATH\Arduino\libraries\ethercard\tcpip.cpp 中的packetLoop()方法中,对默认状态下的连接会调用
make_tcp_ack_from_any(len,TCP_FLAGS_PUSH_V|TCP_FLAGS_FIN_V);  熟悉TCP帧格式的同学应该知道,TCP_FLAGS_FIN_V 表示断开一个TCP连接。也就是说,默认情况下,如果一个页面会分在多个tcp包中,在第一个应答后连接就终止了,没有继续获取后面的包。

修改方式:网上有同学说去掉这个TCP_FLAGS_FIN_V,但应该这样会影响一些其他的逻辑。好在有一个方法:
ether.persistTcpConnection(true)  ,有兴趣的同学可以到库文件里搜一下,这个方法做的正是设置状态,使调用变为:  make_tcp_ack_from_any(len,TCP_FLAGS_PUSH_V); 即不去设置FLAGS_FIN_V位标志。  所以,库应该是做了考虑的,但是从DEMO程序中看不出这种使用方式,查了不少外文论坛找到这个答案。


综上两点,这个程序的代码可以改为如下,注意红色就是这两处更改:

// This demo does web requests via DHCP and DNS lookup.
// 2011-07-05 <[email protected]> http://opensource.org/licenses/mit-license.php

#include <EtherCard.h>

#define REQUEST_RATE 5000 // milliseconds

#define XB_DEBUG
// ethernet interface mac address
static byte mymac[] = { 0x74,0x69,0x69,0x2a,0x18,0x39 };
// remote website name
char website[] PROGMEM = "www.geek-workshop.com";

byte Ethernet::buffer[2048];
static long timer;

// called when the client request is complete
static void my_result_cb (byte status, word off, word len) {
  Serial.print("<<< reply ");
  Serial.print(len);
  Serial.println(" data:");
  Serial.print(millis() - timer);
  Serial.println(" ms");
  int i = 0;
  while(i<len){
    byte c = (const byte) (Ethernet::buffer[off + i]);
    Serial.print((char)(c));
    i++;
  }
  //Serial.println((const char*) Ethernet::buffer + off);
}
void initDns(){
  long timer;
  //waitting status
  timer = millis();
  
  while (!ether.isLinkUp() ) {
    if( millis() - timer < 10000 )
      ether.packetLoop(ether.packetReceive());
    else break;
   }
timer = millis();

while( ether.clientWaitingGw() ){
   if( millis() - timer < 10000 )
      ether.packetLoop(ether.packetReceive());
    else break;
  }
}

void setup () {
  Serial.begin(57600);
  Serial.println("\n[getDHCPandDNS]");
  
  if (ether.begin(sizeof Ethernet::buffer, mymac) == 0)
    Serial.println( "Failed to access Ethernet controller");

  if (!ether.dhcpSetup())
    Serial.println("DHCP failed");
  
  ether.printIp("My IP: ", ether.myip);
  // ether.printIp("Netmask: ", ether.mymask);
  ether.printIp("GW IP: ", ether.gwip);
  ether.printIp("DNS IP: ", ether.dnsip);
  
  
  
  #ifdef XB_DEBUG
ether.persistTcpConnection(true);   // if notset, only 512bytes return in callback
  initDns();    //speed  waitting  port and gateway arp

  #endif
  int i = 0;
  while (!ether.dnsLookup(website)  && (i++ < 5 ))
    Serial.println("DNS failed");
   
  ether.printIp("Server: ", ether.hisip);
  
  timer = - REQUEST_RATE; // start timing out right away
}

void loop () {
   
  ether.packetLoop(ether.packetReceive());
  
  if (millis() > timer + REQUEST_RATE) {
    timer = millis();
    Serial.println("\n>>> REQ");
    ether.browseUrl(PSTR("/"), "thread-2128-1-1.html", website, my_result_cb);
  }
}



评分

参与人数 1 +3 收起 理由
Tiki + 3 很给力!

查看全部评分

回复

使用道具 举报

发表于 2013-7-10 13:13:00 | 显示全部楼层
好贴!{:soso_e179:}
回复 支持 反对

使用道具 举报

发表于 2016-7-11 22:13:18 | 显示全部楼层
请允许我说感谢
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则 需要先绑定手机号

Archiver|联系我们|极客工坊

GMT+8, 2024-3-29 04:49 , Processed in 0.042671 second(s), 21 queries .

Powered by Discuz! X3.4 Licensed

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表