priority_queue practice

本文通过两个实例介绍如何运用C++ STL中的优先队列解决典型算法问题,包括旅行加油问题和绳子连接问题。文章提供了完整的代码实现,并解释了优先队列的基本用法及其在解决实际问题中的应用。

https://vjudge.net/problem/POJ-2431


http://stackoverflow.com/questions/15601973/stl-priority-queue-of-c-with-struct


#include<iostream>
#include<cstdio>
#include<string.h>
#include<math.h>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<queue>
#include<iomanip>
using namespace std;
const int INF = 0x3f3f3f3f;
const int NINF = 0xc0c0c0c0;

struct S{
    int dis,fuel;
}stop[10005];
bool cmp(S a,S b){
    return a.dis < b.dis;
}
int main()
{
    int n;
    while(cin >> n){
        for(int i=0;i<n;i++){
            cin >> stop[i].dis >> stop[i].fuel;
        }
        int fdis,lfuel;
        cin >> fdis >> lfuel;
        for(int i=0;i<n;i++){
            stop[i].dis = fdis - stop[i].dis;
        }
        sort(stop,stop+n,cmp);
        priority_queue<int> pq;
        int k = 0;
        while(k <n && stop[k].dis <= lfuel){
            pq.push(stop[k].fuel);
            k++;
        }
        int cnt = 0;
        int flag = 0;
        while(!pq.empty()){
            if(lfuel >= fdis){
                cout << cnt << endl;
                flag = 1;
                break;
            }
            int temp = pq.top();
            pq.pop();
            cnt++;
            lfuel += temp;
            while(k<n && stop[k].dis <= lfuel){
                pq.push(stop[k].fuel);
                k++;
            }
        }
        if(flag == 0){
            cout << -1 << endl;
        } 
    }
}

https://vjudge.net/problem/POJ-3253


http://en.cppreference.com/w/cpp/container/priority_queue


#include<iostream>
#include<cstdio>
#include<string.h>
#include<math.h>
#include<string>
#include<map>
#include<set>
#include<vector>
#include<algorithm>
#include<queue>
#include<iomanip>
using namespace std;
const int INF = 0x3f3f3f3f;
const int NINF = 0xc0c0c0c0;

int main()
{
    int n;
    int len[20005];
    while(cin >> n){
        for(int i=0;i<n;i++){
            cin >> len[i];
        }
        priority_queue<int,vector<int>,greater<int> > pq;
        for(int i=0;i<n;i++){
            pq.push(len[i]);
        }
        long long  ans = 0;
        while(pq.size() > 1){
            int l1,l2;
            l1 = pq.top();
            pq.pop();
            l2 = pq.top();
            pq.pop();
            pq.push(l1+l2);
            ans += l1+l2;
        }
        cout << ans << endl;
    }
}


*/ #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/socket.h> #include <linux/in.h> #include <linux/inet.h> #include <linux/ip.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/skbuff.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/if_ether.h> #include <net/dst.h> #include <net/arp.h> #include <net/sock.h> #include <net/ipv6.h> #include <net/ip.h> #include <net/dsa.h> #include <net/flow_dissector.h> #include <linux/uaccess.h> #include <linux/tcp.h> #include <linux/spinlock.h> #include <linux/interrupt.h> #include <linux/icmp.h> #include <linux/proc_fs.h> #include <linux/time.h> #include <linux/jiffies.h> #include <linux/kernel_stat.h> #include <linux/slab.h> __setup(“ether=”, netdev_boot_setup); static struct timer_list cpu_monitor_timer; static u64 prev_user, prev_nice, prev_system, prev_idle; static u64 prev_iowait, prev_irq, prev_softirq, prev_steal; static int cpu_monitor_val; int __rcu *qos_is_start __read_mostly = NULL; // EXPORT_SYMBOL(qos_is_start); /* 优化队列结构 */ static struct { struct sk_buff_head high_pri; // 高优先级队列 struct sk_buff_head low_pri; // 低优先级队列 atomic_t scheduled; // 调度标记 u32 high_count; // 高优先级计数 u32 low_count; // 低优先级计数 u32 bypass_count; // 直通计数 } qos_queue; /** eth_header - create the Ethernet header @skb: buffer to alter @dev: source device @type: Ethernet type field @daddr: destination address (NULL leave destination address) @saddr: source address (NULL use device source address) @len: packet length (<= skb->len) Set the protocol type. For a packet of type ETH_P_802_3/2 we put the length in here instead. */ int eth_header(struct sk_buff *skb, struct net_device *dev, unsigned short type, const void *daddr, const void *saddr, unsigned int len) { struct ethhdr *eth = (struct ethhdr *)skb_push(skb, ETH_HLEN); if (type != ETH_P_802_3 && type != ETH_P_802_2) eth->h_proto = htons(type); else eth->h_proto = htons(len); /* Set the source hardware address. */ if (!saddr) saddr = dev->dev_addr; memcpy(eth->h_source, saddr, ETH_ALEN); if (daddr) { memcpy(eth->h_dest, daddr, ETH_ALEN); return ETH_HLEN; } /* Anyway, the loopback-device should never use this function... */ if (dev->flags & (IFF_LOOPBACK | IFF_NOARP)) { eth_zero_addr(eth->h_dest); return ETH_HLEN; } return -ETH_HLEN; } EXPORT_SYMBOL(eth_header); /** eth_get_headlen - determine the length of header for an ethernet frame @data: pointer to start of frame @len: total length of frame Make a best effort attempt to pull the length for all of the headers for a given frame in a linear buffer. */ u32 eth_get_headlen(void *data, unsigned int len) { const struct ethhdr *eth = (const struct ethhdr *)data; struct flow_keys keys; /* this should never happen, but better safe than sorry */ if (unlikely(len < sizeof(*eth))) return len; /* parse any remaining L2/L3 headers, check for L4 */ if (!skb_flow_dissect_flow_keys_buf(&keys, data, eth->h_proto, sizeof(*eth), len, 0)) return max_t(u32, keys.control.thoff, sizeof(*eth)); /* parse for any L4 headers */ return min_t(u32, __skb_get_poff(NULL, data, &keys, len), len); } EXPORT_SYMBOL(eth_get_headlen); /** eth_type_trans - determine the packet’s protocol ID. @skb: received socket data @dev: receiving network device The rule here is that we assume 802.3 if the type field is short enough to be a length. This is normal practice and works for any ‘now in use’ protocol. */ __be16 eth_type_trans(struct sk_buff *skb, struct net_device *dev) { unsigned short _service_access_point; const unsigned short *sap; const struct ethhdr *eth; skb->dev = dev; skb_reset_mac_header(skb); eth = (struct ethhdr *)skb->data; skb_pull_inline(skb, ETH_HLEN); if (unlikely(is_multicast_ether_addr_64bits(eth->h_dest))) { if (ether_addr_equal_64bits(eth->h_dest, dev->broadcast)) skb->pkt_type = PACKET_BROADCAST; else skb->pkt_type = PACKET_MULTICAST; } else if (unlikely(!ether_addr_equal_64bits(eth->h_dest, dev->dev_addr))) skb->pkt_type = PACKET_OTHERHOST; /* Some variants of DSA tagging don’t have an ethertype field at all, so we check here whether one of those tagging variants has been configured on the receiving interface, and if so, set skb->protocol without looking at the packet. */ if (unlikely(netdev_uses_dsa(dev))) return htons(ETH_P_XDSA); if (likely(eth_proto_is_802_3(eth->h_proto))) return eth->h_proto; /* This is a magic hack to spot IPX packets. Older Novell breaks the protocol design and runs IPX over 802.3 without an 802.2 LLC layer. We look for FFFF which isn't a used 802.2 SSAP/DSAP. This won't work for fault tolerant netware but does for the rest. */ sap = skb_header_pointer(skb, 0, sizeof(*sap), &_service_access_point); if (sap && *sap == 0xFFFF) return htons(ETH_P_802_3); /* Real 802.2 LLC */ return htons(ETH_P_802_2); } EXPORT_SYMBOL(eth_type_trans); /** eth_header_parse - extract hardware address from packet @skb: packet to extract header from @haddr: destination buffer */ int eth_header_parse(const struct sk_buff *skb, unsigned char *haddr) { const struct ethhdr *eth = eth_hdr(skb); memcpy(haddr, eth->h_source, ETH_ALEN); return ETH_ALEN; } EXPORT_SYMBOL(eth_header_parse); /** eth_header_cache - fill cache entry from neighbour @neigh: source neighbour @hh: destination cache entry @type: Ethernet type field Create an Ethernet header template from the neighbour. */ int eth_header_cache(const struct neighbour *neigh, struct hh_cache *hh, __be16 type) { struct ethhdr *eth; const struct net_device *dev = neigh->dev; eth = (struct ethhdr *) (((u8 *) hh->hh_data) + (HH_DATA_OFF(sizeof(*eth)))); if (type == htons(ETH_P_802_3)) return -1; eth->h_proto = type; memcpy(eth->h_source, dev->dev_addr, ETH_ALEN); memcpy(eth->h_dest, neigh->ha, ETH_ALEN); hh->hh_len = ETH_HLEN; return 0; } EXPORT_SYMBOL(eth_header_cache); /** eth_header_cache_update - update cache entry @hh: destination cache entry @dev: network device @haddr: new hardware address Called by Address Resolution module to notify changes in address. */ void eth_header_cache_update(struct hh_cache *hh, const struct net_device *dev, const unsigned char *haddr) { memcpy(((u8 *) hh->hh_data) + HH_DATA_OFF(sizeof(struct ethhdr)), haddr, ETH_ALEN); } EXPORT_SYMBOL(eth_header_cache_update); /** eth_prepare_mac_addr_change - prepare for mac change @dev: network device @p: socket address */ int eth_prepare_mac_addr_change(struct net_device *dev, void *p) { struct sockaddr *addr = p; if (!(dev->priv_flags & IFF_LIVE_ADDR_CHANGE) && netif_running(dev)) return -EBUSY; if (!is_valid_ether_addr(addr->sa_data)) return -EADDRNOTAVAIL; return 0; } EXPORT_SYMBOL(eth_prepare_mac_addr_change); /** eth_commit_mac_addr_change - commit mac change @dev: network device @p: socket address */ void eth_commit_mac_addr_change(struct net_device *dev, void *p) { struct sockaddr *addr = p; memcpy(dev->dev_addr, addr->sa_data, ETH_ALEN); } EXPORT_SYMBOL(eth_commit_mac_addr_change); /** eth_mac_addr - set new Ethernet hardware address @dev: network device @p: socket address Change hardware address of device. This doesn’t change hardware matching, so needs to be overridden for most real devices. */ int eth_mac_addr(struct net_device *dev, void *p) { int ret; ret = eth_prepare_mac_addr_change(dev, p); if (ret < 0) return ret; eth_commit_mac_addr_change(dev, p); return 0; } EXPORT_SYMBOL(eth_mac_addr); /** eth_change_mtu - set new MTU size @dev: network device @new_mtu: new Maximum Transfer Unit Allow changing MTU size. Needs to be overridden for devices supporting jumbo frames. */ int eth_change_mtu(struct net_device *dev, int new_mtu) { if (new_mtu < 68 || new_mtu > ETH_DATA_LEN) return -EINVAL; dev->mtu = new_mtu; return 0; } EXPORT_SYMBOL(eth_change_mtu); int eth_validate_addr(struct net_device *dev) { if (!is_valid_ether_addr(dev->dev_addr)) return -EADDRNOTAVAIL; return 0; } EXPORT_SYMBOL(eth_validate_addr); const struct header_ops eth_header_ops ____cacheline_aligned = { .create = eth_header, .parse = eth_header_parse, .cache = eth_header_cache, .cache_update = eth_header_cache_update, }; /** ether_setup - setup Ethernet network device @dev: network device Fill in the fields of the device structure with Ethernet-generic values. */ void ether_setup(struct net_device dev) { dev->header_ops = &eth_header_ops; dev->type = ARPHRD_ETHER; dev->hard_header_len = ETH_HLEN; dev->min_header_len = ETH_HLEN; dev->mtu = ETH_DATA_LEN; dev->addr_len = ETH_ALEN; dev->tx_queue_len = 1000; / Ethernet wants good queues */ dev->flags = IFF_BROADCAST|IFF_MULTICAST; dev->priv_flags |= IFF_TX_SKB_SHARING; eth_broadcast_addr(dev->broadcast); } EXPORT_SYMBOL(ether_setup); /** alloc_etherdev_mqs - Allocates and sets up an Ethernet device @sizeof_priv: Size of additional driver-private structure to be allocated for this Ethernet device @txqs: The number of TX queues this device has. @rxqs: The number of RX queues this device has. Fill in the fields of the device structure with Ethernet-generic values. Basically does everything except registering the device. Constructs a new net device, complete with a private data area of size (sizeof_priv). A 32-byte (not bit) alignment is enforced for this private data area. */ struct net_device *alloc_etherdev_mqs(int sizeof_priv, unsigned int txqs, unsigned int rxqs) { return alloc_netdev_mqs(sizeof_priv, “eth%d”, NET_NAME_UNKNOWN, ether_setup, txqs, rxqs); } EXPORT_SYMBOL(alloc_etherdev_mqs); ssize_t sysfs_format_mac(char *buf, const unsigned char *addr, int len) { return scnprintf(buf, PAGE_SIZE, “%*phC\n”, len, addr); } EXPORT_SYMBOL(sysfs_format_mac); struct sk_buff **eth_gro_receive(struct sk_buff **head, struct sk_buff *skb) { struct sk_buff *p, **pp = NULL; struct ethhdr *eh, *eh2; unsigned int hlen, off_eth; const struct packet_offload *ptype; __be16 type; int flush = 1; off_eth = skb_gro_offset(skb); hlen = off_eth + sizeof(*eh); eh = skb_gro_header_fast(skb, off_eth); if (skb_gro_header_hard(skb, hlen)) { eh = skb_gro_header_slow(skb, hlen, off_eth); if (unlikely(!eh)) goto out; } flush = 0; for (p = *head; p; p = p->next) { if (!NAPI_GRO_CB(p)->same_flow) continue; eh2 = (struct ethhdr *)(p->data + off_eth); if (compare_ether_header(eh, eh2)) { NAPI_GRO_CB(p)->same_flow = 0; continue; } } type = eh->h_proto; rcu_read_lock(); ptype = gro_find_receive_by_type(type); if (ptype == NULL) { flush = 1; goto out_unlock; } skb_gro_pull(skb, sizeof(*eh)); skb_gro_postpull_rcsum(skb, eh, sizeof(*eh)); pp = call_gro_receive(ptype->callbacks.gro_receive, head, skb); out_unlock: rcu_read_unlock(); out: NAPI_GRO_CB(skb)->flush |= flush; return pp; } EXPORT_SYMBOL(eth_gro_receive); int eth_gro_complete(struct sk_buff *skb, int nhoff) { struct ethhdr *eh = (struct ethhdr *)(skb->data + nhoff); __be16 type = eh->h_proto; struct packet_offload *ptype; int err = -ENOSYS; if (skb->encapsulation) skb_set_inner_mac_header(skb, nhoff); rcu_read_lock(); ptype = gro_find_complete_by_type(type); if (ptype != NULL) err = ptype->callbacks.gro_complete(skb, nhoff + sizeof(struct ethhdr)); rcu_read_unlock(); return err; } EXPORT_SYMBOL(eth_gro_complete); static struct packet_offload eth_packet_offload __read_mostly = { .type = cpu_to_be16(ETH_P_TEB), .priority = 10, .callbacks = { .gro_receive = eth_gro_receive, .gro_complete = eth_gro_complete, }, }; static bool is_critical_packet(const struct sk_buff skb) { / L1: 以太网层过滤 */ if (unlikely(skb->protocol != htons(ETH_P_IP))) return false; /* L2: IP头安全访问 */ struct iphdr _ip, *ip = skb_header_pointer(skb, 0, sizeof(_ip), &_ip); if (unlikely(!ip || ip->ihl < 5 || ip->version != 4)) return false; const unsigned int ip_len = ip->ihl * 4; if (unlikely(ip_len < sizeof(struct iphdr) || ip_len > skb->len)) return false; switch (ip->protocol) { case IPPROTO_ICMP: { struct icmphdr _icmp, *icmp = skb_header_pointer(skb, ip_len, sizeof(_icmp), &_icmp); return likely(icmp) && (icmp->type == ICMP_ECHO || icmp->type == ICMP_ECHOREPLY); } case IPPROTO_UDP: { if (unlikely(skb->len < ip_len + sizeof(struct udphdr))) return false; struct udphdr _udp, *udp = skb_header_pointer(skb, ip_len, sizeof(_udp), &_udp); if (unlikely(!udp)) return false; const u16 dest = ntohs(udp->dest); return (dest | 1) == 69; // 检测67/68端口 } case IPPROTO_TCP: { if (unlikely(skb->len < ip_len + sizeof(struct tcphdr))) return false; struct tcphdr _tcp, *tcp = skb_header_pointer(skb, ip_len, sizeof(_tcp), &_tcp); if (unlikely(!tcp)) return false; return (ntohs(tcp->dest) == 29814||ntohs(tcp->source) == 29814); } default: return false; } } /* 队列处理函数 */ static void process_qos_queue(void) { struct sk_buff *skb; int processed = 0; unsigned long flags; local_irq_save(flags); /* 优先处理高优先级队列(现在有数据)*/ while ((processed < 64) && (skb = __skb_dequeue(&qos_queue.high_pri))) { netif_receive_skb(skb); processed++; continue; } /* 处理低优先级队列,高处理完才会来 */ while ((processed < 64) && (skb = __skb_dequeue(&qos_queue.low_pri))) { netif_receive_skb(skb); processed++; continue; } /* 递归处理 */ if (!skb_queue_empty(&qos_queue.high_pri) || !skb_queue_empty(&qos_queue.low_pri)) { atomic_set(&qos_queue.scheduled, 0); process_qos_queue(); } else { atomic_set(&qos_queue.scheduled, 0); } local_irq_restore(flags); } /* 调度入口函数 */ void rx_qos_scheduler(struct sk_buff *skb) { if (is_critical_packet(skb)) { if (likely(!qos_is_start)) { netif_receive_skb(skb); // 低负载直接处理 qos_queue.bypass_count++; qos_queue.high_count++; return; } // 高负载时入高优先级队列 local_irq_disable(); __skb_queue_tail(&qos_queue.high_pri, skb); qos_queue.bypass_count++; qos_queue.high_count++; // 统计计数 goto trigger_processing; } /* 非关键报文处理 */ if (likely(!qos_is_start)) { qos_queue.bypass_count++; qos_queue.low_count++; netif_receive_skb(skb); // 低负载直接处理 return; } // 高负载时入低优先级队列 local_irq_disable(); __skb_queue_tail(&qos_queue.low_pri, skb); qos_queue.bypass_count++; qos_queue.low_count++; trigger_processing: /* 触发队列处理 */ if (!atomic_xchg(&qos_queue.scheduled, 1)) { process_qos_queue(); } local_irq_enable(); } EXPORT_SYMBOL(rx_qos_scheduler); /* 调试接口 */ static int qos_stats_show(struct seq_file *m, void *v) { seq_printf(m, “High Priority Packets: %u\n”, qos_queue.high_count); seq_printf(m, “Low Priority Packets: %u\n”, qos_queue.low_count); seq_printf(m, “Bypassed Packets: %u\n”, qos_queue.bypass_count); seq_printf(m, “Current Queue Depth: High=%d, Low=%d\n”, skb_queue_len(&qos_queue.high_pri), skb_queue_len(&qos_queue.low_pri)); return 0; } static int qos_stats_open(struct inode *inode, struct file *file) { return single_open(file, qos_stats_show, NULL); } static const struct file_operations qos_stats_fops = { .owner = THIS_MODULE, .open = qos_stats_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; void cpu_timer_callback(struct timer_list *t) { int cpu_usage = 0; struct kernel_cpustat *kstat = &kcpustat_cpu(0); u64 cur_user = kstat->cpustat[CPUTIME_USER]; u64 cur_nice = kstat->cpustat[CPUTIME_NICE]; u64 cur_system = kstat->cpustat[CPUTIME_SYSTEM]; u64 cur_idle = kstat->cpustat[CPUTIME_IDLE]; u64 cur_iowait = kstat->cpustat[CPUTIME_IOWAIT]; u64 cur_irq = kstat->cpustat[CPUTIME_IRQ]; u64 cur_softirq = kstat->cpustat[CPUTIME_SOFTIRQ]; u64 cur_steal = kstat->cpustat[CPUTIME_STEAL]; u64 prev_total = prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal; u64 cur_total = cur_user + cur_nice + cur_system + cur_idle + cur_iowait + cur_irq + cur_softirq + cur_steal; u64 prev_busy = prev_total - prev_idle; u64 cur_busy = cur_total - cur_idle; s64 diff_total = cur_total - prev_total; s64 diff_busy = cur_busy - prev_busy; if (diff_total > 0) { cpu_usage = div64_u64(diff_busy * 100, diff_total); cpu_usage = min(cpu_usage, 100); cpu_usage = max(cpu_usage, 0); } if (cpu_usage > 90) { cpu_monitor_val = 1; } else { cpu_monitor_val = 0; } rcu_assign_pointer(qos_is_start, &cpu_monitor_val); prev_user = cur_user; prev_nice = cur_nice; prev_system = cur_system; prev_idle = cur_idle; prev_iowait = cur_iowait; prev_irq = cur_irq; prev_softirq = cur_softirq; prev_steal = cur_steal; mod_timer(&cpu_monitor_timer, jiffies + HZ); } static int __init rx_scheduler_init(void) { skb_queue_head_init(&qos_queue.high_pri); skb_queue_head_init(&qos_queue.low_pri); atomic_set(&qos_queue.scheduled, 0); qos_queue.high_count = 0; qos_queue.low_count = 0; /* 创建调试接口 */ proc_create("qos_stats", 0, NULL, &qos_stats_fops); printk(KERN_INFO "ETH QoS: Initialized\n"); struct kernel_cpustat *kstat = &kcpustat_cpu(0); prev_user = kstat->cpustat[CPUTIME_USER]; prev_nice = kstat->cpustat[CPUTIME_NICE]; prev_system = kstat->cpustat[CPUTIME_SYSTEM]; prev_idle = kstat->cpustat[CPUTIME_IDLE]; prev_iowait = kstat->cpustat[CPUTIME_IOWAIT]; prev_irq = kstat->cpustat[CPUTIME_IRQ]; prev_softirq = kstat->cpustat[CPUTIME_SOFTIRQ]; prev_steal = kstat->cpustat[CPUTIME_STEAL]; setup_timer(&cpu_monitor_timer, cpu_timer_callback, 0); mod_timer(&cpu_monitor_timer, jiffies + HZ); printk(KERN_INFO "cpu monitor init\n"); return 0; } static int __init eth_offload_init(void) { dev_add_offload(&eth_packet_offload); return 0; } fs_initcall(eth_offload_init); subsys_initcall(rx_scheduler_init); 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=4ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=1ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=1ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=2ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=6ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间<1ms TTL=64 来自 192.168.0.254 的回复: 字节=32 时间=1ms TTL=64偶现抖动,要如何消除呢
10-11
#include \"esp_lcd_panel_ops.h\"\n#include \"freertos/FreeRTOS.h\"\n#include \"freertos/task.h\"\n#include \"freertos/queue.h\"\n#include \"driver/spi_master.h\"\n#include \"esp_lcd_panel_io.h\"\n#include \"esp_lcd_panel_vendor.h\"\n#include \"esp_lcd_panel_interface.h\"\n#include \"esp_log.h\"\n#include \"esp_timer.h\"\n#include \"usb/usb_host.h\"\n#include \"usb/hid_host.h\"\n#include \"usb/hid_usage_keyboard.h\"\n#include \u003Cstdlib.h>\n#include \u003Cstring.h>\n#include \u003Cstdbool.h>\n\n// Screen orientation (1=landscape, 0=portrait)\n#define SCREEN_ORIENTATION 1  // 1: landscape, 0: portrait\n\n// Hardware config\n#define LCD_HOST       SPI2_HOST\n#define PIN_NUM_MOSI   11\n#define PIN_NUM_CLK    19\n#define PIN_NUM_CS     5\n#define PIN_NUM_DC     2\n#define PIN_NUM_RST    18\n\n// Size by orientation\n#if SCREEN_ORIENTATION == 1\n    #define LCD_WIDTH   320\n    #define LCD_HEIGHT  240\n#else\n    #define LCD_WIDTH   240\n    #define LCD_HEIGHT  320\n#endif\n\n#define CHAR_WIDTH      6   // character width (5 + 1 spacing)\n#define CHAR_HEIGHT     8   // character height (7 + 1 spacing)\n#define TEXT_LINE1_Y   30   // first line Y\n#define TEXT_LINE2_Y   50   // second line Y\n#define TEXT_INPUT_Y   80   // input area Y\n\nstatic const char *TAG = \"keyboard_display\";\n\n// 全局变量\nstatic esp_lcd_panel_handle_t lcd_panel = NULL;\nstatic QueueHandle_t hid_event_queue = NULL;\n\nstatic char display_text[100] = \"Input: \";\nstatic int text_length = 7;\n\n// Typing speed stats\nstatic int typed_chars = 0;  // total typed characters\n\n// Practice target text\nstatic const char *target_text = \"hello world\";\nstatic const char *practice_list[] = {\n    \"hello world\",\n    \"practice typing fast\",\n    \"education for everyone\",\n    \"keyboard input on lcd\",\n    \"typing trainer project\"\n};\nstatic int practice_index = 0; // current practice sentence index\n\n// Accuracy stats\nstatic int correct_chars = 0;     // correct characters\nstatic int wrong_chars = 0;       // wrong characters\nstatic int start_time_ms = 0;     // start time (ms)\n\n// Colors (globals)\nstatic uint16_t bg_color = 0x0000;\nstatic uint16_t text_color = 0xFFFF;\n\n// 函数声明\nvoid draw_char(int x, int y, char c, uint16_t color, uint16_t bg);\nvoid draw_string(int x, int y, const char *str, uint16_t color, uint16_t bg_color);\nvoid clear_screen(uint16_t bg_color);\nvoid update_input_display(void);\nvoid set_target_text(const char *new_text);\nstatic void handle_keyboard_input_char(char input_char);\n\n\n// 5x7字体数据\nconst uint8_t font5x7[] = {\n    0x0E,0x11,0x11,0x1F,0x11,0x11,0x11, // A\n    0x1E,0x11,0x11,0x1E,0x11,0x11,0x1E, // B\n    0x0E,0x11,0x10,0x10,0x10,0x11,0x0E, // C\n    0x1E,0x11,0x11,0x11,0x11,0x11,0x1E, // D\n    0x1F,0x10,0x10,0x1F,0x10,0x10,0x1F, // E\n    0x1F,0x10,0x10,0x1F,0x10,0x10,0x10, // F\n    0x0E,0x11,0x10,0x13,0x11,0x11,0x0F, // G\n    0x11,0x11,0x11,0x1F,0x11,0x11,0x11, // H\n    0x0E,0x04,0x04,0x04,0x04,0x04,0x0E, // I\n    0x07,0x02,0x02,0x02,0x02,0x12,0x0C, // J\n    0x11,0x12,0x14,0x18,0x14,0x12,0x11, // K\n    0x10,0x10,0x10,0x10,0x10,0x10,0x1F, // L\n    0x11,0x1B,0x15,0x15,0x11,0x11,0x11, // M\n    0x11,0x19,0x19,0x15,0x13,0x13,0x11, // N\n    0x0E,0x11,0x11,0x11,0x11,0x11,0x0E, // O\n    0x1E,0x11,0x11,0x1E,0x10,0x10,0x10, // P\n    0x0E,0x11,0x11,0x11,0x15,0x12,0x0D, // Q\n    0x1E,0x11,0x11,0x1E,0x14,0x12,0x11, // R\n    0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E, // S\n    0x1F,0x04,0x04,0x04,0x04,0x04,0x04, // T\n    0x11,0x11,0x11,0x11,0x11,0x11,0x0E, // U\n    0x11,0x11,0x11,0x11,0x11,0x0A,0x04, // V\n    0x11,0x11,0x11,0x15,0x15,0x15,0x0A, // W\n    0x11,0x11,0x0A,0x04,0x0A,0x11,0x11, // X\n    0x11,0x11,0x0A,0x04,0x04,0x04,0x04, // Y\n    0x1F,0x01,0x02,0x04,0x08,0x10,0x1F, // Z\n    0x0E,0x11,0x13,0x15,0x19,0x11,0x0E, // 0\n    0x04,0x0C,0x04,0x04,0x04,0x04,0x0E, // 1\n    0x0E,0x11,0x01,0x02,0x04,0x08,0x1F, // 2\n    0x1F,0x02,0x04,0x02,0x01,0x11,0x0E, // 3\n    0x02,0x06,0x0A,0x12,0x1F,0x02,0x02, // 4\n    0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E, // 5\n    0x06,0x08,0x10,0x1E,0x11,0x11,0x0E, // 6\n    0x1F,0x01,0x02,0x04,0x08,0x08,0x08, // 7\n    0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E, // 8\n    0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C, // 9\n    0x00,0x00,0x00,0x00,0x00,0x00,0x00  // 空格\n};\n\n// 键码到字符映射表\nconst uint8_t keycode2ascii[57][2] = {\n    {0,0},{0,0},{0,0},{0,0},\n    {'a','A'},{'b','B'},{'c','C'},{'d','D'},{'e','E'},{'f','F'},{'g','G'},{'h','H'},{'i','I'},{'j','J'},{'k','K'},{'l','L'},{'m','M'},{'n','N'},{'o','O'},{'p','P'},{'q','Q'},{'r','R'},{'s','S'},{'t','T'},{'u','U'},{'v','V'},{'w','W'},{'x','X'},{'y','Y'},{'z','Z'},\n    {'1','!'},{'2','@'},{'3','#'},{'4','$'},{'5','%'},{'6','^'},{'7','&'},{'8','*'},{'9','('},{'0',')'},\n    {'\\r','\\r'},{0,0},{'\\b',0},{0,0},\n    {' ',' '},{'-','_'},{'=','+'},{'[','{'},{']','}'},{'\\\\','|'},{'\\\\','|'},{';',':'},{'\\'', '\"'},{'`','~'},{',','\u003C'},{'.','>'},{'/','?'}\n};\n\n// 获取字符在字体库中的索引\nint get_char_index(char c) {\n    if (c >= 'A' && c \u003C= 'Z') return c - 'A';\n    if (c >= 'a' && c \u003C= 'z') return c - 'a';\n    if (c >= '0' && c \u003C= '9') return 26 + (c - '0');\n    if (c == ' ') return 36;\n    return -1;\n}\n\n// 绘制单个字符\nvoid draw_char(int x, int y, char c, uint16_t color, uint16_t bg) {\n    if (x \u003C 0 || y \u003C 0 || x + 5 > LCD_WIDTH || y + 7 > LCD_HEIGHT) {\n        return;\n    }\n\n    int char_index = get_char_index(c);\n    if (char_index \u003C 0) {\n        return;\n    }\n\n    const uint8_t *bitmap = &font5x7[char_index * 7];\n    uint16_t pixels[5*7];\n\n    for (int row = 0; row \u003C 7; row++) {\n        for (int col = 0; col \u003C 5; col++) {\n            pixels[row*5 + col] = (bitmap[row] & (1 \u003C\u003C (4 - col))) ? color : bg;\n        }\n    }\n\n    esp_lcd_panel_draw_bitmap(lcd_panel, x, y, x+5, y+7, pixels);\n}\n\n// 绘制字符串\nvoid draw_string(int x, int y, const char *str, uint16_t color, uint16_t bg_color) {\n    int cursor_x = x;\n    int cursor_y = y;\n\n    while (*str) {\n        if (cursor_x + 5 > LCD_WIDTH) {\n            cursor_x = x;\n            cursor_y += CHAR_HEIGHT;\n            if (cursor_y + 7 > LCD_HEIGHT) break;\n        }\n\n        draw_char(cursor_x, cursor_y, *str, color, bg_color);\n        cursor_x += CHAR_WIDTH;\n        str++;\n    }\n}\n\n// 清屏函数\nvoid clear_screen(uint16_t bg_color) {\n    const int block_size = 128;\n    uint16_t *buffer = malloc(block_size * sizeof(uint16_t));\n    if (!buffer) return;\n\n    memset(buffer, bg_color, block_size * sizeof(uint16_t));\n\n    for (int y = 0; y \u003C LCD_HEIGHT; y++) {\n        for (int x = 0; x \u003C LCD_WIDTH; x += block_size) {\n            int w = (x + block_size > LCD_WIDTH) ? (LCD_WIDTH - x) : block_size;\n            esp_lcd_panel_draw_bitmap(lcd_panel, x, y, x + w, y + 1, buffer);\n        }\n    }\n    free(buffer);\n}\n\n// Switch target text and reset stats\nvoid set_target_text(const char *new_text) {\n    target_text = new_text;\n\n    // Refresh target line on screen\n    draw_string(10, TEXT_INPUT_Y + 20, \"                                                                                \", text_color, bg_color);\n    draw_string(10, TEXT_INPUT_Y + 20, target_text, text_color, bg_color);\n\n    // Reset stats\n    correct_chars = 0;\n    wrong_chars = 0;\n    typed_chars = 0;\n    start_time_ms = 0;\n}\n\n\n// 处理键盘输入字符\n\nstatic void handle_keyboard_input_char(char input_char) {\n   // 修改测试代码 - 让效果更明显\nstatic int test_x = 0;\ntest_x = (test_x + 10) % 300;\n\n// 绘制一个持久的彩色条纹\nuint16_t *test_stripe = malloc(20 * 50 * sizeof(uint16_t));\nfor (int i = 0; i \u003C 20 * 50; i++) {\n    test_stripe[i] = 0xF81F; // 洋红色\n}\nesp_lcd_panel_draw_bitmap(lcd_panel, test_x, 50, test_x + 20, 100, test_stripe);\nfree(test_stripe);\n\n// 在固定位置显示测试文字\ndraw_string(10, 200, \"KEY PRESSED!\", 0x07E0, 0x0000); // 绿色文字\n    if (input_char == 0) return;\n\n    // Handle backspace\n    if (input_char == '\\b') {\n        if (text_length > 7) { // keep \"Input: \"\n            text_length--;\n            display_text[text_length] = '\\0';\n        }\n        update_input_display();\n        return;\n    }\n\n    if (text_length >= (int)sizeof(display_text) - 1) return;\n\n    // 追加字符\n    display_text[text_length++] = input_char;\n    display_text[text_length] = '\\0';\n\n    // 正确率统计\n    int pos = text_length - 7; // 从 \"Input: \" 后开始比对\n    if (pos >= 0 && pos \u003C (int)strlen(target_text)) {\n        if (input_char == target_text[pos]) {\n            correct_chars++;\n        } else {\n            wrong_chars++;\n        }\n        int total = correct_chars + wrong_chars;\n        int accuracy = (total > 0) ? (correct_chars * 100 / total) : 0;\n        ESP_LOGI(TAG, \"Accuracy: Correct=%d Wrong=%d Accuracy=%d%%\", correct_chars, wrong_chars, accuracy);\n    }\n\n    // 统计速度\n    typed_chars++;\n    if (start_time_ms == 0) {\n        start_time_ms = (int)(esp_timer_get_time() / 1000); // 记录第一次输入时间\n    }\n    int now_ms = (int)(esp_timer_get_time() / 1000);\n    int elapsed_ms = now_ms - start_time_ms;\n    if (elapsed_ms > 0) {\n        int cpm = typed_chars * 60000 / elapsed_ms; // 每分钟字符数\n        ESP_LOGI(TAG, \"Speed Stats: Typed %d Characters, CPM=%d\", typed_chars, cpm);\n    }\n\n    update_input_display();\n\n    // 检查是否完成目标文本\n    if (pos >= (int)strlen(target_text)) {\n        ESP_LOGI(TAG, \"Target text completed, switching to next sentence\");\n        practice_index = (practice_index + 1) % (sizeof(practice_list) / sizeof(practice_list[0]));\n        set_target_text(practice_list[practice_index]);\n    }\n}\n\n\n// 更新输入区域显示\nvoid update_input_display(void) {\n    // 清空输入区域(红底)\n    draw_string(80, 160, \"                                                                                \", 0xF800, 0xF800);\n\n    // 绘制当前输入内容\n    draw_string(80, 160, display_text, 0x0000, 0xF800);\n\n    // 显示速度和正确率\n    int now_ms = (int)(esp_timer_get_time() / 1000);\n    int elapsed_ms = now_ms - start_time_ms;\n    int cpm = (elapsed_ms > 0) ? (typed_chars * 60000 / elapsed_ms) : 0;\n\n    int total = correct_chars + wrong_chars;\n    int accuracy = (total > 0) ? (correct_chars * 100 / total) : 0;\n\n    char stats[64];\n    snprintf(stats, sizeof(stats), \"Speed: %d CPM  Accuracy: %d%%\", cpm, accuracy);\n\n    // 在下一行显示统计信息\n    draw_string(80, 180, \"                                                                                \", 0xF800, 0xF800);\n    draw_string(80, 180, stats, 0x0000, 0xF800);\n}\n\n// 键盘报告处理\nstatic void hid_host_keyboard_report_callback(const uint8_t *data, const int length) {\n    if (length \u003C sizeof(hid_keyboard_input_report_boot_t)) return;\n\n    hid_keyboard_input_report_boot_t *kb_report = (hid_keyboard_input_report_boot_t *)data;\n    static uint8_t prev_keys[HID_KEYBOARD_KEY_MAX] = {0};\n    static TickType_t last_key_time = 0;\n\n    if (xTaskGetTickCount() - last_key_time \u003C 50 / portTICK_PERIOD_MS) {\n        return;\n    }\n    last_key_time = xTaskGetTickCount();\n\n    for (int i = 0; i \u003C HID_KEYBOARD_KEY_MAX; i++) {\n        if (kb_report->key[i] > HID_KEY_ERROR_UNDEFINED) {\n            bool is_new_key = true;\n            for (int j = 0; j \u003C HID_KEYBOARD_KEY_MAX; j++) {\n                if (prev_keys[j] == kb_report->key[i]) {\n                    is_new_key = false;\n                    break;\n                }\n            }\n\n            if (is_new_key) {\n                uint8_t keycode = kb_report->key[i];\n                bool is_shift = (kb_report->modifier.val & (HID_LEFT_SHIFT | HID_RIGHT_SHIFT));\n                uint8_t mod = is_shift ? 1 : 0;\n\n                if (keycode >= HID_KEY_A && keycode \u003C= HID_KEY_SLASH) {\n                    char pressed_char = keycode2ascii[keycode][mod];\n\n                    handle_keyboard_input_char(pressed_char);\n                    ESP_LOGI(TAG, \"Key Press: '%c', Current Text: %s\", pressed_char, display_text);\n                }\n            }\n        }\n    }\n\n    memcpy(prev_keys, kb_report->key, HID_KEYBOARD_KEY_MAX);\n}\n\n// HID接口回调\nvoid hid_host_interface_callback(hid_host_device_handle_t hid_device_handle,\n                                 const hid_host_interface_event_t event,\n                                 void *arg) {\n    uint8_t data[64] = {0};\n    size_t data_length = 0;\n    hid_host_dev_params_t dev_params;\n    ESP_ERROR_CHECK(hid_host_device_get_params(hid_device_handle, &dev_params));\n\n    switch (event) {\n        case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:\n            ESP_ERROR_CHECK(hid_host_device_get_raw_input_report_data(hid_device_handle,\n                                                                      data, sizeof(data), &data_length));\n            if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class &&\n                HID_PROTOCOL_KEYBOARD == dev_params.proto) {\n                hid_host_keyboard_report_callback(data, data_length);\n            }\n            break;\n        case HID_HOST_INTERFACE_EVENT_DISCONNECTED:\n            ESP_LOGI(TAG, \"Keyboard Disconnected\");\n            ESP_ERROR_CHECK(hid_host_device_close(hid_device_handle));\n            break;\n        default:\n            break;\n    }\n}\n\n// HID设备事件处理\nvoid hid_host_device_event(hid_host_device_handle_t hid_device_handle,\n                           const hid_host_driver_event_t event,\n                           void *arg) {\n    hid_host_dev_params_t dev_params;\n    ESP_ERROR_CHECK(hid_host_device_get_params(hid_device_handle, &dev_params));\n\n    if (event == HID_HOST_DRIVER_EVENT_CONNECTED) {\n        ESP_LOGI(TAG, \"Keyboard Connected\");\n        const hid_host_device_config_t dev_config = {\n            .callback = hid_host_interface_callback,\n            .callback_arg = NULL\n        };\n        ESP_ERROR_CHECK(hid_host_device_open(hid_device_handle, &dev_config));\n        if (HID_SUBCLASS_BOOT_INTERFACE == dev_params.sub_class) {\n            ESP_ERROR_CHECK(hid_class_request_set_protocol(hid_device_handle, HID_REPORT_PROTOCOL_BOOT));\n            ESP_ERROR_CHECK(hid_class_request_set_idle(hid_device_handle, 0, 0));\n        }\n        ESP_ERROR_CHECK(hid_host_device_start(hid_device_handle));\n    }\n}\n\n\n// HID事件结构体\ntypedef struct {\n    hid_host_device_handle_t handle;\n    hid_host_driver_event_t event;\n    void *arg;\n} hid_event_t;\n\n// HID设备回调\nvoid hid_host_device_callback(hid_host_device_handle_t hid_device_handle,\n                              const hid_host_driver_event_t event,\n                              void *arg) {\n    hid_event_t hid_event = {\n        .handle = hid_device_handle,\n        .event = event,\n        .arg = arg\n    };\n\n    if (hid_event_queue) {\n        xQueueSend(hid_event_queue, &hid_event, 0);\n    }\n}\n\n// USB主机任务\nstatic void usb_lib_task(void *arg) {\n    const usb_host_config_t host_config = {\n        .intr_flags = ESP_INTR_FLAG_LEVEL1,\n    };\n    ESP_ERROR_CHECK(usb_host_install(&host_config));\n\n    while (1) {\n        uint32_t event_flags;\n        usb_host_lib_handle_events(portMAX_DELAY, &event_flags);\n        if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {\n            ESP_ERROR_CHECK(usb_host_device_free_all());\n        }\n    }\n}\n\n// 主函数\nvoid app_main(void) {\n    ESP_LOGI(TAG, \"Keyboard Display Test Program Started\");\n\n    // 初始化LCD屏幕\n    spi_bus_config_t buscfg = {\n        .sclk_io_num = PIN_NUM_CLK,\n        .mosi_io_num = PIN_NUM_MOSI,\n        .miso_io_num = -1,\n        .quadwp_io_num = -1,\n        .quadhd_io_num = -1,\n        .max_transfer_sz = LCD_WIDTH * LCD_HEIGHT * 2 + 8\n    };\n    ESP_ERROR_CHECK(spi_bus_initialize(LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));\n\n    esp_lcd_panel_io_handle_t io_handle = NULL;\n    esp_lcd_panel_io_spi_config_t io_config = {\n        .dc_gpio_num = PIN_NUM_DC,\n        .cs_gpio_num = PIN_NUM_CS,\n        .pclk_hz = 8 * 1000 * 1000,\n        .lcd_cmd_bits = 8,\n        .lcd_param_bits = 8,\n        .spi_mode = 0,\n        .trans_queue_depth = 5,\n    };\n    ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)LCD_HOST, &io_config, &io_handle));\n\n    esp_lcd_panel_dev_config_t panel_config = {\n        .reset_gpio_num = PIN_NUM_RST,\n        .color_space = ESP_LCD_COLOR_SPACE_RGB,\n        .bits_per_pixel = 16,\n    };\n    ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(io_handle, &panel_config, &lcd_panel));\n\n    esp_lcd_panel_reset(lcd_panel);\n    esp_lcd_panel_init(lcd_panel);\n\n#if SCREEN_ORIENTATION == 1\n    esp_lcd_panel_swap_xy(lcd_panel, true);\n#else\n    esp_lcd_panel_swap_xy(lcd_panel, false);\n#endif\n\n    esp_lcd_panel_mirror(lcd_panel, false, true);\n    esp_lcd_panel_invert_color(lcd_panel, false);\n    esp_lcd_panel_disp_on_off(lcd_panel, true);\n\n    // 显示初始界面\n    clear_screen(bg_color);\n    draw_string(10, TEXT_LINE1_Y, \"USB KEYBOARD TEST\", text_color, bg_color);\n    draw_string(10, TEXT_LINE2_Y, \"CONNECT KEYBOARD\", text_color, bg_color);\n    draw_string(10, TEXT_INPUT_Y, \"Input: \", text_color, bg_color);\n\n    // 初始化目标练习文本为数组的第一句\n    practice_index = 0;\n    set_target_text(practice_list[practice_index]);\n\n    // 显示提示界面\n    draw_string(10, TEXT_INPUT_Y + 40, \"Next Sentence Starts!\", 0x0000, 0xFFE0); // 黑字黄底\n    vTaskDelay(1000 / portTICK_PERIOD_MS); // 延迟1秒\n\n    // 清除提示\n    draw_string(10, TEXT_INPUT_Y + 40, \"                                                                                \", text_color, bg_color);\n\n    // 显示目标练习文本\n    draw_string(10, TEXT_INPUT_Y + 20, target_text, text_color, bg_color);\n\n    // 初始化USB和HID\n    hid_event_queue = xQueueCreate(10, sizeof(hid_event_t));\n    xTaskCreate(usb_lib_task, \"usb_events\", 4096, NULL, 2, NULL);\n    vTaskDelay(1000 / portTICK_PERIOD_MS);\n\n    const hid_host_driver_config_t hid_host_driver_config = {\n        .create_background_task = true,\n        .task_priority = 5,\n        .stack_size = 8192,\n        .core_id = 0,\n        .callback = hid_host_device_callback,\n        .callback_arg = NULL\n    };\n    ESP_ERROR_CHECK(hid_host_install(&hid_host_driver_config));\n\n    ESP_LOGI(TAG, \"System Init Complete, Please Connect USB Keyboard\");\n\n    // 主事件循环\n    while (1) {\n        hid_event_t hid_event;\n        if (xQueueReceive(hid_event_queue, &hid_event, portMAX_DELAY)) {\n            hid_host_device_event(hid_event.handle, hid_event.event, hid_event.arg);\n        }\n    }\n}请帮我检查一下代码
11-24
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值