ARP 协议 代码 解释

1. ARP高速缓存的数据结构

/* arp.h – SHA, SPA, THA, TPA */

/* 互联网地址解析协议  (see RFCs 826, 920)    */

#define    AR_HARDWARE    1    /* 以太网硬件类型代码    */

/* Definitions of codes used in operation field of ARP packet */
    
#define    AR_REQUEST    1    /* ARP请求解决地址 ARP request to resolve address    */
#define    AR_REPLY    2    /* 回答解决的请求 reply to a resolve request    */

#define    RA_REQUEST    3    /* 反向ARP请求 reverse ARP request (RARP packets)*/
#define    RA_REPLY    4    /* 答复反向请求 reply to a reverse request (RARP ")*/

struct    arp    {
    u_short    ar_hwtype;    /* 硬件类型    */
    u_short    ar_prtype;    /* 协议类型    */
    u_char    ar_hwlen;    /* 硬件地址的长度    */
    u_char    ar_prlen;    /* 协议地址的长度    */
    u_short    ar_op;        /* ARP协议运作(见上述列表)    */
    u_char    ar_addrs[1];    /* 发送者和目标硬件及原地址    */
/*    char    ar_sha[???];     – 发件人的物理硬件地址    */
/*    char    ar_spa[???];     – 发送者的协议地址(IP地址)    */
/*    char    ar_tha[???];     – 目标物理硬件地址    */
/*    char    ar_tpa[???];     – 目标的协议地址(IP)    */
};

#define    SHA(p)    (&p->ar_addrs[0])                /*计算源物理地址*/
#define    SPA(p)    (&p->ar_addrs[p->ar_hwlen])            /*计算源协议地址*/
#define    THA(p)    (&p->ar_addrs[p->ar_hwlen + p->ar_prlen])    /*计算目标物理地址*/
#define    TPA(p)    (&p->ar_addrs[(p->ar_hwlen*2) + p->ar_prlen])    /*计算目标协议地址*/

#define    MAXHWALEN    EP_ALEN    /* 以太网地址最大长度            */
#define    MAXPRALEN    IP_ALEN    /* IP 地址最大长度            */

#define ARP_HLEN    8    /* ARP协议头长度            */

#define    ARP_TSIZE    50    /*  ARP缓存大小            */
#define    ARP_QSIZE    10    /*  ARP协议端口队列的大小        */

/* 缓存超时 cache timeouts */

#define ARP_TIMEOUT    600        /* 10 minutes                */
#define    ARP_INF        0x7fffffff    /* “无限”超时值 "infinite" timeout value    */
#define    ARP_RESEND    1    /* 如果没有在1秒内回复,重新发送            */
#define    ARP_MAXRETRY    4    /* 多少秒后放弃?? give up after ~30 seconds    */

struct    arpentry {        /* ARP缓存中的条目格式    */
    short    ae_state;    /* 本条目状态(见下文)    */
    short    ae_hwtype;    /* 硬件类型            */
    short    ae_prtype;    /* 协议类型            */
    char    ae_hwlen;    /* 硬件地址的长度        */
    char    ae_prlen;    /* 协议地址的长度        */
    struct netif *ae_pni;    /* 指向接口结构体的指针    */
    int    ae_queue;    /* 队列的数据包地址    */
    int    ae_attempts;    /* 重试,到目前为止 number of retries so far    */
    int    ae_ttl;        /* 生存时间                */
    u_char    ae_hwa[MAXHWALEN];    /* 硬件地址         */
    u_char    ae_pra[MAXPRALEN];    /* 协议地址         */
};

#define    AS_FREE        0    /* 表项空闲    */
#define    AS_PENDING    1    /* 表项使用中,但未绑定        */
#define    AS_RESOLVED    2    /* 表项正确绑定         */

/* RARP变量 */

extern int    rarppid;    /* 进程ID为RARP等待答复    */
extern int    rarpsem;    /* RARP服务获取信号量    */

/*  ARP协议的变量 */

extern struct    arpentry    arptable[ARP_TSIZE];

/*  ARP协议函数声明 */

int         arp_in(struct netif *, struct ep *);
struct arpentry *arpfind(u_char *, u_short, struct netif *);
struct arpentry *arpadd(struct netif *, struct arp *);


2. 搜索ARP高速缓存

/* arpfind.c – arpfind */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * arpfind – find an ARP entry given a protocol address and interface
 *————————————————————————
 */
struct arpentry *
arpfind(u_char *pra, u_short prtype, struct netif *pni)    /*参数:协议地址,协议类型,接口结构指针*/
{
    struct arpentry    *pae;                /*定义ARP缓存结构体指针*/
    int        i;

    for (i=0; i<ARP_TSIZE; ++i) {            /*遍历ARP的缓存*/
        pae = &arptable[i];            /*缓存数组*/
        if (pae->ae_state == AS_FREE)        /*缓存为空闲接找下一个*/
            continue;
        if (pae->ae_prtype == prtype &&    
            pae->ae_pni == pni &&
            BLKEQU(pae->ae_pra, pra, pae->ae_prlen))
            return pae;            /*如果缓存中的协议类型与参数相同,并且接口与协议地址全相同,则返回这个ARP条目的指针。BLKEQU的定义 #define    BLKEQU(b1, b2, len)    (!memcmp((b1), (b2), len))*/
    }
    return 0;
}
 


3. ARP请求广播分组

/* arpsend.c – arpsend */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * arpsend – broadcast an ARP request
 *    N.B. Assumes interrupts disabled
 *————————————————————————
 */
int
arpsend(struct arpentry *pae)                    /*指向ARP表项的指针*/
{
    struct    netif    *pni = pae->ae_pni;            /*指向接口结构指针*/
    struct    ep    *pep;                    /*贞结构指针*/
    struct    arp    *parp;                    /*ARP分组指针*/
    int        arplen;

    pep = (struct ep *) getbuf(Net.netpool);        /*为分组分配一个缓存区*/
    if ((int)pep == SYSERR)
        return SYSERR;
    memcpy(pep->ep_dst, pni->ni_hwb.ha_addr, pae->ae_hwlen);/*目的地播为物理广播地址*/
    pep->ep_type = EPT_ARP;                    /*帧类型*/
    pep->ep_order = EPO_NET;                /*网络工作层*/
    
    /*填写ARP包*/
    parp = (struct arp *) pep->ep_data;            /*以太网帧中ARP的数据*/
    parp->ar_hwtype = hs2net(pae->ae_hwtype);        /*硬件类型*/        
    parp->ar_prtype = hs2net(pae->ae_prtype);        /*协议类型*/
    parp->ar_hwlen = pae->ae_hwlen;                /*硬件地址长度*/
    parp->ar_prlen = pae->ae_prlen;                /*协议地址长度*/
    parp->ar_op = hs2net(AR_REQUEST);            /*操作字段,请求*/
    memcpy(SHA(parp), pni->ni_hwa.ha_addr, pae->ae_hwlen);    /*发送方硬件地址*/
    memcpy(SPA(parp), &pni->ni_ip, pae->ae_prlen);        /*发送方协议地址*/
    memset(THA(parp), 0, pae->ae_hwlen);            /*目标硬件地址清空*/
    memcpy(TPA(parp), pae->ae_pra, pae->ae_prlen);        /*目标协议地址*/
    arplen = ARP_HLEN + 2*(parp->ar_hwlen + parp->ar_prlen);/*ARP的总长度*/
    write(pni->ni_dev, pep, EP_HLEN+arplen);        /*发送分组*/
    return OK;
}


4. ARP输出过程

/* netwrite.c – netwrite */

#include <conf.h>
#include <kernel.h>
#include <network.h>

#include <ospf.h>

struct    arpentry    *arpalloc();
struct    arpentry     *arpfind(u_char *, u_short, struct netif *);
int    arpsend(struct arpentry *);
int    local_out(struct ep *);        //用于将目的是本地机的数据包通过loopback 发送到指定本机端口

/*#define    DEBUG */

/*————————————————————————
 * netwrite – write a packet on an interface, using ARP if needed
 *————————————————————————
 */
int
netwrite(struct netif *pni, struct ep *pep, unsigned len)
{
    struct    arpentry     *pae;            //ARP项目指针
    STATWORD        ps;            //状态字

    if (pni->ni_state != NIS_UP) {            
        freebuf(pep);
        return SYSERR;
    }                        //接口未打开则退出
    pep->ep_len = len;                //帧长度
#ifdef    DEBUG                        
if (pni != &nif[NI_LOCAL])
{
struct ip *pip = (struct ip *)pep->ep_data;
    if (pip->ip_proto == IPT_OSPF) {
        struct ospf *po = (struct ospf *)pip->ip_data;
/*        if (po->ospf_type != T_HELLO) { */
{
            kprintf("netwrite(pep %X, len %d)n", pep, len);
            pdump(pep);
        }
    }
}
#endif    /* DEBUG */
    if (pni == &nif[NI_LOCAL])        //如果是本地接口,用local_out 发送
        return local_out(pep);
    else if (isbrc(pep->ep_nexthop)) {    /*否则,首先检查下一跳是否是广播地址*/
        memcpy(pep->ep_dst, pni->ni_hwb.ha_addr, EP_ALEN);//填写目的物理地址为广播物理地址
        write(pni->ni_dev, pep, len);    //发送
        return OK;
    }
    /* 否则,该协议地址查找… */

    disable(ps);    /*如果下一跳地址不是广播地址,则先在arp 缓存表中查找是否有绑定的物理地址,有则直接发送,没有则先做arp 地址绑定。*/
    pae = arpfind((u_char *)&pep->ep_nexthop, pep->ep_type, pni);
    if (pae && pae->ae_state == AS_RESOLVED) {
        memcpy(pep->ep_dst, pae->ae_hwa, pae->ae_hwlen);
        restore(ps);
        return write(pni->ni_dev, pep, len);
    }
    if (IP_CLASSD(pep->ep_nexthop)) {    /*下一跳地址为d 类地址则直接返回*/
        restore(ps);
        return SYSERR;
    }
    if (pae == 0) {                //没有绑定,做arp 解析
        pae = arpalloc();        //分配空间
        pae->ae_hwtype = AR_HARDWARE;    //硬件类型
        pae->ae_prtype = EPT_IP;    //协议类型
        pae->ae_hwlen = EP_ALEN;    //硬件地址长度
        pae->ae_prlen = IP_ALEN;    //协议地址长度
        pae->ae_pni = pni;        //接口
        pae->ae_queue = EMPTY;        //队列地址
        memcpy(pae->ae_pra, &pep->ep_nexthop, pae->ae_prlen);    //协议地址
        pae->ae_attempts = 0;        //重试
        pae->ae_ttl = ARP_RESEND;    //生存时间
        arpsend(pae);            //发送
    }
    /*将等待ARP绑定的数据包存放在和ARP表项相关的队列中,如果队列为空,则分配一个。*/
    if (pae->ae_queue == EMPTY)    
        pae->ae_queue = newq(ARP_QSIZE, QF_NOWAIT);    //如没有队列,建立一个新队列
    if (enq(pae->ae_queue, pep, 0) < 0)    //将分组放入队列中,
        freebuf(pep);
    restore(ps);
    return OK;    //如果队列满,则失丢这个分组
}


5. ARP的输入处理

/* arpadd.c – arpadd */

#include <conf.h>
#include <kernel.h>
#include <network.h>

struct arpentry *arpalloc(void);

/*————————————————————————
 * arpadd – Add a RESOLVED entry to the ARP cache
 *     N.B. Assumes interrupts disabled
 *————————————————————————
 */
struct    arpentry *
arpadd(struct netif *pni, struct arp *parp)
{
    struct    arpentry    *pae;            //ARP缓存表项指针

    pae = arpalloc();                //为指针分配内存空间
    //从收到的ARP包中填写到缓存中
    pae->ae_hwtype = parp->ar_hwtype;        //硬件类型        
    pae->ae_prtype = parp->ar_prtype;        //协议类型
    pae->ae_hwlen = parp->ar_hwlen;            //硬件地址长度
    pae->ae_prlen = parp->ar_prlen;            //协议地址长度
    pae->ae_pni = pni;                //接口
    pae->ae_queue = EMPTY;                //队列地址
    memcpy(pae->ae_hwa, SHA(parp), parp->ar_hwlen);    //源物理地址
    memcpy(pae->ae_pra, SPA(parp), parp->ar_prlen);    //源协议地址
    pae->ae_ttl = ARP_TIMEOUT;            //生存时间
    pae->ae_state = AS_RESOLVED;            //表项的状态
    return pae;
}


6. 发送等待发送的分组

/* arpqsend.c – arpqsend */

#include <conf.h>
#include <kernel.h>
#include <network.h>

int netwrite(struct netif *, struct ep *, unsigned);

/*————————————————————————
 * arpqsend – write packets queued waiting for an ARP resolution
 *————————————————————————
 */
void
arpqsend(struct arpentry *pae)
{
    struct    ep    *pep;
    struct    netif    *pni;

    if (pae->ae_queue == EMPTY)        //待发送队列为空则返回
        return;

    pni = pae->ae_pni;            //接口
    while (pep = (struct ep *)deq(pae->ae_queue))    //遍历队列并发送,为空则释放队列
        netwrite(pni, pep, pep->ep_len);
    freeq(pae->ae_queue);
    pae->ae_queue = EMPTY;
}


7. ARP输入过程

/* arp_in.c – arp_in */

#include <conf.h>
#include <kernel.h>
#include <network.h>

void arpqsend(struct arpentry *);

/*————————————————————————
 *  arp_in  –  handle ARP packet coming in from Ethernet network
 *    N.B. – Called by ni_in– SHOULD NOT BLOCK
 *————————————————————————
 */
int
arp_in(struct netif *pni, struct ep *pep)
{
    struct    arp        *parp = (struct arp *)pep->ep_data;
    struct    arpentry    *pae;
    int            arplen;
    /*从网络字节顺序转换成主机字节顺序*/
    parp->ar_hwtype = net2hs(parp->ar_hwtype);
    parp->ar_prtype = net2hs(parp->ar_prtype);
    parp->ar_op = net2hs(parp->ar_op);
    //如果硬件类型或协议类型不匹配,则丢充此分组并返回
    if (parp->ar_hwtype != pni->ni_hwtype ||
        parp->ar_prtype != EPT_IP) {
        freebuf(pep);
        return OK;
    }
    //如果ARP表项中有此IP,就更新之
    if (pae = arpfind(SPA(parp), parp->ar_prtype, pni)) {
        memcpy(pae->ae_hwa, SHA(parp), pae->ae_hwlen);
        pae->ae_ttl = ARP_TIMEOUT;
    }
    //如果此分组不是发往此接口的,则丢弃之
    if (memcmp(TPA(parp), &pni->ni_ip, IP_ALEN)) {
        freebuf(pep);
        return OK;
    }
    //如果是发往本接口并没有此表项,则加入一个新表项
    if (pae == 0)
        pae = arpadd(pni, parp);
    //如果表项状态为正在绑定中,则发送队列中的分组
    if (pae->ae_state == AS_PENDING) {
        pae->ae_state = AS_RESOLVED;
        arpqsend(pae);
    }
    //如果收到的分组操作类型为请求ARP,则填写ARP包并发送,否则丢充分组
    if (parp->ar_op == AR_REQUEST) {
        parp->ar_op = AR_REPLY;
        memcpy(TPA(parp), SPA(parp), parp->ar_prlen);
        memcpy(THA(parp), SHA(parp), parp->ar_hwlen);
        memcpy(pep->ep_dst, THA(parp), EP_ALEN);
        memcpy(SHA(parp), pni->ni_hwa.ha_addr,
            pni->ni_hwa.ha_len);
        memcpy(SPA(parp), &pni->ni_ip, IP_ALEN);

        parp->ar_hwtype = hs2net(parp->ar_hwtype);
        parp->ar_prtype = hs2net(parp->ar_prtype);
        parp->ar_op = hs2net(parp->ar_op);

        arplen = ARP_HLEN + 2*(parp->ar_prlen + parp->ar_hwlen);

        write(pni->ni_dev, pep, arplen);
    } else
        freebuf(pep);
    return OK;
}


8. ARP高速缓存的管理

/* arpalloc.c – arpalloc */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <network.h>

void arpdq(struct arpentry *);

/*————————————————————————
 * arpalloc – allocate an entry in the ARP table
 *    N.B. Assumes interrupts DISABLED
 *————————————————————————
 */
struct arpentry *arpalloc()
{
    static    int    aenext = 0;    /*静态变量*/
    struct    arpentry *pae;
    int    i;
    //找空表项
    for (i=0; i<ARP_TSIZE; ++i) {
        if (arptable[aenext].ae_state == AS_FREE)
            break;
        aenext = (aenext + 1) % ARP_TSIZE;    //实现静态变量循环计数
    }
    pae = & arptable[aenext];
    aenext = (aenext + 1) % ARP_TSIZE;
    //如没有空表项,则使用这个当前的表项,判断为使用中但未绑定,并且有队列,如果有则丢弃队列中的分组
    if (pae->ae_state == AS_PENDING && pae->ae_queue >= 0)
        arpdq(pae);
    pae->ae_state = AS_PENDING;
    return pae;
}


9. 高速缓存的定期维护管理

/* arptimer.c – arptimer */

#include <conf.h>
#include <kernel.h>
#include <network.h>

void    arpdq(struct arpentry *);
int    arpsend(struct arpentry *);

/*————————————————————————
 * arptimer – Iterate through ARP cache, aging (possibly removing) entries
 *————————————————————————
 *    gran    – time since last iteration
 */
void
arptimer(int gran)
{
    struct arpentry *pae;
    STATWORD    ps;
    int        i;

    disable(ps);    /* 互斥量 */

    for (i=0; i<ARP_TSIZE; ++i) {
        if ((pae = &arptable[i])->ae_state == AS_FREE)
            continue;                /*空闲的条目*/
        if (pae->ae_ttl == ARP_INF)
            continue;                 /*不超时永久条目*/
        if ((pae->ae_ttl -= gran) <= 0)            /*寿命为0或负*/
            if (pae->ae_state == AS_RESOLVED)    /*条目绑定中但未正确*/
                pae->ae_state = AS_FREE;    /*仅改变状态为空闲*/
            else if (++pae->ae_attempts > ARP_MAXRETRY) {    /*超过最大广播次数*/
                pae->ae_state = AS_FREE;
                arpdq(pae);            /*清空队列*/
            } else {
                pae->ae_ttl = ARP_RESEND;    /*等于重发时间*/    
                arpsend(pae);            /*重新广播分组*/
            }
    }
    restore(ps);
}


10. 释放队列中的分组

/* arpdq.c – arpdq */

#include <conf.h>
#include <kernel.h>
#include <network.h>

/*————————————————————————
 * arpdq – destroy an arp queue that has expired
 *————————————————————————
 */
void
arpdq(struct arpentry *pae)
{
    struct    ep    *pep;
    struct    ip    *pip;

    if (pae->ae_queue < 0)        /* 队列为空,直接返回 */
        return;

    while (pep = (struct ep *)deq(pae->ae_queue)) {
        if (gateway && pae->ae_prtype == EPT_IP) {/*本机是网关并且数据报为IP数据报*/
            pip = (struct ip *)pep->ep_data;    //生成ICMP"目的站不可达"信息
            icmp(ICT_DESTUR, ICC_HOSTUR, pip->ip_src, pep, 0);
        } else
            freebuf(pep);            //释放帧
    }
    freeq(pae->ae_queue);                //释放队列
    pae->ae_queue = EMPTY;
}


11. ARP初始化

/* arpinit.c – arpinit */

#include <conf.h>
#include <kernel.h>
#include <proc.h>
#include <network.h>

/*————————————————————————
 *  arpinit  –  initialize data structures for ARP processing
 *————————————————————————
 */
void arpinit()
{
    int    i;

    rarpsem = screate(1);            /*创建一个与RARP一起使用的互斥型信号量*/
    rarppid = BADPID;            /*RARP进程号初始为-1*/

    for (i=0; i<ARP_TSIZE; ++i)
        arptable[i].ae_state = AS_FREE;    /*将状态全部置为AS_FREE*/
}

int    rarpsem;
int    rarppid;

struct    arpentry    arptable[ARP_TSIZE];    /*定义ARP高速缓存*/

解决 godaddy 空间 无法 自动发送 wordpress 评论 通知 方法

安装
WP-Mail-SMTP插件

安完后点插件->WP-Mail-SMTP->settings

From
From Email:     写上你发出邮件到邮箱时显示的发件人的地址

From Name:   发出邮件到邮箱时显示的发件人的名字

Mailer: 选第一个Send all WordPress emails via SMTP.

SMTP Host:relay-hosting.secureserver.net

SMTP Port: 25

Encryption:选第一个No encryption.

Authentication:选第一个No: Do not use SMTP authentication.

Username:为空
Password: 为空

点击 update options保存

然后在send test email to:
写上你要发送到哪里,再点 send test 看看发送成功了没

注意:我测试时用qq邮箱不行,用gmail的没问题,其它的邮箱自己试试吧

Virtualbox 使用物理硬盘 (ACL) for linux

物理硬盘在linux中在/dev目录下 ,我的是阵列,是/dev/dm-1 如果是硬盘就是/dev/sda 或sdb之类的

这些文件是root用户,disk组。vbox并不能直接访问,可以chmod o+rw /dev/dm-1,但是这样并不安全。

我使用ACL访问控制列表

先su – 进入root

setfacl -m u:ssj:rw /dev/dm-1

建立映射

VBoxManage internalcommands createrawvmdk -filename /home/ssj/.VirtualBox/HardDisks/phard.vmdk -rawdisk /dev/dm-1 -register

由于是root ,正常的用户没法用,所以还是先

cd /home/ssj/.VirtualBox/HardDisks/

setfacl -m u:ssj:rw phard.vmdk

getfacl phard.vmdk

再在vbox里把phard.vmdk加入就可以了。

重启后发现/dev/dm-1的权限又变回去了,不能访问了,我是把ssj这个用户加入到了disk这个组里

vi /etc/group

disk:x:6:root,ssj

网络接口层 使用的数据结构

1. 接口数据结构

/* netif.h – NIGET */

#define    NI_MAXHWA 14            /* 任何硬件最大尺寸 max size of any hardware*/
                    /* (物理)网络地址 */
struct    hwa    {            /* *硬件地址    */
    int        ha_len;        /* 地址长度    */
    unsigned char    ha_addr[NI_MAXHWA];     /* 地址    */
};

#define    NI_INQSZ    30        /* 接口输入队列大小    */
#define    NETNLEN        30        /* 网络名称长度    */

#define    NI_LOCAL    0        /* 本地接口序号index of local interface    */
#define    NI_PRIMARY    1        /* 主要接口序号index of primary interface    */

#define    NI_MADD        0        /* 添加多播add multicast (ni_mcast)    */
#define    NI_MDEL        1        /* 删除多播 multicast (ni_mcast)    */

/* 接口状态 */

#define    NIS_UP        0x1
#define    NIS_DOWN    0x2
#define    NIS_TESTING    0x3

/* 网络接口结构 (one per interface)    */

struct    netif {                /* 网络接口信息    */
    char        ni_name[NETNLEN]; /* 接口的域名    */
    char        ni_state;    /* 接口的状态 取上边的三个值    */
    IPaddr        ni_ip;        /* 接口的IP地址*/
    IPaddr        ni_net;        /* 接口的网络IP地址        */
    IPaddr        ni_subnet;    /* 子网的IP地址    */
    IPaddr        ni_mask;    /* 接口的子网掩码    */
    IPaddr        ni_brc;        /* IP广播地址        */
    IPaddr        ni_nbrc;    /* IP的网络广播地址    */
    unsigned int    ni_mtu;        /* 最大传输单元(字节)    */
    unsigned int    ni_hwtype;    /* 硬件类型 (for ARP)    */
    struct    hwa    ni_hwa;        /* 接口的硬件地址*/
    struct    hwa    ni_hwb;        /* 硬件广播地址    */
                    /* 多播添加/删除函数    */
    int        (*ni_mcast)(int op,int dev,Eaddr hwa,IPaddr ipa);
    Bool        ni_ivalid;    /* 接口的IP地址是否有效        */
    Bool        ni_nvalid;    /* 接口的域名是否有效        */
    Bool        ni_svalid;    /* 子网的IP地址是否有效        */
    int        ni_dev;        /* 设备描述符    */
    int        ni_ipinq;    /*  IP输入队列        */
    int        ni_outq;    /* (设备)的输出队列    */
    /* 接口信息管理库- MIB */
    char        *ni_descr;    /* 硬件的文字描述 */
    int        ni_mtype;    /* MIB类型        */
    long        ni_speed;    /* 速率 bit/s        */
    char        ni_admstate;    /* 管理状态 (NIS_*)*/
    long        ni_lastchange;    /* 最后一个状态变化 (1/100 sec)*/
    long        ni_ioctets;    /* # 收到的字节        */
    long        ni_iucast;    /* # 收到的单播字节    */
    long        ni_inucast;    /* # 收到的非单播字节    */
    long        ni_idiscard;    /* # 丢弃 – 输出队列满*/
    long        ni_ierrors;    /* # 输入包错误    */
    long        ni_iunkproto;    /* # in packets for unk. protos */
    long        ni_ooctets;    /* # 发送的字节        */
    long        ni_oucast;    /* # 发送的单播字节        */
    long        ni_onucast;    /* # 发送的非单播字节    */
    long        ni_odiscard;    /* # 输出的数据包被丢弃    */
    long        ni_oerrors;    /* # 发送的包错误    */
    long        ni_oqlen;    /* 输出队列长度        */
    long        ni_maxreasm;    /* 可以重新组装的最大数据包的长度    */
};

#define    NIGET(ifn)    ((struct ep *)deq(nif[ifn].ni_ipinq))

#define    NIF    Neth+Noth+1        /* # of interfaces, +1 for local*/

extern struct netif    nif[];

int netwrite(struct netif *, struct ep *, unsigned);
 


2. 以太网的基本定义

/* ether.h */

/* 以太网的定义和常量 */

#define    EP_MAXMULTI      10    /* 多播地址表的大小        */

#define    EP_MINLEN      60    /* 最小数据包长度        */
#define    EP_DLEN        1500    /* 数据字段长度        */
#define    EP_HLEN          24    /* 头长度    */
#define    EP_CRC           4    /* 效检位长度 ether CRC (trailer)    */
#define    EP_MAXLEN    EP_HLEN+EP_DLEN
#define    EP_ALEN    6        /* 物理地址的字节数    */
typedef    unsigned char    Eaddr[EP_ALEN]; /* 物理地址    */
#define    EP_RETRY    3    /* number of times to retry xmit errors    */
#define    EP_BRC    "377377377377377377"/* 以太网广播地址    */
#define EP_RTO 300        /* time out in seconds for reads    */

#define EP_NUMRCV 16        /* number LANCE recv buffers (power 2)    */
#define EP_NUMRCVL2 4        /* log2 of the number of buffers    */

#define    EPT_LOOP    0x0060        /* type: Loopback 回环        */
#define    EPT_ECHO    0x0200        /* type: Echo    应答        */
#define    EPT_PUP        0x0400        /* type: Xerox PUP        */
#define    EPT_IP        0x0800        /* type: Internet Protocol    */
#define    EPT_ARP        0x0806        /* type: ARP            */
#define    EPT_RARP    0x8035        /* type: Reverse ARP        */

struct    eh {            /* 以太网报头            */
    Eaddr    eh_dst;        /* 目标主机地址        */
    Eaddr    eh_src;        /* 源主机地址            */
    unsigned short    eh_type;/* 以太网数据包类型(见下文)    */
};

struct    ep    {        /* 结构完整的以太网数据包*/
    u_long    ep_nexthop;    /* niput() uses this            */
    short    ep_ifn;        /* 原接口序号originating interface number*/
    short    ep_len;        /* 数据包的长度            */
    short    ep_order;    /* 字节顺序 (for debugging)    */
    struct    eh ep_eh;    /* 以太网报头            */
    char    ep_data[EP_DLEN];    /* 数据        */
};

/* 数据包字节顺序的常量(设置方法主机顺序) */

#define    EPO_NET        1    /* 网络层 network layer        */
#define    EPO_IP        2    /* level 1 protocols…        */
#define    EPO_ARP        2
#define    EPO_RARP    2
#define    EPO_ICMP    4    /* level 2 protocols        */
#define    EPO_IGMP    4
#define    EPO_TCP        4
#define    EPO_UDP        4
#define    EPO_OSPF    4
#define    EPO_DNS        5    /* 应用协议 application protocols*/

/* these allow us to pretend it's all one big happy structure */

#define    ep_dst    ep_eh.eh_dst
#define    ep_src    ep_eh.eh_src
#define    ep_type    ep_eh.eh_type

#if    Noth > 0
/* this is for Othernet simulation */

struct    otblk {
    Bool    ot_valid;    /* these entries are valid?        */
    Eaddr    ot_paddr;    /* the Othernet physical address    */
    Eaddr    ot_baddr;    /* the Othernet broadcast address    */
    int    ot_rpid;    /* id of process reading from othernet    */
    int    ot_rsem;    /* mutex for reading from the othernet    */
    int    ot_pdev;    /* Physical device devtab index        */
    int    ot_intf;    /* the associate interface        */
    char    *ot_descr;    /* text description of the device    */
};
#endif    /* Noth */

#define    ETOUTQSZ    30

/* 以太网控制模块的描述 */

struct    etblk    {
    int    etxpending;    /* 号的数据包等待输出number of packets pending output*/
    int    etrpending;    /* 正在等待已接收1 => a receive is pending already*/
    int    etwtry;        /* 重试num. of times to retry xmit errors    */
    char    *etwbuf;    /* 当前发送缓冲区的指针    */
    short    etnextbuf;    /* 检查缓冲区循环for checking buffers round robin    */
#if    Noth > 0
    struct    otblk    *etoth[Noth];    /* eaddr to oaddr translations    */
#endif    /* Noth */
};

/* 以太网功能的代码 */

#define    EPC_PROMON    1        /* 打开混杂模式    */
#define    EPC_PROMOFF    2        /* 关闭混杂模式    */
#define    EPC_MADD    3        /* 添加多播地址    */
#define    EPC_MDEL    4        /* 删除多播地址    */

struct utdev {
    unsigned int     ud_iomem;
    unsigned int     ud_iosize;
    struct devsw    *ud_pdev;
    Eaddr         ud_paddr;
    Eaddr         ud_bcast;
    char        *ud_descr;
    int         ud_outq;
    int         ud_ifnum;
    int         ud_nextbuf;
    int         ud_xpending;
    int         ud_xmaddr;    /* 发送基地缓冲区transmit base buf #    */
    int         ud_wretry;
    int         ud_rmin;    /* 接收环形缓冲区基地址receive ring buffer base addr*/
    int         ud_rmax;    /* 环形缓冲区最大接收地址receive ring buffer max addr    */
};

struct etdev {
    unsigned int     ed_iomem;
    unsigned int     ed_iosize;
    struct devsw    *ed_pdev;
    Eaddr         ed_paddr;
    Eaddr         ed_bcast;
    char        *ed_descr;
    int         ed_outq;
    int         ed_ifnum;
    int         ed_wretry;
    struct scb    *ed_scb;
    struct cbl    *ed_cbl;
    struct rfd    *ed_rfd;    /* first in RFD ring        */
    struct rfd    *ed_rfdend;    /* last in RFDring        */
    struct rbd    *ed_rbd;    /* first in RBD ring        */
    struct rbd    *ed_rbdend;    /* last in RBD ring        */
    struct tbd    *ed_tbd;    /* transmit buffer descriptor    */
    unsigned char    *ed_xmbuf;    /* transmit buffer base        */
    unsigned char     ed_irq;    /* SIRQ reg value        */
    int         ed_mcc;    /* # valid mca's        */
    Eaddr         ed_mca[EP_MAXMULTI]; /* multicast addr. table    */
/* XXX */
    int        ed_xpending;
};

extern struct utdev    ue[];
extern struct etdev    ee[];
 


3. 传入分组的多路分解

/* ni_in.c – ni_in */

#include <conf.h>
#include <kernel.h>
#include <network.h>

#include <ospf.h>

int arp_in(struct netif *, struct ep *);
int rarp_in(struct netif *, struct ep *);
int ip_in(struct netif *, struct ep *);

/*————————————————————————
 *  ni_in – network interface input function 网络接口输入功能
 *————————————————————————
 */
int
ni_in(struct netif *pni, struct ep *pep, unsigned len)
{
    int    rv;

    pep->ep_ifn = pni – &nif[0];    /* 记录原接口序号 # */

    pni->ni_ioctets += len;        /*收到的字节累加*/
    if (!memcmp(pni->ni_hwa.ha_addr, pep->ep_dst, EP_ALEN))
        pni->ni_iucast++; /*比较帧的目的地址与接口的物理地址,相同则单播字节累加*/
    else
        pni->ni_inucast++;    /*非单播字节累加*/
    switch (pep->ep_type) {        /*分类*/
    case EPT_ARP:    rv = arp_in(pni, pep);    break;
    case EPT_RARP:    rv = rarp_in(pni, pep);    break;
    case EPT_IP:    rv = ip_in(pni, pep);    break;
    default:
        pni->ni_iunkproto++;
        freebuf(pep);
        rv = OK;
    }
    return rv;
}

extern    struct    etblk    eth[];

如何把win7安装在U盘上

usb跨平台先用PE3.0 win7内核的,或直接先安装一个win7旗见版。

1.建立一个vhd虚拟硬盘

F:>diskpart
Diskpart> Creste vdisk file=D:Win7.vhd type=fixed maximum=20000
select vdisk file=D:win7.vhd
attach vdisk
create partition primary
format fs=ntfs quick
assign letter=G
Exit

2.安装Windows 7到VHD文件
  自Vista之后系统的安装过程便是WIM文件的解压过程了,我们在Windows 7的安装盘sources目录中找到install.wim文件,解压到G盘中(刚才设置VHD盘符),在命令行中输入:
  F:>imagex /apply d:sourcesinstall.wim 5 G:

还有个办法,就是virtualbox,用vbox打开vhd,并在其中安系统。

3.在本机测试一下,vhd安装的系统是能可以引导

  bcdedit /copy {current} /d "Windows 7 Second"
  注:这里会随机出现一串字符:{06022834-cc00-41bd-3e41-hjk628796301}
  Bcdedit /set {931efa53-6d8e-11df-83de-f6c8754438ba} device vhd=[D:]Win7.VHD
  bcdedit /set {931efa53-6d8e-11df-83de-f6c8754438ba} osdevice vhd=[D:]Win7.VHD

4.然后重启系统,进入系统改注册表

HKLMsystemCurrentControlSetControlBootDriverFlags
HKLMsystemCurrentControlSetControlPnPPollBootPartitionTimeout
把第一个值设成4就可以在启动开始阶段就加载usb驱动;
而第二个则控制内核等待启动分区PnP反应时间,usb启动设为15000(win7 embedded的值)或者30000(hyper-v r2中的值)我改的是4和10进制的30000
 

5.对U盘进行操作了

在u盘上建立激活主分区,把U盘变成usb-hdd+ ,我用的ultraISO9.3,菜单里有一个,把映像写入硬盘,选择你的U盘,便捷启动->usb-hdd+ 和写入win7引导

然后把vhd文件请入U盘,把引导写入U盘

bcdboot c:windows /s d: /l zh-CN
bcdedit /store d:bootbcd /set {default} device vhd=[locate]win7.vhd
bcdedit /store d:bootbcd /set {default} osdevice vhd=[locate]win7.vhd

 

 

从启电脑,试试U盘版的WIN7

NT6.X快速安装器100227

NT6快捷安装器v1.1

usbreg

usb跨平台

TCP 状态机 理解TCP重要的环节

1.问:TCP使用有限长的字段来记录流序号。研究协议规范,找出在两台机器之间允许任意长度流传输的方法

答:目前我只知道使用,时间戳选项来解决这个问题,具体的还不太清楚,有待提高

2.问:TCP有一个选项允许接收方指明它愿意接受的最大报文段长度。在TCP已具有窗口通告机制的情况下,为什么还要支持这个选项来规定最大报文段长度?

答:第一,最大报文段长度规定了最大报文的长度,避免在物理网络上对TCP数据报进行分组,以提高传输效率。

第二,在避免糊涂窗口综合证时还使用了最大报文段长度做为量度。

3.问:在什么样的时延,带宽,负载以及分组丢失率情况下,TCP没有必要重传大量的数据?

答:时延低且稳定,带宽高,负载低,分组丢失率低的情况下(我不太明白这题的意思,可能不太对 🙂 )

4.问:一个丢失的TCP确认并不一定会导致重传,试解释原因。

答:第一种,TCP采用累积确认,如果发送方确认丢失也不会重传。

第二种,两台主机通信,主机1主动关闭发送FIN,主机2 发送确认,但是确认丢失。主机2被动关闭发送FIN,ACK.主机1确定,并进入TIMED_WAIT,这个丢失的确认不用重传,因为主机2发送关闭时,包含有主机1发送的信息,所以已经可以确定收到。

5.问:考虑检验和的计算。假设报文段中的检验和字段并未置零,而检验和计算的结果却是零。你能从中得出什么结论?

答:结论是这次的检验和应该是上一次检验和减1

6.问:如果两个程序使用TCP来发送数据,但每次只发送含一个字符的报文段,那么它们的数据最多有多大的网络带宽利用率?

答:首先是多层封装,TCP封在IP报里,IP报封在帧中。

帧:最少要6octet 源地址,6octet 目的地址,2octet帧类型,和4octet的CRC,一共是18octet (1octet=8bit)

IP数据报与TCP数据报的报头最少都是20byte,两种加起来是40byte,加上18byte+1byte的数据。

1/(40+18+1)=0.016949 约合1.7%

Tomato Dualwan 做 SSH 加密 代理

先来表达一下不满,有的单位的网管就是吃多了。封这个,封那个,没事找事。有本事你就别开外网,开了外网总是有办法穿透的。就像某党弄的网络长城一样,有真正长城那么好用吗????!!!!!!!!

下面是正题。

虽说TT全功能版是有VPN服务器功能的,但是已经被封,用不了,因为端口是不能变的。所以有能用SSH功能了。TT无法在管理WEB中加入多个用户,所以只能到SHELL里操作。

先启动WEB管理中的 系统管理->JFFS2设置 开启 并格式化

再到 系统管理->访问设置 中改掉原来的密码。改成你想新建立的用户使用的密码(因为密码存储是加密的,所以要得到密码加密后的结果)

连接SSH 键入命令 cat /etc/shadow

root:$1$TFGPEA$g/l3FMuNtLsgRjQLOA/:0:0:99999:7:0:0:
admin:$1$TFGPEA$g/l3FMuNtLsgRjQLOA/:0:0:99999:7:0:0:
nobody:*:0:0:99999:7:0:0:
nas:*:0:0:99999:7:0:0:

复制第一行root: 和后边冒号之间的安符串,不包括前后的冒号,这时就是$1$TFGPEA$g/l3FMuNtLsgRjQLOA/
然后到 系统管理->访问设置 把密码再改回原来的

然后复制密码文件到/jffs 目录 cp /etc/shadow /jffs/shadow

编辑密码文件 vi /jffs/shadow 在最下边一行加入ssh:$1$TFGPEA$g/l3FMuNtLsgRjQLOA/:0:0:99999:7:0:0:

ssh后边的冒号之间是刚才记下来的密码.

最后就是到 系统管理->脚本设置->开机时 输入以下内容

sleep 30
echo ssh:x:101: >> /etc/group
echo ssh:x:101:101:ssh:/tmp/home/ssh:/bin/sh >> /etc/passwd
cp /jffs/shadow /etc/shadow
mkdir /tmp/home/ssh
chown ssh:ssh /tmp/home/ssh

基本上就完了,路由开启后30秒会自动建立新用户。

SSH代理的好处是加密,我在这个要代理的朋友那里试过socks代理,结果是用不了,应该进行了包过滤。那可是世界500强的单位设备一定是不俗.http更是不用说了。SSH以出色的加密传输打败了那要死的设备(应该是人)

缺点:看到最后的语句,可能有人会问,组和用户可以用echo来加入,密码为什么要复制文件呢。答案是,我试过了密码用重定向加入到文件中,结果进去一看,是不对的。所以用了这个复制文件的笨方法。所有用户的密码全都在这里。也就是说,你在WEB管理页改了密码,在每次开机30秒,就会变成原来的密码(就是这个文件里写的)

最后到 系统管理->访问设置 中开启SSH 并先取 使用密码登陆 设定远程端口(我用的80,只要能上网的地方,就行)  

linux 命令行 共享 无线网络

这几天改了路由器,前面的贴子里写了。刷了TOMATO DUALWAN的固件,可以双卡合并,但是这个固件不支持中继,我的笔记本还要用无线。怎么能合并无线网和有线网,又能让笔记本上合并后的无线网呢?

答案是,共享一台电脑上的无线网卡,转成有线,插在路由的WAN2上,这样路由的无线就可以AP了。

下面来说说如何在LINUX下共享无线网络。

具体过程
1. 打开无线网卡

ifup wlan0
2. 列出区域内的无线网络

iwlist wlan0 scan
3. 假设要连接到网络MyHome(即essid为MyHome的网络),那么输入命令

iwconfig wlan0 essid "MyHome"
如果网络是加密的,密码是0123456789,那么就输入命令

iwconfig wlan0 essid "MyHome" key 0123456789
4. 如果正常的话,输入

iwconfig wlan0
就可以看到连接正常的各项参数了。

5. 如果是用DHCP获取IP的,那么用dhclient或dhcpcd获取ip

dhclient wlan0

dhcpcd wlan0
6. 现在无线网卡应该可以正常使用了

7.设置转发

#!/bin/sh  
# Name: nat.sh  
# Author: Bixuan  
# Date: 2003/04/04  
  
echo "Your user the iptables firewall.";  
ETH="wlan0"  #外网的网卡
SRC="192.168.3.0/24" #内网的IP段
   
  
case $1 in  
   start)  
       echo 1 > /proc/sys/net/ipv4/ip_forward  
       /sbin/modprobe ip_tables  
       /sbin/modprobe iptable_filter  
        /sbin/modprobe iptable_nat  
        /sbin/modprobe ip_conntrack  
        /sbin/modprobe ip_conntrack_ftp  
        /sbin/modprobe ip_nat_ftp  
        /sbin/iptables -F INPUT  
        /sbin/iptables -F FORWARD  
        /sbin/iptables -F POSTROUTING -t nat  
        /sbin/iptables -P FORWARD DROP  
        /sbin/iptables -t nat -A POSTROUTING -o $ETH -s $SRC -j MASQUERADE  
        /sbin/iptables -A FORWARD -i $ETH -m state –state ESTABLISHED,RELATED -j ACCEPT  
        /sbin/iptables -A FORWARD -s $SRC -j ACCEPT  
        echo "Nat is strating …… [ OK ]"  
        ;;  
   stop)  
echo 0 > /proc/sys/net/ipv4/ip_forward  
       ;;  
    *)  
        echo $"Usage: $0 {start|stop}";  
esac
 

我以写成脚本nat

浏览器 定制 扩展 (转)

前言

  由于本人在开发中经常要在程序中嵌入浏览器,为了符合自己的需求经常要对浏览器进行扩展和定制, 解决这些问题需在网上找资料和学习的过程,我想可能很多开发者或许会遇到同样的问题,特写此文,以供大家参考。

在MFC中使用浏览器

  在MFC中微软为我们提供了CHtmlView、CDHtmlDialog类让我们的程序很方便的嵌入浏览器和进行浏览器的二次开发,这比直 接使用WebBrowser控件要方便很多,所以本文中讨论的浏览器的问题都是针对CHtmlView来讨论的。文中将提到一个类CLhpHtmlView, 它是CHtmlView的派生类,文中提及的扩展或定制都将在CLhpHtmlView类(或派生类)上实现。

怎样扩展或定制浏览器

  浏览器定义了一些扩展接口(如IDocHostUIHandler可以定制浏览器界面有关的行为),以便开发者进行定制和扩展。浏览 器会在需要的时候向他的控制站点查询这些接口,在控制站点里实现相应的接口就可以进行相应的扩展。在MFC7.01类 库中,CHtmlView使用的控制站点是CHtmlControlSite的,在CHtmlControlSite类中 只实现了接口IDocHostUIHandler, 而要实现更多的扩展接口,必须用自定义的控制站类来取代CHtmlControlSite,在下文中提及的类CDocHostSite即为自定义 的控制站类。

关于接口的介绍请参考:

http://dev.csdn.net/develop/article/48/48483.shtm 

如何使自定义的控制站点来替换默认的控制站点呢?在MFC7.0中只需重载CHtmlView的虚函数CreateControlSite即可:
 

BOOL CLhpHtmlView::CreateControlSite(COleControlContainer * pContainer, 
COleControlSite ** ppSite, UINT /*nID*/, REFCLSID /*clsid*/)
{
	*ppSite = new CDocHostSite(pContainer, this);// 创建自己的控制站点实例
	return (*ppSite) ? TRUE : FALSE;
}

VC6.0要替换控制站要复杂的多,这里就不讨论了,如需要6.0版本的请给我发邮件到yourshine@21cn.com。

定制鼠标右键弹出出菜单

  要定制浏览器的鼠标右键弹出菜单,必须在自定义的控制站点类中实现IDocHostUIHandler2接口,并且IE的 版本是5.5或以上。在接口IDocHostUIHandler2的ShowContextMenu方法中调用浏览器类的OnShowContextMenu虚函数,我们 在浏览器类的派生类重载此虚函数即可实现右键菜单的定制,参见代码
 

HRESULT CDocHostSite::XDocHostUIHandler::ShowContextMenu(DWORD dwID,
                                                         POINT * ppt,
                                                         IUnknown * pcmdtReserved,
                                                         IDispatch * pdispReserved)
{
	METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
	return pThis->m_pView->OnShowContextMenu( dwID, ppt, pcmdtReserved,pdispReserved );
}

HRESULT CLhpHtmlView::OnShowContextMenu(DWORD dwID, 
                                        LPPOINT ppt,
                                        LPUNKNOWN pcmdtReserved, 
                                        LPDISPATCH pdispReserved)
{
	HRESULT result = S_FALSE;

	switch(m_ContextMenuMode)
	{
	case NoContextMenu:			// 无菜单
		result=S_OK;
		break;
	case DefaultMenu:				// 默认菜单
		break;
	case TextSelectionOnly:			// 仅文本选择菜单
		if(!(dwID == CONTEXT_MENU_TEXTSELECT || dwID == CONTEXT_MENU_CONTROL))
			result=S_OK;
		break;
	case CustomMenu:				// 自定义菜单
		if(dwID!=CONTEXT_MENU_TEXTSELECT)
			result=OnShowCustomContextMenu(ppt,pcmdtReserved,pdispReserved);
		break;
	}

	return result;
}		

在CLhpHtmlView中定义的枚举类型CONTEXT_MENU_MODE举出了定制右键弹出菜单的四种类型
 

enum CONTEXT_MENU_MODE		// 上下文菜单
{
	NoContextMenu,		// 无菜单
	DefaultMenu,		// 默认菜单
	TextSelectionOnly,		// 仅文本选择菜单
	CustomMenu		// 自定义菜单
};

  通过CLhpHtmlView的函数SetContextMenuMode来设置右键菜单的类型。如果设定的右键弹出菜单是“自定义菜单”类型, 我们只要在CLhpHtmlView的派生类中重载OnShowCustomContextMenu虚函数即可,如下代码 CDemoView是CLhpHtmlView的派生类

HRESULT CDemoView::OnShowCustomContextMenu(LPPOINT ppt, LPUNKNOWN pcmdtReserved,LPDISPATCH pdispReserved)
{
	if ((ppt==NULL)||(pcmdtReserved==NULL)||(pcmdtReserved==NULL))
		return S_OK;

	HRESULT hr=0;
	IOleWindow *oleWnd=NULL;
    	hr=pcmdtReserved->QueryInterface(IID_IOleWindow, (void**)&oleWnd);
	if((hr != S_OK)||(oleWnd == NULL))
		return S_OK;

	HWND hwnd=NULL;
	hr=oleWnd->GetWindow(&hwnd);
	if((hr!=S_OK)||(hwnd==NULL))
	{
		oleWnd->Release();
		return S_OK;
	}

	IHTMLElementPtr	pElem=NULL;
	hr = pdispReserved->QueryInterface(IID_IHTMLElement, (void**)&pElem);
	if(hr != S_OK)
	{
		oleWnd->Release();
		return S_OK;
	}

	IHTMLElementPtr	pParentElem=NULL;

	_bstr_t	tagID;
	BOOL go=TRUE;

	pElem->get_id(&tagID.GetBSTR());
	
	while(go && tagID.length()==0)
	{
		hr=pElem->get_parentElement(&pParentElem);
		if(hr==S_OK && pParentElem!=NULL)
		{
			pElem->Release();
			pElem=pParentElem;
			pElem->get_id(&tagID.GetBSTR());
		}
		else
			go=FALSE;
	};
	if(tagID.length()==0)
		tagID="no id";

	CMenu Menu,SubMenu;
	Menu.CreatePopupMenu();
	
	CString strTagID = ToStr(tagID);

	if(strTagID == "red")
		Menu.AppendMenu(MF_BYPOSITION, ID_RED, "您点击的是红色");
	else if(strTagID == "green")
		Menu.AppendMenu(MF_BYPOSITION, ID_GREEN, "您点击的是绿色");
	else if(strTagID == "blue")
		Menu.AppendMenu(MF_BYPOSITION, ID_BLUE, "您点击的是蓝色");
	else
		Menu.AppendMenu(MF_BYPOSITION, ID_NONE, "你点了也白点,请在指定的地方点击");
		
	int MenuID=Menu.TrackPopupMenu(TPM_RETURNCMD|TPM_LEFTALIGN|TPM_RIGHTBUTTON,ppt->x, ppt->y, this);
	switch(MenuID)
	{
	case ID_RED:
		MessageBox("红色");
		break;
	case ID_GREEN:
		MessageBox("红色");
		break;
	case ID_BLUE:
		MessageBox("红色");
		break;
	case ID_NONE:
		MessageBox("haha");
		break;
	}

	oleWnd->Release();
	pElem->Release();

	return S_OK;
}		


实现脚本扩展(很重要的external接口)

  在你嵌入了浏览器的工程中,如果网页的脚本中能调用C++代码,那将是一件很惬意的事情,要实现这种交互,就必须实现脚本扩展。实现脚本扩展就是在程序中实现一个IDispatch接口,通过CHtmlView类的OnGetExternal虚函数返回此接口指针,这样就可以在脚本中通过window.external.XXX(关键字window可以省略)来 引用接口暴露的方法或属性(XXX为方法或属性名)。在MFC中从CCmdTarget派生的类都可以实现自动化,而不必在MFC工程中引入繁杂的ATL。从CCmdTarget派生的类实现自动化接口的时候不要忘了在构造函数中调用EnableAutomation函数。
  要使虚函数OnGetExternal发挥作用必须在 自定义的控制站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetExternal方法中调用浏览器类的OnGetExternal虚函数,我们在浏览器类的派生类重载OnGetExternal虚函数, 通过参数lppDispatch返回一个IDispatch指针,这样脚本中引用window.external时就是引用的返回的接口,参见代码

HRESULT CDocHostSite::XDocHostUIHandler::GetExternal(IDispatch ** ppDispatch)
{
	METHOD_PROLOGUE(CDocHostSite, DocHostUIHandler);
	return pThis->m_pView->OnGetExternal( ppDispatch );
}

CLhpHtmlView::CLhpHtmlView(BOOL isview)
{
	......
	EnableAutomation();// 允许自动化
}

HRESULT CLhpHtmlView::OnGetExternal(LPDISPATCH *lppDispatch)
{
	*lppDispatch = GetIDispatch(TRUE);// 返回自身的IDispatch接口
	return S_OK;
}      

请注意上面代码中,在OnGetExternal返回的是自身IDispatch接口, 这样就不比为脚本扩展而另外写一个从CCmdTarget派生的新类, CLhpHtmlView本身就是从CCmdTarget派生,直接在上面实现接口就是。

下用具体示例来说明怎样实现脚本扩展

示例会在网页上点击一个按钮而使整个窗口发生抖动

从CLhpHtmlView派生一个类CDemoView,在类中实现IDispatch, 并通过IDispatch暴露方法WobbleWnd

---------------------------------------------------------------------------
文件 DemoView.h
---------------------------------------------------------------------------
.......
class CDemoView : public CLhpHtmlView
{
	......
	DECLARE_DISPATCH_MAP() // 构建dispatch映射表以暴露方法或属性
	......
	void WobbleWnd();// 抖动窗口
};

---------------------------------------------------------------------------
文件 DemoView.cpp
---------------------------------------------------------------------------

......

// 把成员函数映射到Dispatch映射表中,暴露方法给脚本
BEGIN_DISPATCH_MAP(CDemoView, CLhpHtmlView)
	DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)
END_DISPATCH_MAP()

......

void CDemoView::WobbleWnd()
{
	// 在这里实现抖动窗口
	......
}

---------------------------------------------------------------------------
文件 Demo.htm
---------------------------------------------------------------------------

...... onclick="external.WobbleWnd()" ......		

这里我要介绍一下DISP_FUNCTION宏,它的作用是将一个函数映射到Dispatch映射表中,我们看

DISP_FUNCTION(CDemoView, "WobbleWnd", WobbleWnd, VT_EMPTY, VTS_NONE)

CDemoView是宿主类名, "WobbleWnd"是暴露给外面的名字(脚本调用时使用的名字), VT_EMPTY是返回值得类型为空,VTS_NONE说明此方法没有参数,如果要映射的函数有返回值和参数该 如何映射,通过下面举例来说明
 

DISP_FUNCTION(CCalendarView,"TestFunc",TestFunc,VT_BOOL,VTS_BSTR VTS_I4 VTS_I4)

BOOL TestFunc(LPCSTR param1, int param2, int param3)
{
	.....
}		

  参数表VTS_BSTR VTS_I4 VTS_I4是用空格分隔,他们的类型映射请参考MSDN,这要提醒的是不要把VTS_BSTR与CString对应,而应与LPCSTR对应。

C++代码中如何调用网页脚本中的函数

  IHTMLDocument2::scripts属性表示HTML文档中所有脚本对象。使用脚本对象的IDispatch接口的GetIDsOfNames方法可以得到脚本函数的 DispID,得到DispID后,使用IDispatch的Invoke函数可以调用对应的脚本函数。CLhpHtmlView提供了方便的调用JavaScript的函数,请参考CLhpHtmlView中有关键字“JScript”的代码。

定制消息框的标题

  我们在脚本中调用alert弹出消息框时,消息框的标题是微软预定义的“Microsoft Internet Explorer”,如下图:

  在自定义的控制站点类中实现IDocHostShowUI接口,在接口的ShowMessage方法中调用浏览器的OnShowMessage,我们重载 OnShowMessage虚函数即可定制消息框的标题,实现代码如下:
 

// 窗口标题"Microsoft Internet Explorer"的资源标识
#define IDS_MESSAGE_BOX_TITLE 2213
HRESULT CLhpHtmlView::OnShowMessage(HWND hwnd,
                                    LPOLESTR lpstrText,
                                    LPOLESTR lpstrCaption,
                                    DWORD dwType,
                                    LPOLESTR lpstrHelpFile,
                                    DWORD dwHelpContext,
                                    LRESULT * plResult)
{
    //载入Shdoclc.dll 和IE消息框标题字符串
    HINSTANCE hinstSHDOCLC = LoadLibrary(TEXT("SHDOCLC.DLL"));
    if (hinstSHDOCLC == NULL)
        return S_FALSE;

	CString strBuf,strCaption(lpstrCaption);
	strBuf.LoadString(hinstSHDOCLC, IDS_MESSAGE_BOX_TITLE);

    // 比较IE消息框标题字符串和lpstrCaption
    // 如果相同,用自定义标题替换
	if(strBuf==lpstrCaption)
        strCaption = m_DefaultMsgBoxTitle;

    // 创建自己的消息框并且显示
    *plResult = MessageBox(CString(lpstrText), strCaption, dwType);

    //卸载Shdoclc.dll并且返回
    FreeLibrary(hinstSHDOCLC);
    return S_OK;
}		

从代码中可以看到通过设定m_DefaultMsgBoxTitle的值来改变消息宽的标题,修改此值是同过SetDefaultMsgBoxTitle来实现

void CLhpHtmlView::SetDefaultMsgBoxTitle(CString strTitle)
{
	m_DefaultMsgBoxTitle=strTitle;
}


怎样定制、修改浏览器向Web服务器发送的HTTP请求头

  在集成了WebBrowser控件的应用中,Web服务器有时可能希望客户端(浏览器)发送的HTTP请求中附带一些额外的信息或自定义的 HTTP头字段,这样就必须在浏览器中控制向Web服务器发送的HTTP请求。 下面是捕获的一个普通的用浏览器发送的HTTP请求头:

GET /text7.htm HTTP/1.0
Accept: image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, 
application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Referer: http://localhost
Accept-Language: en-us
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Poco 0.31; LHP Browser 1.01; 
.NET CLR 1.1.4322)
Host: localhost
Connection: Keep-Alive

CHtmlView的 
void Navigate2(
   LPCTSTR lpszURL,
   DWORD dwFlags = 0,
   LPCTSTR lpszTargetFrameName = NULL,
   LPCTSTR lpszHeaders = NULL,
   LPVOID lpvPostData = NULL,
   DWORD dwPostDataLen = 0 
);

函数参数lpszHeaders可以指定HTTP请求头,示例如下:

Navigate2(_T("http://localhost"),NULL,NULL, "MyDefineField: TestValue");

我们捕获的HTTP头如下:

怎样修改浏览器标识

  在HTTP请求头中User-Agent字段表明了浏览器的版本以及操作系统的版本等信息。WEB服务器经常需要知道用户请求页面时是来自IE还是来自自己的客户端中的WebBrowser控件, 以便分开处理,而WebBrowser控件向WEB服务器发送的浏览器标识(User-Agent字段)跟用IE发送的是一样的,怎样区分自己的浏览器和IE呢? 微软没有提供现成的方法,要自己想法解决。 前面讨论的定制HTTP请求头就是为这一节准备的。 思路是这样的: 在自己的浏览器里处理每一个U页面请求,把请求头User-Agent改成自己想要的。 在CHtmlView的OnBeforeNavigate2虚函数里来修改HTTP请求是再好不过了,

#define WM_NVTO		(WM_USER+1000)

class NvToParam
{
public:
	CString URL;
	DWORD Flags;
	CString TargetFrameName;
	CByteArray PostedData;
	CString Headers;
};


void CDemoView::OnBeforeNavigate2(LPCTSTR lpszURL, 
                                  DWORD nFlags, 
                                  LPCTSTR lpszTargetFrameName, 
                                  CByteArray& baPostedData, 
                                  LPCTSTR lpszHeaders, 
                                  BOOL* pbCancel)
{
	CString strHeaders(lpszHeaders);
	if(strHeaders.Find("User-Agent:LHPBrowser 1.0") < 0)// 检查头里有没有自定义的User-Agent串
	{
		*pbCancel = TRUE;// 没有,取消这次导航

		if(!strHeaders.IsEmpty())
			strHeaders += "rn";
		strHeaders += "User-Agent:LHPBrowser 1.0";// 加上自定义的User-Agent串

		NvToParam* pNvTo = new NvToParam;
		pNvTo->URL = lpszURL;
		pNvTo->Flags = nFlags;
		pNvTo->TargetFrameName = lpszTargetFrameName;
		baPostedData.Copy(pNvTo->PostedData);
		pNvTo->Headers = strHeaders;

		// 发送一个自定义的导航消息,并把参数发过去
		PostMessage(WM_NVTO,(WPARAM)pNvTo);
		return;
	}

	CHtmlView::OnBeforeNavigate2(lpszURL, 
	                             nFlags, 
	                             lpszTargetFrameName, 
	                             baPostedData, 
	                             lpszHeaders, 
	                             pbCancel);
}

LRESULT CDemoView::OnNvTo(WPARAM wParam, LPARAM lParam)
{
	NvToParam* pNvTo = (NvToParam*)wParam;
	Navigate2((LPCTSTR)pNvTo->URL, 
	           pNvTo->Flags, 
	           pNvTo->PostedData, 
	           (LPCTSTR)pNvTo->TargetFrameName, 
	           (LPCTSTR)pNvTo->Headers);
	delete pNvTo;
	return 1;
}

  在OnBeforeNavigate2中如果发现没有自定义的User-Agent串,就加上这个串,并取消本次导航,再Post一个消息(一定要POST,让OnBeforeNavigate2跳出以后再进行导航 ),在消息中再次导航,再次导航时请求头已经有了自己的标识,所以能正常的导航。

去掉讨厌的异常警告

  在程序中使用了CHtmlView以后,我们在调整窗口大小的时候经常会看到输出窗口输出的异常警告: ReusingBrowser.exe 中的 0x77e53887 处最可能的异常: Microsoft C++ exception: COleException @ 0x0012e348 。

Warning: constructing COleException, scode = DISP_E_MEMBERNOTFOUND($80020003).

这是由于CHtmlView在处理WM_SIZE消息时的一点小问题引起的,采用如下代码处理WM_SIZE消息就不会有此警告了

void CLhpHtmlView::OnSize(UINT nType, int cx, int cy)
{
	CFormView::OnSize(nType, cx, cy);

	if (::IsWindow(m_wndBrowser.m_hWnd)) 
	{ 
		CRect rect; 
		GetClientRect(rect); 
		// 就这一句与CHtmlView的不同
		::AdjustWindowRectEx(rect, GetStyle(), FALSE, WS_EX_CLIENTEDGE);
		m_wndBrowser.SetWindowPos(NULL, 
		                          rect.left, 
		                          rect.top, 
		                          rect.Width(), 
		                          rect.Height(), 
		                          SWP_NOACTIVATE | SWP_NOZORDER); 
	} 
}


怎样处理浏览器内的拖放

  有时可能有这样的需求,我们希望在资源管理器里托一个文件到浏览器而做出相应的处理,甚至是将文件拖到某一个网页元素上来做出相应的处理,而浏览器默认的处理拖放文件操作是将文件打开,但WebBrowser控件给了我们一个自己处理拖放的机会。 那就是在自定义的控制站点类中实现IDocHostUIHandler,在接口IDocHostUIHandler的GetDropTarget方法中调用 浏览器类的OnGetDropTarget虚函数。要处理网页内的拖放,必需在OnGetDropTarget函数中返回一个自己定义的IDropTarget接口指针, 所以我们自己写一个类CMyOleDropTarget从COleDropTarget类派生,并且在实现IDropTarget接口,此类的代码在这就不列出了,请下载演示 程序,参考文件MyOleDropTarget.h和MyOleDropTarget.cpp。我们看CLhpHtmlView中OnGetDropTarget的代码

HRESULT CLhpHtmlView::OnGetDropTarget(LPDROPTARGET pDropTarget, LPDROPTARGET* ppDropTarget )
{
	m_DropTarget.SetIEDropTarget(pDropTarget);

	LPDROPTARGET pMyDropTarget;
	pMyDropTarget = (LPDROPTARGET)m_DropTarget.GetInterface(&IID_IDropTarget);
	if(pMyDropTarget)
	{
		*ppDropTarget = pMyDropTarget;
		pMyDropTarget->AddRef();
		return S_OK;
	}

	return S_FALSE;
}

  m_DropTarget即为自定义的处理拖放的对象。这样就能通过在从CLhpHtmlView派生的类中重载OnDragEnter、OnDragOver、 OnDrop、OnDragLeave虚函数来处理拖放了。在这里顺带讲一下视图是怎样处理拖放的。 要使视图处理拖放,首先在视图里添加一个COleDropTarget(或派生类)成员变量,如CLhpHtmlView中的“CMyOleDropTarget m_DropTarget;”,再在 视图创建时调用COleDropTarget对象的Register,即把视图与COleDropTarget对象关联起来,如CLhpHtmlView中的“m_DropTarget.Register(this);”,再对拖放 触发的事件进行相应的处理, OnDragEnter 把某对象拖入到视图时触发,在此检测拖入的对象是不是视图想接受的对象,如是返回“DROPEFFECT_MOVE”表示接受此对象,如

if(pDataObject->IsDataAvailable(CF_HDROP))// 被拖对象是文件吗?
	return DROPEFFECT_MOVE;

  OnDragOver 被拖对象在视图上移动,同OnDragEnter一样检测拖入对象,如果要接受此对象返回“DROPEFFECT_MOVE”。 OnDrop 拖着被拖对象在视图上放开鼠标,在这里对拖入对象做出处理; OnDragLeave 拖着被拖对象离开视图。 C++的代码写好了,但事情还没完,还必须在网页里用脚本对拖放事件进行处理, 即页面里哪个元素要接受拖放对象哪个元素就要处理ondragenter、ondragover、ondrop,代码其实很简单,让事件的返回值为false即可,这样 C++的代码才有机会处理拖放事件,代码如下:

......
<td ondragenter="event.returnValue = false" ondragover="event.returnValue = false" 
ondrop="event.returnValue = false">
......

  如果要使整个视图都接受拖放,则在Body元素中处理此三个事件。 注意:别忘了让工程对OLE的支持即在初始化应用程序时调用AfxOleInit()。

怎样禁止网页元素的选取

  用网页做界面时多数情况下是不希望网页上的元素是能够被鼠标选中的, 要使网页元素不能被选中做法是:给浏览器的“宿主信息标记”加上DOCHOSTUIFLAG_DIALOG标记。

“宿主信息标记”用N个标记位来控制浏览器的许多性质,如:

  • 禁用浏览器的3D的边缘;
  • 禁止滚动条;
  • 禁用脚本;
  • 定义双击处理的方式;
  • 禁用浏览器的自动完成功能;

…… 更多详情请参考MSDN的DOCHOSTUIFLAG帮助。

怎样修改“宿主信息标记”?

在CDocHostSite中实现IDocHostUIHandler, 在GetHostInfo方法中调用浏览器的OnGetHostInfo虚函数,在虚函数OnGetHostInfo中便可指定“宿主信息标记”,如:

HRESULT CLhpHtmlView::OnGetHostInfo(DOCHOSTUIINFO * pInfo)
{
	pInfo->cbSize = sizeof(DOCHOSTUIINFO);
	pInfo->dwFlags = DOCHOSTUIFLAG_DIALOG | 
	                    DOCHOSTUIFLAG_THEME  | 
	                    DOCHOSTUIFLAG_NO3DBORDER | 
	                    DOCHOSTUIFLAG_SCROLL_NO;
	pInfo->dwDoubleClick = DOCHOSTUIDBLCLK_DEFAULT;

	return S_OK;
}

用脚本也可实现: 在Head中加入脚本:

document.onselectstart=new Function(''return false'');

或者

<body onselectstart="return false">。


其它

在CLhpHtmlView中还提供了几个函数, 修改网页元素的内容:

BOOL PutElementHtml(CString ElemID,CString Html);

取表单元素的值:

BOOL GetElementValue(CString ElemID,CString& Value);

设置表单元素的值:

BOOL PutElementValue(CString ElemID,CString Value);

给表单元素设置焦点:

void ElementSetFocus(CString EleName);

代码下载:reusingbrowser