찾다
백엔드 개발PHP 튜토리얼二层(链路层)数据包发送过程分析_PHP教程

二层(链路层)数据包发送过程分析

二层(链路层)数据包发送过程分析

——lvyilong316

说明:本系列博文所涉及内核版本为2.6.32
当上层准备好一个包之后,交给链路层,链路层数据包发送主要通过dev_queue_xmit函数处理。数据包的发送可分为两种,一种是正常的传输流程,即通过网卡驱动,另一种是通过软中断(见注3)。为了理解方便,首先看一下dev_queue_xmi函数的整体调用关系图。

ldev_queue_xmit

本函数用来将带发送的skb加入一个dev的队列(Queue),调用这个函数前必须设置好skb的device和priority,本函数可以在中断上下文中被调用。

返回值:

返回非0(正数或负数)表示函数出错,返回0表示成功,但是并不表示数据包被成功发送出去,因为数据包可能因为限速等原因被丢掉。

函数执行后传入的skb将被释放,所以如果想控制数据包,实现对skb的重传时需要增加skb的引用计数。

当调用此函数时中断必须是打开的,因为BHenable必须要求IRQenable,否则会造成死锁。

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>int dev_queue_xmit(struct sk_buff *skb)<br /> </li><li>{<br /></li><li>struct net_device *dev = skb->dev;<br /></li><li>struct netdev_queue *txq;<br /></li><li>struct Qdisc *q;<br /></li><li>int rc = -ENOMEM;<br /></li><li>/* GSO will handle the following emulations directly. */<br /></li><li>if (netif_needs_gso(dev, skb))<br /></li><li>goto gso;<br /></li><li>if (skb_has_frags(skb) &&<br /></li><li>!(dev->features & NETIF_F_FRAGLIST) &&<br /></li><li>__skb_linearize(skb))<br /></li><li>goto out_kfree_skb;<br /></li><li>//如果skb有分片但是发送设备不支持分片,或分片中有分片在高端内存但发送设备不支持DMA,需要将所有段重新组合成一个段 ,这里__skb_linearize其实就是__pskb_pull_tail(skb, skb->data_len),这个函数基本上等同于pskb_may_pull  ,pskb_may_pull的作用就是检测skb对应的主buf中是否有足够的空间来pull出len长度,如果不够就重新分配skb并将frags中的数据拷贝入新分配的主buff中,而这里将参数len设置为skb->datalen, 也就是会将所有的数据全部拷贝到主buff中,以这种方式完成skb的线性化。<br /></li><li>if (skb_shinfo(skb)->nr_frags &&<br /></li><li>(!(dev->features & NETIF_F_SG) || illegal_highdma(dev, skb)) &&<br /></li><li>__skb_linearize(skb))<br /></li><li>goto out_kfree_skb;<br /></li><li>//如果数据包没有被计算校验和并且发送设备不支持这个协议的校验,则在此进行校验和的计算(注1)。如果上面已经线性化了一次,这里的__skb_linearize就会直接返回,注意区别frags和frag_list,前者是将多的数据放到单独分配的页面中,sk_buff只有一个。而后者则是连接多个sk_buff <br /></li><li>if (skb->ip_summed == CHECKSUM_PARTIAL) {<br /></li><li>skb_set_transport_header(skb, skb->csum_start -<br /></li><li>skb_headroom(skb));<br /></li><li>if (!dev_can_checksum(dev, skb) && skb_checksum_help(skb))<br /></li><li>goto out_kfree_skb;<br /></li><li>}<br /></li><li>gso:<br /></li><li>//关闭软中断,禁止cpu抢占<br /></li><li>rcu_read_lock_bh();<br /></li><li>//选择一个发送队列,如果设备提供了select_queue回调函数就使用它,否则由内核选择一个队列,这里只是Linux内核多队列的实现,但是要真正的使用都队列,需要网卡支持多队列才可以,一般的网卡都只有一个队列。在调用alloc_etherdev分配net_device是,设置队列的个数<br /></li><li>txq = dev_pick_tx(dev, skb);<br /></li><li>// 从netdev_queue结构上获取设备的qdisc <br /></li><li>q = rcu_dereference(txq->qdisc);<br /></li><li>//如果该设备有队列可用,就调用__dev_xmit_skb <br /></li><li>if (q->enqueue) {<br /></li><li>rc = __dev_xmit_skb(skb, q, dev, txq);<br /></li><li>goto out;<br /></li><li>}<br /></li><li>//下面的处理是在没有发送队列的情况,软设备一般没有发送队列:如lo、tunnle;我们所要做的就是直接调用驱动的hard_start_xmit将它发送出去  如果发送失败就直接丢弃,因为没有队列可以保存它 <br /></li><li>if (dev->flags & IFF_UP) { //确定设备是否开启<br /></li><li>int cpu = smp_processor_id(); /* ok because BHs are off */<br /></li><li>if (txq->xmit_lock_owner != cpu) {//是否在同一个cpu上<br /></li><li>HARD_TX_LOCK(dev, txq, cpu);<br /></li><li>if (!netif_tx_queue_stopped(txq)) {//确定队列是运行状态<br /></li><li>rc = NET_XMIT_SUCCESS;<br /></li><li>if (!dev_hard_start_xmit(skb, dev, txq)) {<br /></li><li>HARD_TX_UNLOCK(dev, txq);<br /></li><li>goto out;<br /></li><li>}<br /></li><li>}<br /></li><li>HARD_TX_UNLOCK(dev, txq);<br /></li><li>if (net_ratelimit())<br /></li><li>printk(KERN_CRIT "Virtual device %s asks to "<br /></li><li>"queue packet!\n", dev->name);<br /></li><li>} else {// txq->xmit_lock_owner == cpu的情况,说明发生递归<br /></li><li>if (net_ratelimit())<br /></li><li>printk(KERN_CRIT "Dead loop on virtual device "<br /></li><li>"%s, fix it urgently!\n", dev->name);<br /></li><li>}<br /></li><li>}<br /></li><li>rc = -ENETDOWN;<br /></li><li>rcu_read_unlock_bh();<br /></li><li>out_kfree_skb:<br /></li><li>kfree_skb(skb);<br /></li><li>return rc;<br /></li><li>out:<br /></li><li>rcu_read_unlock_bh();<br /></li><li>return rc;<br /></li><li>}</li></ol>

l__dev_xmit_skb

__dev_xmit_skb函数主要做两件事情:

(1)如果流控对象为空的,试图直接发送数据包。

(2)如果流控对象不空,将数据包加入流控对象,并运行流控对象。

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,<br /> </li><li>struct net_device *dev,<br /></li><li>struct netdev_queue *txq)<br /></li><li>{<br /></li><li>spinlock_t *root_lock = qdisc_lock(q);//见注2<br /></li><li>int rc;<br /></li><li>spin_lock(root_lock); //锁qdisc<br /></li><li>if (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, &q->state))) {//判断队列是否失效<br /></li><li>kfree_skb(skb);<br /></li><li>rc = NET_XMIT_DROP;<br /></li><li>} else if ((q->flags & TCQ_F_CAN_BYPASS) && !qdisc_qlen(q) &&<br /></li><li>!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state)) {<br /></li><li>/*<br /></li><li>* This is a work-conserving queue; there are no old skbs<br /></li><li>* waiting to be sent out; and the qdisc is not running -<br /></li><li>* xmit the skb directly.<br /></li><li>*/<br /></li><li>__qdisc_update_bstats(q, skb->len);<br /></li><li>if (sch_direct_xmit(skb, q, dev, txq, root_lock))<br /></li><li>__qdisc_run(q);<br /></li><li>else<br /></li><li>clear_bit(__QDISC_STATE_RUNNING, &q->state);<br /></li><li>rc = NET_XMIT_SUCCESS;<br /></li><li>} else {<br /></li><li>rc = qdisc_enqueue_root(skb, q);<br /></li><li>qdisc_run(q);<br /></li><li>}<br /></li><li>spin_unlock(root_lock);<br /></li><li>return rc;<br /></li><li>}</li></ol>

lqdisc_run

有两个时机将会调用qdisc_run():

1.__dev_xmit_skb()

2.软中断服务线程NET_TX_SOFTIRQ

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static inline void qdisc_run(struct Qdisc *q)<br /> </li><li>{<br /></li><li>if (!test_and_set_bit(__QDISC_STATE_RUNNING, &q->state))//将队列设置为运行状态<br /></li><li>__qdisc_run(q);<br /></li><li>}</li></ol>

l__qdisc_run

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>void __qdisc_run(struct Qdisc *q)<br /> </li><li>{<br /></li><li>unsigned long start_time = jiffies;<br /></li><li>while (qdisc_restart(q)) { //返回值大于0,说明流控对象非空<br /></li><li>/*如果发现本队列运行的时间太长了,将会停止队列的运行,并将队列加入output_queue链表头<br /></li><li>* Postpone processing if (延迟处理)<br /></li><li>* 1. another process needs the CPU;<br /></li><li>* 2. we've been doing it for too long.<br /></li><li>*/<br /></li><li>if (need_resched() || jiffies != start_time) { //已经不允许继续运行本流控对象<br /></li><li>__netif_schedule(q); //将本qdisc加入每cpu变量softnet_data的output_queue链表中<br /></li><li>break;<br /></li><li>}<br /></li><li>}<br /></li><li>//清除队列的运行标识<br /></li><li>clear_bit(__QDISC_STATE_RUNNING, &q->state);<br /></li><li>}</li></ol>

循环调用qdisc_restart发送数据,下面这个函数qdisc_restart是真正发送数据包的函数,它从队列上取下一个帧,然后尝试将它发送出去,若发送失败则一般是重新入队。

此函数返回值为:发送成功时返回剩余队列长度,发送失败时返回0(若发送成功且剩余队列长度为0也返回0)

lqdisc_restart

__QDISC_STATE_RUNNING状态保证同一时刻只有一个cpu在处理这个qdisc,qdisc_lock(q)用来保证对这个队列的顺序访问。

通常netif_tx_lock用来确保本设备驱动的顺序(独占)访问的,qdisc_lock(q)用来保证qdisc的顺序访问,这两个是互斥的,获得其中一个必须释放另一个。

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static inline int qdisc_restart(struct Qdisc *q)<br /> </li><li>{<br /></li><li>struct netdev_queue *txq;<br /></li><li>struct net_device *dev;<br /></li><li>spinlock_t *root_lock;<br /></li><li>struct sk_buff *skb;<br /></li><li>/* Dequeue packet */<br /></li><li>skb = dequeue_skb(q); //一开始就调用dequeue函数<br /></li><li>if (unlikely(!skb))<br /></li><li>return 0; //返回0说明队列是空的或者被限制<br /></li><li>root_lock = qdisc_lock(q);<br /></li><li>dev = qdisc_dev(q);<br /></li><li>txq = netdev_get_tx_queue(dev, skb_get_queue_mapping(skb));<br /></li><li>return sch_direct_xmit(skb, q, dev, txq, root_lock); //用于发送数据包<br /></li><li>}</li></ol>

lsch_direct_xmit

发送一个skb,将队列置为__QDISC_STATE_RUNNING状态,保证只有一个cpu运行这个函数,返回0表示队列为空或者发送受限,大于0表示队列非空。

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,<br /> </li><li>struct net_device *dev, struct netdev_queue *txq,<br /></li><li>spinlock_t *root_lock)<br /></li><li>{<br /></li><li>int ret = NETDEV_TX_BUSY;<br /></li><li>spin_unlock(root_lock);// release qdisc,因为后面要获取设备锁<br /></li><li>// 调用__netif_tx_lockà spin_lock(&txq->_xmit_lock,,保证设备驱动的独占访问<br /></li><li>HARD_TX_LOCK(dev, txq, smp_processor_id());<br /></li><li>if (!netif_tx_queue_stopped(txq) && //设备没有被停止,且发送队列没有被冻结<br /></li><li>!netif_tx_queue_frozen(txq))<br /></li><li>ret = dev_hard_start_xmit(skb, dev, txq); //发送数据包<br /></li><li>HARD_TX_UNLOCK(dev, txq); // 调用__netif_tx_unlock<br /></li><li>spin_lock(root_lock);<br /></li><li>switch (ret) {<br /></li><li>case NETDEV_TX_OK: //如果设备成功将数据包发送出去<br /></li><li>ret = qdisc_qlen(q); //返回剩余的队列长度<br /></li><li>break;<br /></li><li>case NETDEV_TX_LOCKED: //获取设备锁失败<br /></li><li>ret = handle_dev_cpu_collision(skb, txq, q);<br /></li><li>break;<br /></li><li>default: //设备繁忙,重新入队发送(利用softirq)<br /></li><li>if (unlikely (ret != NETDEV_TX_BUSY && net_ratelimit()))<br /></li><li>printk(KERN_WARNING "BUG %s code %d qlen %d\n",<br /></li><li>dev->name, ret, q->q.qlen);<br /></li><li>ret = dev_requeue_skb(skb, q);<br /></li><li>break;<br /></li><li>}<br /></li><li>if (ret && (netif_tx_queue_stopped(txq) ||<br /></li><li>netif_tx_queue_frozen(txq)))<br /></li><li>ret = 0;<br /></li><li>return ret;<br /></li><li>}</li></ol>

ldev_hard_start_xmit

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev,<br /> </li><li>struct netdev_queue *txq)<br /></li><li>{<br /></li><li>const struct net_device_ops *ops = dev->netdev_ops;<br /></li><li>int rc;<br /></li><li>if (likely(!skb->next)) {<br /></li><li>//从这里可以看出,对于每一个发送的包也会发给ptype_all一份,  而packet套接字创建时对于proto为ETH_P_ALL的会在ptype_all中注册一个成员,因此对于协议号为ETH_P_ALL的packet套接字来说,发送和接受的数据都能收到<br /></li><li>if (!list_empty(&ptype_all))<br /></li><li>dev_queue_xmit_nit(skb, dev);<br /></li><li>if (netif_needs_gso(dev, skb)) {<br /></li><li>if (unlikely(dev_gso_segment(skb)))<br /></li><li>goto out_kfree_skb;<br /></li><li>if (skb->next)<br /></li><li>goto gso;<br /></li><li>}<br /></li><li>//如果发送设备不需要skb->dst,则在此将其释放<br /></li><li>if (dev->priv_flags & IFF_XMIT_DST_RELEASE)<br /></li><li>skb_dst_drop(skb);<br /></li><li>//调用设备注册的发送函数,即dev->netdev_ops-> ndo_start_xmit(skb, dev)<br /></li><li>rc = ops->ndo_start_xmit(skb, dev);<br /></li><li>if (rc == NETDEV_TX_OK)<br /></li><li>txq_trans_update(txq);<br /></li><li>return rc;<br /></li><li>}<br /></li><li>gso:<br /></li><li>……<br /></li><li>}</li></ol>

ldev_queue_xmit_nit

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static void dev_queue_xmit_nit(struct sk_buff *skb, struct net_device *dev)<br /> </li><li>{<br /></li><li>struct packet_type *ptype;<br /></li><li>#ifdef CONFIG_NET_CLS_ACT<br /></li><li>if (!(skb->tstamp.tv64 && (G_TC_FROM(skb->tc_verd) & AT_INGRESS)))<br /></li><li>net_timestamp(skb); //记录该数据包输入的时间戳<br /></li><li>#else<br /></li><li>net_timestamp(skb);<br /></li><li>#endif<br /></li><li>rcu_read_lock();<br /></li><li>list_for_each_entry_rcu(ptype, &ptype_all, list) {<br /></li><li>/* Never send packets back to the socket they originated from */<br /></li><li>//遍历ptype_all链表,查找所有符合输入条件的原始套接口,并循环将数据包输入到满足条件的套接口<br /></li><li>if ((ptype->dev == dev || !ptype->dev) &&<br /></li><li>(ptype->af_packet_priv == NULL ||<br /></li><li>(struct sock *)ptype->af_packet_priv != skb->sk)) {<br /></li><li>//由于该数据包是额外输入到这个原始套接口的,因此需要克隆一个数据包<br /></li><li>struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);<br /></li><li>if (!skb2)<br /></li><li>break;<br /></li><li>/* skb->nh should be correctly(确保头部偏移正确)<br /></li><li>set by sender, so that the second statement is<br /></li><li>just protection against buggy protocols.<br /></li><li>*/<br /></li><li>skb_reset_mac_header(skb2);<br /></li><li>if (skb_network_header(skb2) < skb2->data ||<br /></li><li>skb2->network_header > skb2->tail) {<br /></li><li>if (net_ratelimit())//net_ratelimit用来保证网络代码中printk的频率<br /></li><li>printk(KERN_CRIT "protocol %04x is "<br /></li><li>"buggy, dev %s\n",<br /></li><li>skb2->protocol, dev->name);<br /></li><li>skb_reset_network_header(skb2); //重新设置L3头部偏移<br /></li><li>}<br /></li><li>skb2->transport_header = skb2->network_header;<br /></li><li>skb2->pkt_type = PACKET_OUTGOING;<br /></li><li>ptype->func(skb2, skb->dev, ptype, skb->dev);//调用协议(ptype_all)接受函数<br /></li><li>}<br /></li><li>}<br /></li><li>rcu_read_unlock();<br /></li><li>}</li></ol>

?环回设备

对于环回设备loopback,设备的ops->ndo_start_xmit被初始化为loopback_xmit函数。

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static const struct net_device_ops loopback_ops = {<br /> </li><li>.ndo_init = loopback_dev_init,<br /></li><li>.ndo_start_xmit= loopback_xmit,<br /></li><li>.ndo_get_stats = loopback_get_stats,<br /></li><li>};</li></ol>

drivers/net/loopback.c

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static netdev_tx_t loopback_xmit(struct sk_buff *skb,<br /> </li><li>struct net_device *dev)<br /></li><li>{<br /></li><li>struct pcpu_lstats *pcpu_lstats, *lb_stats;<br /></li><li>int len;<br /></li><li>skb_orphan(skb);<br /></li><li>skb->protocol = eth_type_trans(skb, dev);<br /></li><li>/* it's OK to use per_cpu_ptr() because BHs are off */<br /></li><li>pcpu_lstats = dev->ml_priv;<br /></li><li>lb_stats = per_cpu_ptr(pcpu_lstats, smp_processor_id());<br /></li><li>len = skb->len;<br /></li><li>if (likely(netif_rx(skb) == NET_RX_SUCCESS)) { //直接调用了netif_rx进行了接收处理<br /></li><li>lb_stats->bytes += len;<br /></li><li>lb_stats->packets++;<br /></li><li>} else<br /></li><li>lb_stats->drops++;<br /></li><li>return NETDEV_TX_OK;<br /></li><li>}</li></ol>


  • 注:
1.CHECKSUM_PARTIAL表示使用硬件checksum,L4层的伪头的校验已经完毕,并且已经加入uh->check字段中,此时只需要设备计算整个头4层头的校验值。


2.整个数据包发送逻辑中会涉及到三个用于互斥访问的代码:

(1)spinlock_t*root_lock=qdisc_lock(q);

(2)test_and_set_bit(__QDISC_STATE_RUNNING,&q->state)

(3)__netif_tx_lockàspin_lock(&txq->_xmit_lock)

其中(1)(3)分别对应一个spinlock,(2)对应一个队列状态。在了解代码中如何使用这三个同步方法时,首先看一下相关数据结构的关系,如下。

图中绿色部分表示(1)(3)两处spinlock。首先看(1)处对应的代码:

<ol style="margin:0 1px 0 0px;padding-left:40px;" start="1" class="dp-css"><li>static inline spinlock_t *qdisc_lock(struct Qdisc *qdisc)<br /> </li><li>{<br /></li><li>return &qdisc->q.lock;<br /></li><li>}</li></ol>

所以root_lock是用于控制qdisc中skb队列访问的锁,当需要对skb队列进行enqueue、dequeue、requeue时,就需要加锁。

__QDISC_STATE_RUNNING标志用于保证一个流控对象(qdisc)不会同时被多个cpu访问。

而(3)处的spinlock,即structnetdev_queue中的_xmit_lock,则用于保证dev的注册函数的互斥访问,即deriver的同步。

另外,内核代码注释中写到,(1)和(3)是互斥的,获得(1)处的锁时必须先保证释放(3)处的锁,反之亦然,为什么要这样还没有想明白。。。。哪位大神知道还望指点

3.已经有了dev_queue_xmit函数,为什么还需要软中断来发送呢?

我们可以看到在dev_queue_xmit中将skb进行了一些处理(比如合并成一个包,计算校验和等),处理完的skb是可以直接发送的了,这时dev_queue_xmit也会先将skb入队(skb一般都是在这个函数中入队的),并且调用qdisc_run尝试发送,但是有可能发送失败,这时就将skb重新入队,调度软中断,并且自己直接返回。

软中断只是发送队列中的skb以及释放已经发送的skb,它无需再对skb进行线性化或者校验和处理。另外在队列被停止的情况下,dev_queue_xmit仍然可以把包加入队列,但是不能发送,这样在队列被唤醒的时候就需要通过软中断来发送停止期间积压的包。简而言之,dev_queue_xmit是对skb做些最后的处理并且第一次尝试发送,软中断是将前者发送失败或者没发完的包发送出去。(其实发送软中断还有一个作用,就是释放已经发送的包,因为某些情况下发送是在硬件中断中完成的,为了提高硬件中断处理效率,内核提供一种方式将释放skb放到软中断中进行,这时只要调用dev_kfree_skb_irq,它将skb加入softnet_data的completion_queue中,然后开启发送软中断,net_tx_action会在软中断中将completion_queue中的skb全部释放掉)

www.bkjia.comtruehttp://www.bkjia.com/PHPjc/1103186.htmlTechArticle二层(链路层)数据包发送过程分析 二层(链路层)数据包发送过程分析 ——lvyilong316 说明:本系列博文所涉及内核版本为2.6.32 当上层准...
성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
PHP는 사용자 세션을 어떻게 식별합니까?PHP는 사용자 세션을 어떻게 식별합니까?May 01, 2025 am 12:23 AM

phpidifiesauser의 sssessionusessessioncookiesandssessionids.1) whensession_start () iscalled, phpgeneratesauniquessessionStoredInacookienamedPhpsSessIdonSeuser 'sbrowser.2) thisidallowsphptoretrievessessionDataTromServer.

PHP 세션을 확보하기위한 모범 사례는 무엇입니까?PHP 세션을 확보하기위한 모범 사례는 무엇입니까?May 01, 2025 am 12:22 AM

PHP 세션의 보안은 다음 측정을 통해 달성 할 수 있습니다. 1. Session_REGENEREAT_ID ()를 사용하여 사용자가 로그인하거나 중요한 작업 일 때 세션 ID를 재생합니다. 2. HTTPS 프로토콜을 통해 전송 세션 ID를 암호화합니다. 3. 세션 _save_path ()를 사용하여 세션 데이터를 저장하고 권한을 올바르게 설정할 보안 디렉토리를 지정하십시오.

PHP 세션 파일은 기본적으로 어디에 저장됩니까?PHP 세션 파일은 기본적으로 어디에 저장됩니까?May 01, 2025 am 12:15 AM

phpsessionfilesarestoredInTheRectorySpecifiedBysession.save_path, 일반적으로/tmponunix-likesystemsorc : \ windows \ temponwindows.tocustomizethis : 1) austession_save_path () toSetacustomDirectory, verlyTeCustory-swritation;

PHP 세션에서 데이터를 어떻게 검색합니까?PHP 세션에서 데이터를 어떻게 검색합니까?May 01, 2025 am 12:11 AM

toretrievedatafromAphPsession, startSessionstart_start () andaccessvariblesinthe $ _sessionArray.forexample : 1) startthessession : session_start (). 2) retrievedata : $ _ session [ 'username']; echo "Welcome,". $ username;

세션을 사용하여 쇼핑 카트를 구현할 수있는 방법은 무엇입니까?세션을 사용하여 쇼핑 카트를 구현할 수있는 방법은 무엇입니까?May 01, 2025 am 12:10 AM

세션을 사용하여 효율적인 쇼핑 카트 시스템을 구축하는 단계에는 다음이 포함됩니다. 1) 세션의 정의와 기능을 이해합니다. 세션은 요청에 따라 사용자 상태를 유지하는 데 사용되는 서버 측 스토리지 메커니즘입니다. 2) 쇼핑 카트에 제품 추가와 같은 기본 세션 관리를 구현합니다. 3) 제품 수량 관리 및 삭제 지원 고급 사용으로 확장; 4) 세션 데이터를 지속하고 보안 세션 식별자를 사용하여 성능 및 보안을 최적화합니다.

PHP에서 인터페이스를 어떻게 생성하고 사용합니까?PHP에서 인터페이스를 어떻게 생성하고 사용합니까?Apr 30, 2025 pm 03:40 PM

이 기사는 PHP의 인터페이스를 생성, 구현 및 사용하는 방법을 설명하여 코드 구성 및 유지 관리에 대한 이점에 중점을 둡니다.

crypt ()와 password_hash ()의 차이점은 무엇입니까?crypt ()와 password_hash ()의 차이점은 무엇입니까?Apr 30, 2025 pm 03:39 PM

이 기사에서는 PHP의 암호 해싱에 대한 Crypt ()와 Password_hash ()의 차이점에 대해 논의하여 최신 웹 애플리케이션에 대한 구현, 보안 및 적합성에 중점을 둡니다.

PHP의 크로스 사이트 스크립팅 (XSS)을 어떻게 방지 할 수 있습니까?PHP의 크로스 사이트 스크립팅 (XSS)을 어떻게 방지 할 수 있습니까?Apr 30, 2025 pm 03:38 PM

기사는 입력 유효성 검사, 출력 인코딩 및 OWASP ESAPI 및 HTML 청정기와 같은 도구를 통해 PHP의 크로스 사이트 스크립팅 (XSS) 방지에 대해 논의합니다.

See all articles

핫 AI 도구

Undresser.AI Undress

Undresser.AI Undress

사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

Video Face Swap

Video Face Swap

완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

뜨거운 도구

VSCode Windows 64비트 다운로드

VSCode Windows 64비트 다운로드

Microsoft에서 출시한 강력한 무료 IDE 편집기

SecList

SecList

SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

DVWA

DVWA

DVWA(Damn Vulnerable Web App)는 매우 취약한 PHP/MySQL 웹 애플리케이션입니다. 주요 목표는 보안 전문가가 법적 환경에서 자신의 기술과 도구를 테스트하고, 웹 개발자가 웹 응용 프로그램 보안 프로세스를 더 잘 이해할 수 있도록 돕고, 교사/학생이 교실 환경 웹 응용 프로그램에서 가르치고 배울 수 있도록 돕는 것입니다. 보안. DVWA의 목표는 다양한 난이도의 간단하고 간단한 인터페이스를 통해 가장 일반적인 웹 취약점 중 일부를 연습하는 것입니다. 이 소프트웨어는

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

안전한 시험 브라우저

안전한 시험 브라우저

안전한 시험 브라우저는 온라인 시험을 안전하게 치르기 위한 보안 브라우저 환경입니다. 이 소프트웨어는 모든 컴퓨터를 안전한 워크스테이션으로 바꿔줍니다. 이는 모든 유틸리티에 대한 액세스를 제어하고 학생들이 승인되지 않은 리소스를 사용하는 것을 방지합니다.