驅動11.網卡驅動程式

来源:http://www.cnblogs.com/Lwd-linux/archive/2017/01/30/6357564.html
-Advertisement-
Play Games

1 網路傳輸的簡介 (1)接收過程,如上圖,網路上的數據包到達網卡後,網卡產生中斷,然後設備驅動層收到中斷後,開始進行網路包的接收,接收完之後調用一個netif_rx函數交給網路協議層(層次結構上圖一),然後就是一層一層的網上傳到用戶空間了。 (2)發送過程,從用戶空間過來的數據包,經過層層穿越之後 ...


1 網路傳輸的簡介

  

 

  (1)接收過程,如上圖,網路上的數據包到達網卡後,網卡產生中斷,然後設備驅動層收到中斷後,開始進行網路包的接收,接收完之後調用一個netif_rx函數交給網路協議層(層次結構上圖一),然後就是一層一層的網上傳到用戶空間了。

  (2)發送過程,從用戶空間過來的數據包,經過層層穿越之後,到達網路協議層,然後調用一個dev_queue_xmit()函數之後就不管了,剩下的交給驅動層經過處理後,使用函數hard_start_xmit()函數發送,然後硬體上網卡開始發送數據包了。

2 dm9000網卡驅動源代碼分析

 2.1 首先,看入口函數

1 static int __init dm9000_init(void)
2 {
3     printk(KERN_INFO "%s Ethernet Driver\n", CARDNAME);
4 
5     return platform_driver_register(&dm9000_driver);    /* search board and register */
6 }

這是一個平臺匯流排的結構,註冊dm9000_driver。

 1 static struct platform_driver dm9000_driver = {
 2     .driver    = {
 3         .name    = "dm9000",
 4         .owner     = THIS_MODULE,
 5     },
 6     .probe   = dm9000_probe,
 7     .remove  = dm9000_drv_remove,
 8     .suspend = dm9000_drv_suspend,
 9     .resume  = dm9000_drv_resume,
10 };

如果平臺存在與其同名的平臺設備,將調用probe函數

2.2 probe函數

  1 /*
  2  * Search DM9000 board, allocate space and register it
  3  */
  4 static int
  5 dm9000_probe(struct platform_device *pdev)
  6 {
  7     struct dm9000_plat_data *pdata = pdev->dev.platform_data;
  8     struct board_info *db;    /* Point a board information structure */
  9     struct net_device *ndev;
 10     unsigned long base;
 11     int ret = 0;
 12     int iosize;
 13     int i;
 14     u32 id_val;
 15 
 16 #if defined(CONFIG_ARCH_S3C2410xxx)
 17     unsigned int oldval_bwscon;        /* 用來保存BWSCON寄存器的值 */
 18     unsigned int oldval_bankcon4;    /* 用來保存S3C2410_BANKCON4寄存器的值 */
 19 #endif
 20 
 21     /* Init network device */
 22     ndev = alloc_etherdev(sizeof (struct board_info));
 23     if (!ndev) {
 24         printk("%s: could not allocate device.\n", CARDNAME);
 25         return -ENOMEM;
 26     }
 27 
 28     SET_MODULE_OWNER(ndev);
 29     SET_NETDEV_DEV(ndev, &pdev->dev);
 30 
 31     PRINTK2("dm9000_probe()");
 32 
 33 #if defined(CONFIG_ARCH_S3C2410xxx)
 34     /* 設置Bank4: 匯流排寬度為16, 使能nWAIT。by www.100ask.net */
 35     oldval_bwscon = *((volatile unsigned int *)S3C2410_BWSCON);
 36     *((volatile unsigned int *)S3C2410_BWSCON) = (oldval_bwscon & ~(3<<16)) \
 37         | S3C2410_BWSCON_DW4_16 | S3C2410_BWSCON_WS4 | S3C2410_BWSCON_ST4;
 38 
 39     /* 設置BANK4的時間參數, by www.100ask.net */
 40     oldval_bankcon4 = *((volatile unsigned int *)S3C2410_BANKCON4);
 41     *((volatile unsigned int *)S3C2410_BANKCON4) = 0x1f7c;
 42 #endif
 43 
 44     /* setup board info structure */
 45     db = (struct board_info *) ndev->priv;
 46     memset(db, 0, sizeof (*db));
 47 
 48     spin_lock_init(&db->lock);
 49 
 50     if (pdev->num_resources < 2) {
 51         ret = -ENODEV;
 52         goto out;
 53     } else if (pdev->num_resources == 2) {
 54         base = pdev->resource[0].start;
 55 
 56         if (!request_mem_region(base, 4, ndev->name)) {
 57             ret = -EBUSY;
 58             goto out;
 59         }
 60 
 61         ndev->base_addr = base;
 62         ndev->irq = pdev->resource[1].start;
 63         db->io_addr = (void __iomem *)base;
 64         db->io_data = (void __iomem *)(base + 4);
 65 
 66     } else {
 67         db->addr_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
 68         db->data_res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
 69         db->irq_res  = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
 70 
 71         if (db->addr_res == NULL || db->data_res == NULL ||
 72             db->irq_res == NULL) {
 73             printk(KERN_ERR PFX "insufficient resources\n");
 74             ret = -ENOENT;
 75             goto out;
 76         }
 77 
 78         i = res_size(db->addr_res);
 79         db->addr_req = request_mem_region(db->addr_res->start, i,
 80                           pdev->name);
 81 
 82         if (db->addr_req == NULL) {
 83             printk(KERN_ERR PFX "cannot claim address reg area\n");
 84             ret = -EIO;
 85             goto out;
 86         }
 87 
 88         db->io_addr = ioremap(db->addr_res->start, i);
 89 
 90         if (db->io_addr == NULL) {
 91             printk(KERN_ERR "failed to ioremap address reg\n");
 92             ret = -EINVAL;
 93             goto out;
 94         }
 95 
 96         iosize = res_size(db->data_res);
 97         db->data_req = request_mem_region(db->data_res->start, iosize,
 98                           pdev->name);
 99 
100         if (db->data_req == NULL) {
101             printk(KERN_ERR PFX "cannot claim data reg area\n");
102             ret = -EIO;
103             goto out;
104         }
105 
106         db->io_data = ioremap(db->data_res->start, iosize);
107 
108         if (db->io_data == NULL) {
109             printk(KERN_ERR "failed to ioremap data reg\n");
110             ret = -EINVAL;
111             goto out;
112         }
113 
114         /* fill in parameters for net-dev structure */
115 
116         ndev->base_addr = (unsigned long)db->io_addr;
117         ndev->irq    = db->irq_res->start;
118 
119         /* ensure at least we have a default set of IO routines */
120         dm9000_set_io(db, iosize);
121     }
122 
123     /* check to see if anything is being over-ridden */
124     if (pdata != NULL) {
125         /* check to see if the driver wants to over-ride the
126          * default IO width */
127 
128         if (pdata->flags & DM9000_PLATF_8BITONLY)
129             dm9000_set_io(db, 1);
130 
131         if (pdata->flags & DM9000_PLATF_16BITONLY)
132             dm9000_set_io(db, 2);
133 
134         if (pdata->flags & DM9000_PLATF_32BITONLY)
135             dm9000_set_io(db, 4);
136 
137         /* check to see if there are any IO routine
138          * over-rides */
139 
140         if (pdata->inblk != NULL)
141             db->inblk = pdata->inblk;
142 
143         if (pdata->outblk != NULL)
144             db->outblk = pdata->outblk;
145 
146         if (pdata->dumpblk != NULL)
147             db->dumpblk = pdata->dumpblk;
148     }
149 
150     dm9000_reset(db);
151 
152     /* try two times, DM9000 sometimes gets the first read wrong */
153     for (i = 0; i < 2; i++) {
154         id_val  = ior(db, DM9000_VIDL);
155         id_val |= (u32)ior(db, DM9000_VIDH) << 8;
156         id_val |= (u32)ior(db, DM9000_PIDL) << 16;
157         id_val |= (u32)ior(db, DM9000_PIDH) << 24;
158 
159         if (id_val == DM9000_ID)
160             break;
161         printk("%s: read wrong id 0x%08x\n", CARDNAME, id_val);
162     }
163 
164     if (id_val != DM9000_ID) {
165         printk("%s: wrong id: 0x%08x\n", CARDNAME, id_val);
166         goto release;
167     }
168 
169     /* from this point we assume that we have found a DM9000 */
170 
171     /* driver system function */
172     ether_setup(ndev);
173 
174     ndev->open         = &dm9000_open;
175     ndev->hard_start_xmit    = &dm9000_start_xmit;
176     ndev->tx_timeout         = &dm9000_timeout;
177     ndev->watchdog_timeo = msecs_to_jiffies(watchdog);
178     ndev->stop         = &dm9000_stop;
179     ndev->get_stats         = &dm9000_get_stats;
180     ndev->set_multicast_list = &dm9000_hash_table;
181 #ifdef CONFIG_NET_POLL_CONTROLLER
182     ndev->poll_controller     = &dm9000_poll_controller;
183 #endif
184 
185 #ifdef DM9000_PROGRAM_EEPROM
186     program_eeprom(db);
187 #endif
188     db->msg_enable       = NETIF_MSG_LINK;
189     db->mii.phy_id_mask  = 0x1f;
190     db->mii.reg_num_mask = 0x1f;
191     db->mii.force_media  = 0;
192     db->mii.full_duplex  = 0;
193     db->mii.dev         = ndev;
194     db->mii.mdio_read    = dm9000_phy_read;
195     db->mii.mdio_write   = dm9000_phy_write;
196 
197     /* Read SROM content */
198     for (i = 0; i < 64; i++)
199         ((u16 *) db->srom)[i] = read_srom_word(db, i);
200 
201     /* Set Node Address */
202     for (i = 0; i < 6; i++)
203         ndev->dev_addr[i] = db->srom[i];
204 
205     if (!is_valid_ether_addr(ndev->dev_addr)) {
206         /* try reading from mac */
207 
208         for (i = 0; i < 6; i++)
209             ndev->dev_addr[i] = ior(db, i+DM9000_PAR);
210     }
211 
212     if (!is_valid_ether_addr(ndev->dev_addr)) {
213         printk("%s: Invalid ethernet MAC address.  Please "
214                "set using ifconfig\n", ndev->name);
215 #if defined(CONFIG_ARCH_S3C2410)
216         printk("Now use the default MAC address: 08:90:90:90:90:90\n");
217         ndev->dev_addr[0] = 0x08;
218         ndev->dev_addr[1] = 0x90;
219         ndev->dev_addr[2] = 0x90;
220         ndev->dev_addr[3] = 0x90;
221         ndev->dev_addr[4] = 0x90;
222         ndev->dev_addr[5] = 0x90;
223 #endif
224     }
225 
226     platform_set_drvdata(pdev, ndev);
227     ret = register_netdev(ndev);
228 
229     if (ret == 0) {
230         printk("%s: dm9000 at %p,%p IRQ %d MAC: ",
231                ndev->name,  db->io_addr, db->io_data, ndev->irq);
232         for (i = 0; i < 5; i++)
233             printk("%02x:", ndev->dev_addr[i]);
234         printk("%02x\n", ndev->dev_addr[5]);
235     }
236     return 0;
237 
238  release:
239  out:
240     printk("%s: not found (%d).\n", CARDNAME, ret);
241 #if defined(CONFIG_ARCH_S3C2410xxx)
242     /* 恢復寄存器原來的值 */
243     *((volatile unsigned int *)S3C2410_BWSCON) = oldval_bwscon;
244     *((volatile unsigned int *)S3C2410_BANKCON4) = oldval_bankcon4;
245 #endif
246     dm9000_release_board(pdev, db);
247     free_netdev(ndev);
248 
249     return ret;
250 }
probe函數

主要完後以下工作:

①分配一個net_device結構體(alloc_etherdev)

②定義一個單板相關信息board_info結構體

③設置net_device和board_info結構體

④使用board_info信息來初始化單板

⑤初始化網卡

⑥設置操作函數open,hard_start_xmit(發包函數),tx_timeout(超時函數),watchdog_timeo(看門狗函數),stop,get_stats,set_multicast_list

設置MAC地址

最後,註冊net_device結構體

2.3 發包函數

 1 /*
 2  *  Hardware start transmission.
 3  *  Send a packet to media from the upper layer.
 4  */
 5 static int
 6 dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
 7 {
 8     board_info_t *db = (board_info_t *) dev->priv;
 9 
10     PRINTK3("dm9000_start_xmit\n");
11 
12     if (db->tx_pkt_cnt > 1)
13         return 1;
14 
15     netif_stop_queue(dev);
16 
17     /* Disable all interrupts */
18     iow(db, DM9000_IMR, IMR_PAR);
19 
20     /* Move data to DM9000 TX RAM */
21     writeb(DM9000_MWCMD, db->io_addr);
22 
23     (db->outblk)(db->io_data, skb->data, skb->len);
24     db->stats.tx_bytes += skb->len;
25 
26     /* TX control: First packet immediately send, second packet queue */
27     if (db->tx_pkt_cnt == 0) {
28 
29         /* First Packet */
30         db->tx_pkt_cnt++;
31 
32         /* Set TX length to DM9000 */
33         iow(db, DM9000_TXPLL, skb->len & 0xff);
34         iow(db, DM9000_TXPLH, (skb->len >> 8) & 0xff);
35 
36         /* Issue TX polling command */
37         iow(db, DM9000_TCR, TCR_TXREQ);    /* Cleared after TX complete */
38 
39         dev->trans_start = jiffies;    /* save the time stamp */
40 
41     } else {
42         /* Second packet */
43         db->tx_pkt_cnt++;
44         db->queue_pkt_len = skb->len;
45     }
46 
47     /* free this SKB */
48     dev_kfree_skb(skb);
49 
50     /* Re-enable resource check */
51     if (db->tx_pkt_cnt == 1)
52         netif_wake_queue(dev);
53 
54     /* Re-enable interrupt */
55     iow(db, DM9000_IMR, IMR_PAR | IMR_PTM | IMR_PRM);
56 
57     return 0;
58 }
發包函數

主要完後以下工作:

1.停止該網卡的隊列,把skb數據寫入網卡netif_stop_queue(dev);
2.構造一個虛假的skb_buff上報
3.釋放skb(dev_kfree_skb)

4.數據全部發送出去後喚醒網卡隊列(netif_wake_queue)

5.更新統計信息

3 寫代碼

  1 /*
  2  * 參考 drivers\net\cs89x0.c
  3  */
  4 
  5 #include <linux/module.h>
  6 #include <linux/errno.h>
  7 #include <linux/netdevice.h>
  8 #include <linux/etherdevice.h>
  9 #include <linux/kernel.h>
 10 #include <linux/types.h>
 11 #include <linux/fcntl.h>
 12 #include <linux/interrupt.h>
 13 #include <linux/ioport.h>
 14 #include <linux/in.h>
 15 #include <linux/skbuff.h>
 16 #include <linux/slab.h>
 17 #include <linux/spinlock.h>
 18 #include <linux/string.h>
 19 #include <linux/init.h>
 20 #include <linux/bitops.h>
 21 #include <linux/delay.h>
 22 #include <linux/ip.h>
 23 
 24 #include <asm/system.h>
 25 #include <asm/io.h>
 26 #include <asm/irq.h>
 27 
 28 static struct net_device *vnet_dev;
 29 
 30 static void emulator_rx_packet(struct sk_buff *skb, struct net_device *dev)
 31 {
 32     /* 參考LDD3 */
 33     unsigned char *type;
 34     struct iphdr *ih;
 35     __be32 *saddr, *daddr, tmp;
 36     unsigned char    tmp_dev_addr[ETH_ALEN];
 37     struct ethhdr *ethhdr;
 38     
 39     struct sk_buff *rx_skb;
 40         
 41     // 從硬體讀出/保存數據
 42     /* 對調"源/目的"的mac地址 */
 43     ethhdr = (struct ethhdr *)skb->data;
 44     memcpy(tmp_dev_addr, ethhdr->h_dest, ETH_ALEN);
 45     memcpy(ethhdr->h_dest, ethhdr->h_source, ETH_ALEN);
 46     memcpy(ethhdr->h_source, tmp_dev_addr, ETH_ALEN);
 47 
 48     /* 對調"源/目的"的ip地址 */    
 49     ih = (struct iphdr *)(skb->data + sizeof(struct ethhdr));
 50     saddr = &ih->saddr;
 51     daddr = &ih->daddr;
 52 
 53     tmp = *saddr;
 54     *saddr = *daddr;
 55     *daddr = tmp;
 56     
 57     //((u8 *)saddr)[2] ^= 1; /* change the third octet (class C) */
 58     //((u8 *)daddr)[2] ^= 1;
 59     type = skb->data + sizeof(struct ethhdr) + sizeof(struct iphdr);
 60     //printk("tx package type = %02x\n", *type);
 61     // 修改類型, 原來0x8表示ping
 62     *type = 0; /* 0表示reply */
 63     
 64     ih->check = 0;           /* and rebuild the checksum (ip needs it) */
 65     ih->check = ip_fast_csum((unsigned char *)ih,ih->ihl);
 66     
 67     // 構造一個sk_buff
 68     rx_skb = dev_alloc_skb(skb->len + 2);
 69     skb_reserve(rx_skb, 2); /* align IP on 16B boundary */    
 70     memcpy(skb_put(rx_skb, skb->len), skb->data, skb->len);
 71 
 72     /* Write metadata, and then pass to the receive level */
 73     rx_skb->dev = dev;
 74     rx_skb->protocol = eth_type_trans(rx_skb, dev);
 75     rx_skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
 76     dev->stats.rx_packets++;
 77     dev->stats.rx_bytes += skb->len;
 78 
 79     // 提交sk_buff
 80     netif_rx(rx_skb);
 81 }
 82 
 83 static int virt_net_send_packet(struct sk_buff *skb, struct net_device *dev)
 84 {
 85     static int cnt = 0;
 86     printk("virt_net_send_packet cnt = %d\n", ++cnt);
 87 
 88     /* 對於真實的網卡, 把skb里的數據通過網卡發送出去 */
 89     netif_stop_queue(dev); /* 停止該網卡的隊列 */
 90     /* ...... */           /* 把skb的數據寫入網卡 */
 91 
 92     /* 構造一個假的sk_buff,上報 */
 93     emulator_rx_packet(skb, dev);
 94 
 95     dev_kfree_skb (skb);   /* 釋放skb */
 96     netif_wake_queue(dev); /* 數據全部發送出去後,喚醒網卡的隊列 */
 97 
 98     /* 更新統計信息 */
 99     dev->stats.tx_packets++;
100     dev->stats.tx_bytes += skb->len;
101     
102     return 0;
103 }
104 
105 
106 static int virt_net_init(void)
107 {
108     /* 1. 分配一個net_device結構體 */
109     vnet_dev = alloc_netdev(0, "vnet%d", ether_setup);;  /* alloc_etherdev */
110 
111     /* 2. 設置 */
112     vnet_dev->hard_start_xmit = virt_net_send_packet;
113 
114     /* 設置MAC地址 */
115     vnet_dev->dev_addr[0] = 0x08;
116     vnet_dev->dev_addr[1] = 0x89;
117     vnet_dev->dev_addr[2] = 0x89;
118     vnet_dev->dev_addr[3] = 0x89;
119     vnet_dev->dev_addr[4] = 0x89;
120     vnet_dev->dev_addr[5] = 0x11;
121 
122     /* 設置下麵兩項才能ping通 */
123     vnet_dev->flags           |= IFF_NOARP;
124     vnet_dev->features        |= NETIF_F_NO_CSUM;    
125 
126     /* 3. 註冊 */
127     //register_netdevice(vnet_dev);
128     register_netdev(vnet_dev);
129     
130     return 0;
131 }
132 
133 static void virt_net_exit(void)
134 {
135     unregister_netdev(vnet_dev);
136     free_netdev(vnet_dev);
137 }
138 
139 module_init(virt_net_init);
140 module_exit(virt_net_exit);
141 
142 MODULE_AUTHOR("[email protected],[email protected]");
143 MODULE_LICENSE("GPL");
virtnet.c

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 安裝 啟動 ...
  • 驅動程式的調試一. 列印: printk, 自製proc文件UBOOT傳入console=ttySAC0(串口) console=tty1(LCD)1. 內核處理UBOOT傳入的參數console_setup add_preferred_console // 我想用名為"ttySAC0"的控制台,先 ...
  • 第一、安裝好Win10系統,不需要安裝其他激活工具。第二、是刪除預設序列號,打開命令提示符(管理員),運行 slmgr.vbs -upk,可提示已卸載了序列號。 slmgr /ipk W269N-WFGWX-YVC9B-4J6C9-T83GXslmgr /skms kms.xspace.inslmg ...
  • 給自家的Ubuntu下載軟體速度有點慢,畢竟是從國外下載軟體,就想更換到國內比較好的更新源(就是這些軟體所在的伺服器),一般直接百度Ubuntu更新源就能出來一大堆,這時候最好是找和自己Ubuntu版本一致的更新源,我的Ubuntu版本是16.04,下麵是我找到的一個比較好的更新源 http://w ...
  • 1. 點燈法 可直接使用bl led_flicker來使用該程式。2. 串口列印及棧初步分析2.1 使用的前提是串口已經初始化完畢且可以正常使用 直接在需要定位錯誤的地方加上printk語句,看串口是否有相應的輸出即可。2.2 nand_setup的分析 a.棧和局部變數是臨時生成的 b.局部變數的 ...
  • 原本的字元設備只能有255個驅動程式,原因是一個主設備號占用了0~255的次設備號 把register_chrdev展開可得到一下幾個部分:register_chrdev_region/alloc_chrdev_region,cdev_init,cdev_add 參照register_chrdev的 ...
  • 1 分析i2c設備的識別過程i2c_add_driver i2c_register_driver driver->driver.bus = &i2c_bus_type; driver_register(&driver->driver); list_for_each_entry(adapter, &a ...
  • 1 確定相異性 1.1 選中網卡晶元nGCS4 1.2 確定相異性:基地址,中斷號,設置時序(記憶體控制器BWSCON,BANKCONn) 1.3 修改相應的部分 2 測試DM9000C驅動程式:2.1 把dm9dev9000c.c放到內核的drivers/net目錄下2.2 修改drivers/ne ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...