冰炭寒酒 发表于 2013-7-9 22:48:37

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

本帖最后由 冰炭寒酒 于 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;
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);
    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");

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();    //speedwaittingport 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);
}
}



fangtaonj 发表于 2013-7-10 13:13:00

好贴!{:soso_e179:}

xinxinzhihuo 发表于 2016-7-11 22:13:18

请允许我说感谢
页: [1]
查看完整版本: ENC28J60 RJ45库运行时DNS解析超长及browseUrl返回512B内容