(十二)洞悉linux下的Netfilteramp;iptables:iptables命令行工具源码解析【下】

iptables用户空间和内核空间的交互

iptables目前已经支持IPv4IPv6两个版本了,因此它在实现上也需要同时兼容这两个版本。iptables-1.4.0在这方面做了很好的设计,主要是由libiptc库来实现。libiptciptables control library的简称,是Netfilter的一个编程接口,通常被用来显示、操作(查询、修改、添加和删除)netfilter的规则和策略等。使用libipq库和ip_queue模块,几乎可以实现任何在内核中所实现的功能。

        libiptc库位于iptables源码包里的libiptc目录下,共六个文件还是比较容易理解。我们都知道,运行在用户上下文环境中的代码是可以阻塞的,这样,便可以使用消息队列和 UNIX 域套接字来实现内核态与用户态的通信。但这些方法的数据传输效率较低,Linux 内核提供 copy_from_user()/copy_to_user() 函数来实现内核态与用户态数据的拷贝,但这两个函数会引发阻塞,所以不能用在硬、软中断中。一般将这两个特殊拷贝函数用在类似于系统调用一类的函数中,此类函数在使用中往往"穿梭"于内核态与用户态。此类方法的工作原理为:

 23069658_133802057141y1.jpg

        其中相关的系统调用是需要用户自行编写并载入内核。一般情况都是,内核模块注册一组设置套接字选项的函数使得用户空间进程可以调用此组函数对内核态数据进行读写。我们的libiptc库正是基于这种方式实现了用户空间和内核空间数据的交换。

    为了后面便于理解,这里我们简单了解一下在socket编程中经常要接触的两个函数:

int setsockopt(int sockfd, int proto, int cmd, void *data, int datalen)

int getsockopt(int sockfd, int proto, int cmd, void *data, int datalen)

这个两个函数用来控制相关socket文件描述符的一些选项值,如设置(获取)接受或发送缓冲区的大小、设置(获取)接受或发送超时值、允许(禁止)重用本地端口和地址等等。

参数说明:

sockfd:为socket的文件描述符;

protosock协议,IP RAW的就用SOL_SOCKET/SOL_IP等,TCP/UDP socket的可用SOL_SOCKET/SOL_IP/SOL_TCP/SOL_UDP等,即高层的socket是都可以使用低层socket的命令字 的;

cmd:操作命令字,由自己定义,一般用于扩充;

data:数据缓冲区起始位置指针,set操作时是将缓冲区数据写入内核,get的时候是将内核中的数据读入该缓冲区;

datalen:参数data中的数据长度。

 

我们可以通过扩充新的命令字(即前面的cmd字段)来实现特殊应用程序的内核与用户空间的数据交换,内核实现新的sockopt命令字有两类:一类是添加完整的新的协议后引入;一类是在原有协议命令集的基础上增加新的命令字。以netfilter为例,它就是在原有的基础上扩展命令字,实现了内核与用户空间的数据交换。Netfilter新定义的命令字如下:

setsockopt新增命令字:

#define IPT_SO_SET_REPLACE //设置规则

#define IPT_SO_SET_ADD_COUNTERS   //加入计数器

getsockopt新增命令字;

#define IPT_SO_GET_INFO               //获取ipt_info

#define IPT_SO_GET_ENTRIES         //获取规则

#define IPT_SO_GET_REVISION_MATCH //获取match

#define IPT_SO_GET_REVISION_TARGET      //获取target

       一个标准的setsockopt()操作的调用流程如下:

 23069658_1338015272zLt0.jpg

ip_setsockopt调用时,如果发现是一个没有定义的协议,并且判断现在这个optname是否为netfilter所设置,如果是则调用netfilter所设置的特殊处理函数,于是加入netfiltersockopt特殊处理后,新的流程如下:

 23069658_1338015214jw2J.jpg

        netfitler对于会实例化一些struct nf_sockopt_ops{}对象,然后通过nf_register_sockopt()将其注册到全局链表nf_sockopts里。

struct nf_sockopt_ops

{

         struct list_head list;

         int pf;

 

         /* Non-inclusive ranges: use 0/0/NULL to never get called. */

         int set_optmin;

         int set_optmax;

         int (*set)(struct sock *sk, int optval, void __user *user, unsigned int len);

         int (*compat_set)(struct sock *sk, int optval,void __user *user, unsigned int len);

 

         int get_optmin;

         int get_optmax;

         int (*get)(struct sock *sk, int optval, void __user *user, int *len);

         int (*compat_get)(struct sock *sk, int optval,void __user *user, int *len);

 

         /* Number of users inside set() or get(). */

         unsigned int use;

         struct task_struct *cleanup_task;

};

 

         继续回到libiptc中。libiptc库中的所有函数均以“iptc_”开头,主要有下面一些接口(节选自libiptc.h)

typedef struct iptc_handle *iptc_handle_t;

 

/* Does this chain exist? */

int iptc_is_chain(const char *chain, const iptc_handle_t handle);

 

/* Take a snapshot of the rules.  Returns NULL on error. */

iptc_handle_t iptc_init(const char *tablename);

 

/* Cleanup after iptc_init(). */

void iptc_free(iptc_handle_t *h);

 

/* Iterator functions to run through the chains.  Returns NULL at end. */

const char *iptc_first_chain(iptc_handle_t *handle);

const char *iptc_next_chain(iptc_handle_t *handle);

 

/* Get first rule in the given chain: NULL for empty chain. */

const struct ipt_entry *iptc_first_rule(const char *chain,iptc_handle_t *handle);

/* Returns NULL when rules run out. */

const struct ipt_entry *iptc_next_rule(const struct ipt_entry *prev,iptc_handle_t *handle);

 

/* Returns a pointer to the target name of this entry. */

const char *iptc_get_target(const struct ipt_entry *e,iptc_handle_t *handle);

 

/* Is this a built-in chain? */

int iptc_builtin(const char *chain, const iptc_handle_t handle);

 

int iptc_append_entry(const ipt_chainlabel chain,

                         const struct ipt_entry *e,

                         iptc_handle_t *handle);

 

/* Zeroes the counters in a chain. */

int iptc_zero_entries(const ipt_chainlabel chain,iptc_handle_t *handle);

 

/* Creates a new chain. */

int iptc_create_chain(const ipt_chainlabel chain,iptc_handle_t *handle);

 

/* Makes the actual changes. */

int iptc_commit(iptc_handle_t *handle);

 

        上面这些接口都是为IPv4定义了,同样的IPv6的接口均定义在libip6tc.h头文件中,都以“ip6tc_”开头(如此说来,IPv4的头文件应该叫libip4tc.h才比较合适)。然后在libip4tc.clibip6tc.c文件中分别通过宏定义的形式将IPv4IPv6对外的接口均统一成“TC_”开头的宏,并在libiptc.c中实现这些宏即可。如下图所示:

 23069658_1338020576nHad.jpg

        这里我们看到iptables-v4iptables-v6都和谐地统一到了libiptc.c中,后面我们分析的时候只要分析这些相关的宏定义的实现即可。

      在继续往下分析之前我们先看一下STRUCT_TC_HANDLE这个比较拉风的结构体,它用于存储我们和内核所需要交换的数据。说的通俗一些,就是从内核中取出的表的信息会存储到该结构体类型的变量中;当我们向内核提交iptables变更时,也需要一个该结构体类型的变量用于存储我们所要提交的数据。(定义在ip_tables.h头文件中)

适用于当getsockopt的参数为IPT_SO_GET_INFO,用于从内核读取表信息

struct ipt_getinfo               #define STRUCT_GETINFO struct ipt_getinfo

{

         /* Which table: caller fills this in. */    #从内核取出的表信息会存储在该结构体中

         char name[IPT_TABLE_MAXNAMELEN];

         /* Kernel fills these in. */

         unsigned int valid_hooks; /* Which hook entry points are valid: bitmask */

         unsigned int hook_entry[NF_IP_NUMHOOKS]; // Hook entry points: one per netfilter hook.

         unsigned int underflow[NF_IP_NUMHOOKS]; /* Underflow points. */

         unsigned int num_entries; /* Number of entries */

         unsigned int size; /* Size of entries. */

};

还有一个成员entries用保存表中的所有规则信息,每条规则都是一个ipt_entry的实例:

/* The argument to IPT_SO_GET_ENTRIES. */

struct ipt_get_entries

{

         /* Which table: user fills this in. */

         char name[IPT_TABLE_MAXNAMELEN];

         unsigned int size; /* User fills this in: total entry size. */

 

         struct ipt_entry entrytable[0]; /*内核里表示规则的结构,参见博文三. */

};

        

()、从内核获取数据:iptc_init()

都说磨刀不误砍柴工,接下来我们继续上一篇中do_command()函数里剩下的部分。*handle = iptc_init(*table); 这里即根据表名table去从内核中获取该表的自身信息和表中的所有规则。关于表自身的一些信息存储在handle->info成员里;表中所有规则的信息保存在handle->entries成员里。

      如果handle获取失败,则尝试加载完内核中相应的ko模块后再次执行iptc_init()函数。

      然后,针对“ADRI”操作需要做一些合法性检查,诸如-o选项不能用在PREROUTINGINPUT链中、-i选项不能用在POSTROUTINGOUTPUT链中。

if (target && iptc_is_chain(jumpto, *handle)) {

                   fprintf(stderr,"Warning: using chain %s, not extension\n",jumpto);

             if (target->t)

                            free(target->t);

                           

                   printf("Target is a chain,but we have gotten a target,then free it!\n");

 

                   target = NULL;

}

如果-j XXX 后面的XXX是一条用户自定义规则链,但是之前却解析出了标准target,那么需要将target的空间释放掉。很明显,目前我们的-j ACCEPT不会执行到这里。

if (!target  #如果没有指定target同样,我们的规则也不会执行到这里

&& (strlen(jumpto) == 0|| iptc_is_chain(jumpto, *handle)) #或者target是一条链或为空

)

{

         size_t size;

    … …        

}

        因为我们的targetACCEPT,已经被完全正确解析,即target=NULL。后面我们会执行else条件分子如下的代码:

e = generate_entry(&fw, matches, target->t);

用于生成一条iptables的规则,它首先会为e去申请一块大小n*match+target的空间,其中n为用户输入的命令行中的match个数,target为最后的动作。这里很明显,我们的命令只有一个tcpmatchtarget是标准target,即ACCEPT。将已经解析的fw赋给e,并对结构体e中其他的成员进行初始化,然后将相应的matchtarget的数据拷贝到e中对应的成员中。

size = sizeof(struct ipt_entry);

for (matchp = matches; matchp; matchp = matchp->next)

         size += matchp->match->m->u.match_size;

 

e = fw_malloc(size + target->u.target_size);

*e = *fw;

e->target_offset = size;

e->next_offset = size + target->u.target_size;

 

size = 0;

for (matchp = matches; matchp; matchp = matchp->next) {

         memcpy(e->elems + size, matchp->match->m, matchp->match->m->u.match_size);

         size += matchp->match->m->u.match_size;

}

memcpy(e->elems + size, target, target->u.target_size);

        最后所生成的规则e,其内存结构如下图所示:

 23069658_1338015414zByB.jpg

        这里再联系我们对内核中netfilter的分析就很容易理解了,一旦我们获取一条规则ipt_entry的首地址,那么我们能通过target_offset很快获得这条规则的target地址,同时也可以通过next_offset获得下一条ipt_entry规则的起始地址,很方便我们到时候做数据包匹配的操作。

 紧接着就是对解析出来的command命令进行具体操作,这里我们是-A命令,因此最后command命令就是CMD_APPEND,这里则执行append_entry()函数。

ret = append_entry(chain,  #链名,这里为INPUT

e,      #将用户的命令解析出来的最终的规则对象

nsaddrs,  #-s 后面源地址的个数

saddrs,   #用于保存源地址的数组

ndaddrs,  #-d 后面的目的地址的个数

daddrs,   #用于保存目的地址的数组

options&OPT_VERBOSE, #iptables命令是否有-v参数

handle   #从内核中取出来的规则表信息

);

 在append_entry内部调用了iptc_append_entry(chain, fw, handle),其实就是由宏即TC_APPEND_ENTRY所表示的那个函数。该函数内部有两个值得注意的结构体类型struct chain_head{}struct rule_head{},分别用于保存我们所要操作的链以及链中的规则:

struct chain_head

{

         struct list_head list;

         char name[TABLE_MAXNAMELEN];

         unsigned int hooknum;             /* hook number+1 if builtin */

         unsigned int references; /* 有多少-j 指定了我们的名字 */

         int verdict;                          /* verdict if builtin */

         STRUCT_COUNTERS counters;        /* per-chain counters */

         struct counter_map counter_map;

         unsigned int num_rules;           /* 本链中的规则数*/

         struct list_head rules;             /* 本链中所有规则的入口点 */

 

         unsigned int index;           /* index (needed for jump resolval) */

         unsigned int head_offset;        /* offset in rule blob */

         unsigned int foot_index; /* index (needed for counter_map) */

         unsigned int foot_offset;          /* offset in rule blob */

};

 

struct rule_head

{

         struct list_head list;

         struct chain_head *chain;

         struct counter_map counter_map;

         unsigned int index;           /* index (needed for counter_map) */

         unsigned int offset;          /* offset in rule blob */

 

         enum iptcc_rule_type type;

         struct chain_head *jump;      /* jump target, if IPTCC_R_JUMP */

 

         unsigned int size;              /* size of entry data */

         STRUCT_ENTRY entry[0];   #真正的规则入口点 sizeof计算时不会包含这个字段

};

TC_APPEND_ENTRY的函数实现:

int

TC_APPEND_ENTRY(const IPT_CHAINLABEL chain,

                   const STRUCT_ENTRY *e,

                   TC_HANDLE_T *handle)    #注意:这里的handle是个二级指针

{

         struct chain_head *c;

         struct rule_head *r;

 

         iptc_fn = TC_APPEND_ENTRY;

         if (!(c = iptcc_find_label(chain, *handle))) {      

#根据链名查找真正的链地址赋给c,此时c就指向了INPUT链的内存,

#包括INPUT中的所有规则和它的policy

                   DEBUGP("unable to find chain `%s'\n", chain);

                   errno = ENOENT;

                   return 0;

         }

 

         if (!(r = iptcc_alloc_rule(c, e->next_offset))) {

#ipt_entrynext_offset即指明了下一条规则的起始地址,同时这个值也说明了本条规则所占了存储空间的大小。这里所申请的空间大小=sizeof(rule_head)+当前规则所占的空间大小。

                   DEBUGP("unable to allocate rule for chain `%s'\n", chain);

                   errno = ENOMEM;

                   return 0;

         }

 

         memcpy(r->entry, e, e->next_offset);       #把规则拷贝到柔性数组entry中去

         r->counter_map.maptype = COUNTER_MAP_SET;

 

         if (!iptcc_map_target(*handle, r)) {   #主要是设置规则rtarget,后面分析。

                   DEBUGP("unable to map target of rule for chain `%s'\n", chain);

                   free(r);

                   return 0;

         }

 

         list_add_tail(&r->list, &c->rules); #将新规则r添加在链c的末尾

         c->num_rules++;              #同时将链中的规则计数增加

 

         set_changed(*handle);    #因为INPUT链中的规则已经被改变,则handle->changed=1;

         return 1;

}

         接下来分析一下设置target时其函数内部流程:

static int

iptcc_map_target(const TC_HANDLE_T handle,

            struct rule_head *r)

{

         STRUCT_ENTRY *e = r->entry;                 #取规则的起始地址

         STRUCT_ENTRY_TARGET *t = GET_TARGET(e);    #取规则的target

 

         /* Maybe it's empty (=> fall through) */

         if (strcmp(t->u.user.name, "") == 0) { #如果没有指定target,则将规则类型设为全放行

                   r->type = IPTCC_R_FALLTHROUGH;

                   return 1;

         }

 

         /* Maybe it's a standard target name... */

#因为都是标准target,因此将target中用户空间的user.name都置为空,设置verdict

#并将rule_head中的type字段为IPTCC_R_STANDARD

         else if (strcmp(t->u.user.name, LABEL_ACCEPT) == 0)

                   return iptcc_standard_map(r, -NF_ACCEPT - 1);

         else if (strcmp(t->u.user.name, LABEL_DROP) == 0)

                   return iptcc_standard_map(r, -NF_DROP - 1);

         else if (strcmp(t->u.user.name, LABEL_QUEUE) == 0)

                   return iptcc_standard_map(r, -NF_QUEUE - 1);

         else if (strcmp(t->u.user.name, LABEL_RETURN) == 0)

                   return iptcc_standard_map(r, RETURN);

         else if (TC_BUILTIN(t->u.user.name, handle)) {

                   /* Can't jump to builtins. */

                   errno = EINVAL;

                   return 0;

         } else {

                   /* 如果跳转的目标是一条用户自定义链,则执行下列操作*/

                   struct chain_head *c;

                   DEBUGP("trying to find chain `%s': ", t->u.user.name);

                   c = iptcc_find_label(t->u.user.name, handle); #找到要跳转的目的链的入口地址

                   if (c) {

                            DEBUGP_C("found!\n");

                            r->type = IPTCC_R_JUMP;  #rule_head结构的type字段置为跳转

                            r->jump = c;             #跳转的目标为t->u.user.name所指示的链

                            c->references++;         #跳转到的目的链因此而被引用了一次,则计数器++

                            return 1;

                   }

                   DEBUGP_C("not found :(\n");

         }

 

         /* 如果不是用户自定义链,它一定一个用户自定义开发的target模块,比如SNATLOG等。If not, kernel will reject... */

         /* memset to all 0 for your memcmp convenience: don't clear version */

         memset(t->u.user.name + strlen(t->u.user.name),

                0,

                FUNCTION_MAXNAMELEN - 1 - strlen(t->u.user.name));

         r->type = IPTCC_R_MODULE;  #比如SNATLOG等会执行到这里

         set_changed(handle);

         return 1;

}

 在append_entry()函数最后,将执行的执行结果返回给ret1表示成功;0表示失败。然后在做一下善后清理工作,如果命令行中有-v则将内核中表的快照dump一份详细信息出来显示给用户看:

if (verbose > 1)

     dump_entries(*handle);

clear_rule_matches(&matches); //释放matches所占的存储空间

 由struct ipt_entry e;所存储的规则信息已经被提交给了handle对象对应的成员,因此将e所占的存储空间也释放:

if (e != NULL) {

              free(e);

              e = NULL;

}

 将全局变量opts复位,初始化时opts=original_opts。因为在解析--syntcp的解析参数被加进来了:

static struct option original_opts[] = {

                  { "append", 1, NULL, 'A' },

                  { "delete", 1, NULL,  'D' },

                  … …

}

至此,do_command()函数的执行就算全部完成了。

 

()、向内核提交变更:iptc_commit()

执行完do_command()解析完命令行参数后,用户所作的变更仅被提交给了handle这个结构体变量,这个变量里的所有数据在执行iptc_commit()函数前都驻留在内存里。因此,在iptables-standalone.c里有如下的代码语句:

ret = do_command(argc, argv, &table, &handle);

if (ret)

         ret = iptc_commit(&handle);

  当do_command()执行成功后才会去执行iptc_commit()函数,将handle里的数据提交给Netfilter内核。

        iptc_commit()的实现函数为int TC_COMMIT(TC_HANDLE_T *handle),我们只分析IPv4的情形,因此专注于libiptc.c文件中该函数的实现。

        在TC_COMMIT()函数中,又出现了我们在分析Netfilterfilter表时所见到的一些重要结构体STRUCT_REPLACE *replSTRUCT_COUNTERS_INFO *newcounters;还有前面出现的struct chain_head *c;结构体。

new_number = iptcc_compile_table_prep(*handle, &new_size);

iptcc_compile_table_prep()该函数主要做的工作包含几个方面:

a.初始化handle里每个struct chain_head{}结构体成员中的head_offsetfoot_indexfoot_offset

b.对每个链(struct chain_head{})中的每条规则,再分别计算它们的offsetindex

c.计算handle所指示的表中所有规则所占的存储空间的大小new_size,以及规则的总条数new_number

 接下来,为指针repl;申请存储空间,所申请的大小为sizeof(struct ipt_replace)+new_size。因为struct ipt_replace{}结构的末尾有一个柔性数组struct ipt_entry entries[0]; 它是不计入sizeof的计算结果的。因此,iptables的所有规则实际上是存储在struct ipt_entry entries[0]柔性数组中的,这里所有规则所占大小已经得到:new_size

  因为,每条规则entry都一个计数器,用来记录该规则处理了多少数据包,注意结构体STRUCT_COUNTERS_INFO{}的末尾也有一个柔性数组struct xt_counters counters[0];其中struct xt_counters{}才是真正的用于统计数据包的计数器。

然后开始初始化repl结构:

strcpy(repl->name, (*handle)->info.name);

repl->num_entries = new_number;

repl->size = new_size;

 

repl->num_counters = (*handle)->info.num_entries;

repl->valid_hooks = (*handle)->info.valid_hooks;

        紧接着对repl结构体中剩下的成员进行初始化,hook_entry[]underflow[]等。对于用户自定义链,其末尾的target.verdict=RETURN

setsockopt(sockfd, TC_IPPROTO, SO_SET_REPLACE, repl,sizeof(*repl) + repl->size);

会触发内核去执行前面我们看到的do_ipt_set_ctl()函数,如下:

static struct nf_sockopt_ops ipt_sockopts = {

         .pf              = PF_INET,

         .set_optmin     = IPT_BASE_CTL,    

  .set_optmax    = IPT_SO_SET_MAX+1,

         .set           = do_ipt_set_ctl,

         .get_optmin     = IPT_BASE_CTL,

         .get_optmax    = IPT_SO_GET_MAX+1,

         .get           = do_ipt_get_ctl,

};

        do_ipt_set_ctl()中其核心还是执行do_replace()函数:

static int do_replace(void __user *user, unsigned int len)

{

         int ret;

         struct ipt_replace tmp;

         struct xt_table_info *newinfo;

         void *loc_cpu_entry;

 

         if (copy_from_user(&tmp, user, sizeof(tmp)) != 0)

                   return -EFAULT;

 

         /* Hack: Causes ipchains to give correct error msg --RR */

         if (len != sizeof(tmp) + tmp.size)

                   return -ENOPROTOOPT;

… …

}

       其中copy_from_user()负责将用户空间的repl变量中的内容拷贝到内核中的tmp中去。然后设置规则计数器newcounters,通过setsockopt系统调用将newcounters设置到内核:

setsockopt(sockfd, TC_IPPROTO, SO_SET_ADD_COUNTERS, newcounters, counterlen);

 此时,在do_ipt_set_ctl()中执行的是do_add_counters()函数。至此,iptables用户空间的所有代码流程就算分析完了。命令:

iptables –A INPUT –i eth0 –p tcp --syn –s 10.0.0.0/8 –d 10.1.28.184 –j ACCEPT

即被设置到内核的Netfilter规则中去了。

未完,待续

 

转载于:https://www.cnblogs.com/masterpanda/p/5700491.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/254841.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

恢复Ext3下被删除的文件(转)

前言 下面是这个教程将教你如何在Ext3的文件系统中恢复被rm掉的文件。 删除文件 假设我们有一个文件名叫 ‘test.txt’ $ls -il test.txt15 -rw-rw-r– 2 root root 20 Apr 17 12:08 test.txt 注意:: “-il” 选项表示显示文件的i-node号(15)…

TCP UDP HTTP 的关系和区别

TCP UDP HTTP 三者的关系: TCP/IP是个协议组,可分为四个层次:网络接口层、网络层、传输层和应用层。 在网络层有IP协议、ICMP协议、ARP协议、RARP协议和BOOTP协议。 在传输层中有TCP协议与UDP协议。 在应用层有HTTP、FTP、TELNET、SMTP、DNS等协议。 TCP…

微信开放平台全网发布时,检测失败 —— C#

主要就是三个:返回API文本消息,返回普通文本消息,发送事件消息 --会出现失败的情况 (后续补充说明:出现检测出错,不一定是代码出现了问题,也有可能是1.微信方面检测时出现服务器请求失败&…

Zabbix 钉钉报警

话不多说,咱们直接进入正题钉钉报警时基于zabbix,访问钉钉应用接口去推送的报警消息,所以我们需要一个在钉钉创建一个报警应用1、 我做的钉钉报警是基于钉钉自定义应用进行推送的所以需要登录钉钉管理后台进行创建(zabbix自定义应…

于敦德:途牛五大战略纵深不惧同质化竞争

于敦德说,途牛已经在目的地、出发地、产品系列、客户和品牌五个领域建立起了纵深壁垒,不担心任何局部竞争,将坚决把局部同质化战争打到底。 一个行业的两种公司 包括旅游在内的很多行业通常都有两种公司:…

自定义线程的方式

2019独角兽企业重金招聘Python工程师标准>>> package com.javaxxz.test;public class Demo extends Thread {/*** 创建线程的方式* 方式一:* 1、自定义一个类继承Thread类* 2、重写Thread类的run方法,把自定线程的任务代码写在run方法中* …

20155204 2016-2017-2 《Java程序设计》第8周学习总结

学号 2016-2017-2 《Java程序设计》第X周学习总结 教材学习内容总结 想要取得channel的操作对象,可以使用channels类,它定义了静态方法newChannel()。Buffer的直接子类们都有一个alloocate()方法,可以让你指定Buffer容量。1.java.util.loggin…

HALCON示例程序train_characters_ocr.hdev使用SVM分类器训练字体

HALCON示例程序train_characters_ocr.hdev使用SVM分类器训练字体 小哥哥小姐姐觉得有用点个赞呗! 示例程序源码(加注释) 蓝色字体均为算子解释链接,可以前往查看解答 关于显示类函数解释 read_image (Image, ‘ocr/chars_tra…

安装DirectX SDK时出现Error Code:s1023 的解决方案

安装DXSDK_Jun10时(下载地址:http://www.microsoft.com/en-us/download/confirmation.aspx?id6812 ) 出现下图所示错误 Error Code:s1023 计算机上有安装过更新版的Microsoft Visual C 2010 Redistributable,打开“…

顶级数据库行会Percona阿里全面解析下一代云数据库技术

摘要: 几年前,数据库管理系统的企业市场似乎还如同铜墙铁壁,除了老牌厂商外,其他厂商休想打进来。随着移动互联、物联网技术的发展,多终端应用的时代悄然而至。结构化与非结构化数据的爆发,推动人类社会进入…

C#指定窗口显示位置的方法

小哥哥小姐姐觉得有用点个赞呗! C#指定窗口显示位置的方法 1.使用StartPosition MainForm mainform; mainformnew MainForm (); dlgCtrl.StartPosition FormStartPosition.Manual;下面是FormStartPosition里边的定义与解释 // 指定窗体的初始位置。public …

C# 修改项目文件夹名称完全版

目录步骤1、打开项目,修改文件名称2、更改命名空间名称3、在解决方案中用txt1000替换所有test5004、使用记事本打开项目文件(.sln文件)修改路径5、更改项目文件夹名称6、删除之前的残留文件7、大功告成!!!&…

js中遍历注册事件时索引怎么获取

注意:这种写法,是有问题的。注册事件是在页面加载完毕以后就完成了,但此时并没有触发事件。事件触发是由用户在页面上点击时才会触发,所以说当用户点击时,才会执行事件处理函数,那么此时的i已经变成了4&…

C#DotNetBar TabControl将水平标签设置成竖直

小哥哥小姐姐觉得有用点个赞呗! 首先选中整个TabControl控件 更改属性: 完成

使用 Drone 构建 Coding 项目

2019独角兽企业重金招聘Python工程师标准>>> 使用 Drone 构建 Coding 项目 Drone 是一个轻量级的持续集成工具。它具备许多现代持续集成工具的特性:轻巧(Docker 镜像不到 10M)、部署方便(docker-compose 一键部署&…

Visual Studio Code 常用插件整理

常用插件说明: 一、HTML Snippets 超级使用且初级的H5代码片段以及提示 二、HTML CSS Support 让HTML标签上写class智能提示当前项目所支持的样式 三、Debugger for Chrome 让vscode映射chrome的debug功能,静态页面都可以用vscode来打断点调试、配饰稍…

川崎机器人c#通讯(转)

由于本人在工业自动化行业做机器视觉的工作,所以除了图像处理方面要掌握外,还需要与工业机器人进行通信。最近学习了计算机与川崎机器人的TCP/IP通信,于是在这里记录一下。 除了直接与机器人通信外,有一种方式是通过PLC间接通信&a…

模板类 Template Classes 以及模板类编译时的处理

我们可以建立template classes,使它们能够神奇地操作任何类型的资料。下面这个例子是让CThree 类别储存三个成员变量,成员函数Min 传回其中的最小值,成员函数Max 则传回其中的最大值。我们把它设计为template class&…

行转列及列转行查询

开发过程中常遇到行转列或是列转行的问题,即需要将数据库中一张表信息进行行转列操作,再将每列(即每个字段)作为与其他表进行联表查询的字段进行显示。 一、行转列:将原来同一列下多行的不同内容作为多个字段&#xff…

一、Java语言基础(4)_方法和数组——数组

2018-04-25 不悔梦归处,只恨未尽心 数组 一、一维数组 数组的含义:具有相同类型的多个变量按有序形式组织起来的数据形式。(数组是用来存储固定大小的同类型元素。)数组的定义:方式1(推荐使用)&…