Linux網路驅動--snull

来源:http://www.cnblogs.com/hackfun/archive/2016/12/18/6195139.html
-Advertisement-
Play Games

snull是《Linux Device Drivers》中的一個網路驅動的例子。這裡引用這個例子學習Linux網路驅動。 因為snull的源碼,網上已經更新到適合最新內核,而我自己用的還是2.6.22.6比較舊的內核。而網上好像找不到舊版的snull。因此結合《Linux Device Driver ...


snull是《Linux Device Drivers》中的一個網路驅動的例子。這裡引用這個例子學習Linux網路驅動。

因為snull的源碼,網上已經更新到適合最新內核,而我自己用的還是2.6.22.6比較舊的內核。而網上好像找不到舊版的snull。因此結合《Linux Device Drivers》把最新的snull例子移植到2.6.22.6內核中。移植也相對簡單,這裡也提供移植好的代碼。

估計不少網友看到《Linux Device Drivers》的網路驅動部分,一臉懵逼,包括我自己,不理解作者設計這個例子的真正目的,儘管有配圖,仍然懵懂,甚至不知道為什麼會用到6個IP地址。如圖:

 

其實作者的本意是想通過虛擬網卡來模擬實際的網卡和外部的網路設備的通信來討論網路驅動。通過其中任何一個網路介面(sn0或sn1)發送數據,都在另一個網路介面(sn0或sn1)接收到。

因為sn0和sn1都不在同一個網段,所以sn0和sn1之間直接互ping是不行的,這中間必須必須做點轉換。

例子:

理論上local0和remote0只能互ping,因為他們都在同一個網段:192.168.0.0,但事實上,local0在發出數據之後,local0的第3個位元組最低有效位改取反,就變成了remote1,remote1的數據才能到達local1,因為他們在同一段IP。相反,local1在發出數據之後,local1的第3個位元組最低有效位改取反,就變成了remote0,remote0的數據才能到達local0.

因此,在實驗之前,需要添加一些配置:

在/etc/networks文件中添加如下網段IP:

snullnet0 192.168.2.0

snullnet1 192.168.3.0

在/etc/hosts文件中添加如下IP地址

192.168.2.8 local0

192.168.2.9 remote0

192.168.3.9 local1

192.168.3.8 remote1

註意: 1. 網段IP和IP地址的第三個位元組的最低有效位是相反的

         2. local0和remote1第四個位元組必須一樣,remote0和local1第四個位元組必須一樣

         3. 如果開發板上的真正網卡用了的網段IP,就不能再用於本實驗。如:我的開發板的DM9000網卡使用網段是192.168.1.0, 因此本實驗不能再使用192.168.1.0作為網段,否則有衝突。

代碼: snull.c, 其中snull.h沒改動,因此不貼出來

  1 /*
  2  * snull.c --  the Simple Network Utility
  3  *
  4  * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
  5  * Copyright (C) 2001 O'Reilly & Associates
  6  *
  7  * The source code in this file can be freely used, adapted,
  8  * and redistributed in source or binary form, so long as an
  9  * acknowledgment appears in derived source files.  The citation
 10  * should list that the code comes from the book "Linux Device
 11  * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 12  * by O'Reilly & Associates.   No warranty is attached;
 13  * we cannot take responsibility for errors or fitness for use.
 14  *
 15  * $Id: snull.c,v 1.21 2004/11/05 02:36:03 rubini Exp $
 16  */
 17 
 18 #include <linux/module.h>
 19 #include <linux/init.h>
 20 #include <linux/moduleparam.h>
 21 
 22 #include <linux/sched.h>
 23 #include <linux/kernel.h> /* printk() */
 24 #include <linux/slab.h> /* kmalloc() */
 25 #include <linux/errno.h>  /* error codes */
 26 #include <linux/types.h>  /* size_t */
 27 #include <linux/interrupt.h> /* mark_bh */
 28 
 29 #include <linux/in.h>
 30 #include <linux/netdevice.h>   /* struct device, and other headers */
 31 #include <linux/etherdevice.h> /* eth_type_trans */
 32 #include <linux/ip.h>          /* struct iphdr */
 33 #include <linux/tcp.h>         /* struct tcphdr */
 34 #include <linux/skbuff.h>
 35 
 36 #include "snull.h"
 37 
 38 #include <linux/in6.h>
 39 #include <asm/checksum.h>
 40 
 41 MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
 42 MODULE_LICENSE("Dual BSD/GPL");
 43 
 44 
 45 /*
 46  * Transmitter lockup simulation, normally disabled.
 47  */
 48 static int lockup = 0;
 49 module_param(lockup, int, 0);
 50 
 51 static int timeout = SNULL_TIMEOUT;
 52 module_param(timeout, int, 0);
 53 
 54 /*
 55  * Do we run in NAPI mode?
 56  */
 57 static int use_napi = 0;
 58 module_param(use_napi, int, 0);
 59 
 60 
 61 /*
 62  * A structure representing an in-flight packet.
 63  */
 64 struct snull_packet {
 65     struct snull_packet *next;
 66     struct net_device *dev;
 67     int    datalen;
 68     u8 data[ETH_DATA_LEN];
 69 };
 70 
 71 int pool_size = 8;
 72 module_param(pool_size, int, 0);
 73 
 74 /*
 75  * This structure is private to each device. It is used to pass
 76  * packets in and out, so there is place for a packet
 77  */
 78 
 79 struct snull_priv {
 80     struct net_device_stats stats;
 81     int status;
 82     struct snull_packet *ppool;
 83     struct snull_packet *rx_queue;  /* List of incoming packets */
 84     int rx_int_enabled;
 85     int tx_packetlen;
 86     u8 *tx_packetdata;
 87     struct sk_buff *skb;
 88     spinlock_t lock;
 89     struct net_device *dev;
 90     //struct napi_struct napi;
 91 };
 92 
 93 static void snull_tx_timeout(struct net_device *dev);
 94 static void (*snull_interrupt)(int, void *, struct pt_regs *);
 95 
 96 /*
 97  * Set up a device's packet pool.
 98  */
 99 void snull_setup_pool(struct net_device *dev)
100 {
101     struct snull_priv *priv = netdev_priv(dev);
102     int i;
103     struct snull_packet *pkt;
104 
105     priv->ppool = NULL;
106     for (i = 0; i < pool_size; i++) {
107         pkt = kmalloc (sizeof (struct snull_packet), GFP_KERNEL);
108         if (pkt == NULL) {
109             printk (KERN_NOTICE "Ran out of memory allocating packet pool\n");
110             return;
111         }
112         pkt->dev = dev;
113         pkt->next = priv->ppool;
114         priv->ppool = pkt;
115     }
116 }
117 
118 void snull_teardown_pool(struct net_device *dev)
119 {
120     struct snull_priv *priv = netdev_priv(dev);
121     struct snull_packet *pkt;
122     
123     while ((pkt = priv->ppool)) {
124         priv->ppool = pkt->next;
125         kfree (pkt);
126         /* FIXME - in-flight packets ? */
127     }
128 }    
129 
130 /*
131  * Buffer/pool management.
132  */
133 struct snull_packet *snull_get_tx_buffer(struct net_device *dev)
134 {
135     struct snull_priv *priv = netdev_priv(dev);
136     unsigned long flags;
137     struct snull_packet *pkt;
138     
139     spin_lock_irqsave(&priv->lock, flags);
140     pkt = priv->ppool;
141     priv->ppool = pkt->next;
142     if (priv->ppool == NULL) {
143         printk (KERN_INFO "Pool empty\n");
144         netif_stop_queue(dev);
145     }
146     spin_unlock_irqrestore(&priv->lock, flags);
147     return pkt;
148 }
149 
150 
151 void snull_release_buffer(struct snull_packet *pkt)
152 {
153     unsigned long flags;
154     struct snull_priv *priv = netdev_priv(pkt->dev);
155     
156     spin_lock_irqsave(&priv->lock, flags);
157     pkt->next = priv->ppool;
158     priv->ppool = pkt;
159     spin_unlock_irqrestore(&priv->lock, flags);
160     if (netif_queue_stopped(pkt->dev) && pkt->next == NULL)
161         netif_wake_queue(pkt->dev);
162 
163     printk("snull_release_buffer\n");
164 }
165 
166 void snull_enqueue_buf(struct net_device *dev, struct snull_packet *pkt)
167 {
168     unsigned long flags;
169     struct snull_priv *priv = netdev_priv(dev);
170 
171     spin_lock_irqsave(&priv->lock, flags);
172     pkt->next = priv->rx_queue;  /* FIXME - misorders packets */
173     priv->rx_queue = pkt;
174     spin_unlock_irqrestore(&priv->lock, flags);
175 }
176 
177 struct snull_packet *snull_dequeue_buf(struct net_device *dev)
178 {
179     struct snull_priv *priv = netdev_priv(dev);
180     struct snull_packet *pkt;
181     unsigned long flags;
182 
183     spin_lock_irqsave(&priv->lock, flags);
184     pkt = priv->rx_queue;
185     if (pkt != NULL)
186         priv->rx_queue = pkt->next;
187     spin_unlock_irqrestore(&priv->lock, flags);
188     return pkt;
189 }
190 
191 /*
192  * Enable and disable receive interrupts.
193  */
194 static void snull_rx_ints(struct net_device *dev, int enable)
195 {
196     struct snull_priv *priv = netdev_priv(dev);
197     priv->rx_int_enabled = enable;
198 }
199 
200     
201 /*
202  * Open and close
203  */
204 
205 int snull_open(struct net_device *dev)
206 {
207     /* request_region(), request_irq(), ....  (like fops->open) */
208 
209     /* 
210      * Assign the hardware address of the board: use "\0SNULx", where
211      * x is 0 or 1. The first byte is '\0' to avoid being a multicast
212      * address (the first byte of multicast addrs is odd).
213      */
214     /* [cgw]: 分配一個假的硬體地址,真正的網卡的時候,這個地址是從網卡讀出來的 */
215     memcpy(dev->dev_addr, "\0SNUL0", ETH_ALEN);
216     /* [cgw]: 因為註冊了兩個虛擬網卡,第二個虛擬網卡的地址跟第一個的地址必須不一樣
217      * 即這兩個網卡地址分別為\0SNUL0和\0SNUL1
218      */
219     if (dev == snull_devs[1])
220         dev->dev_addr[ETH_ALEN-1]++; /* \0SNUL1 */
221     /* [cgw]: 啟動發送隊列 */
222     netif_start_queue(dev);
223 
224     printk("snull_open\n");
225     
226     return 0;
227 }
228 
229 int snull_release(struct net_device *dev)
230 {
231     /* release ports, irq and such -- like fops->close */
232 
233     netif_stop_queue(dev); /* can't transmit any more */
234     
235     printk("snull_release\n");
236     
237     return 0;
238 }
239 
240 /*
241  * Configuration changes (passed on by ifconfig)
242  */
243 int snull_config(struct net_device *dev, struct ifmap *map)
244 {
245     if (dev->flags & IFF_UP) /* can't act on a running interface */
246         return -EBUSY;
247 
248     /* Don't allow changing the I/O address */
249     if (map->base_addr != dev->base_addr) {
250         printk(KERN_WARNING "snull: Can't change I/O address\n");
251         return -EOPNOTSUPP;
252     }
253 
254     /* Allow changing the IRQ */
255     if (map->irq != dev->irq) {
256         dev->irq = map->irq;
257             /* request_irq() is delayed to open-time */
258     }
259 
260     printk("snull_config\n");
261 
262     /* ignore other fields */
263     return 0;
264 }
265 
266 /*
267  * Receive a packet: retrieve, encapsulate and pass over to upper levels
268  */
269 void snull_rx(struct net_device *dev, struct snull_packet *pkt)
270 {
271     struct sk_buff *skb;
272     struct snull_priv *priv = netdev_priv(dev);
273 
274     /*
275      * The packet has been retrieved from the transmission
276      * medium. Build an skb around it, so upper layers can handle it
277      */
278     /* [cgw]: 為接收包分配一個skb */
279     skb = dev_alloc_skb(pkt->datalen + 2);
280     if (!skb) {
281         if (printk_ratelimit())
282             printk(KERN_NOTICE "snull rx: low on mem - packet dropped\n");
283         priv->stats.rx_dropped++;
284         goto out;
285     }
286     /* [cgw]: 16位元組對齊,即IP首部前是網卡硬體地址首部,其占14位元組,需要為其增加2
287      * 個位元組 
288      */
289     skb_reserve(skb, 2); /* align IP on 16B boundary */
290     /* [cgw]: 開闢一個數據緩衝區用於存放接收數據 */
291     memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
292 
293     /* Write metadata, and then pass to the receive level */
294     skb->dev = dev;
295     if (skb->dev == snull_devs[0]) {
296         printk("skb->dev is snull_devs[0]\n");
297     } else {
298         printk("skb->dev is snull_devs[1]\n");
299     }
300     /* [cgw]: 確定包的協議ID */
301     skb->protocol = eth_type_trans(skb, dev);
302 
303     printk("skb->protocol = %d\n", skb->protocol);
304     
305     skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
306     /* [cgw]: 統計接收包數和位元組數 */
307     priv->stats.rx_packets++;
308     priv->stats.rx_bytes += pkt->datalen;
309     /* [cgw]: 上報應用層 */
310     netif_rx(skb);
311 
312     printk("snull_rx\n");
313     
314   out:
315     return;
316 }
317     
318 
319 /*
320  * The poll implementation.
321  */
322 //static int snull_poll(struct napi_struct *napi, int budget)
323 static int snull_poll(struct net_device *dev, int *budget)
324 {
325     //int npackets = 0;
326     //struct sk_buff *skb;
327     //struct snull_priv *priv = container_of(napi, struct snull_priv, napi);
328     //struct net_device *dev = priv->dev;
329     //struct snull_packet *pkt;
330 
331     int npackets = 0, quota = min(dev->quota, *budget);
332     struct sk_buff *skb;
333     struct snull_priv *priv = netdev_priv(dev);
334     struct snull_packet *pkt;
335 
336     printk("snull_poll\n");
337     
338     //while (npackets < budget && priv->rx_queue) {
339     while (npackets < quota && priv->rx_queue) {
340         pkt = snull_dequeue_buf(dev);
341         skb = dev_alloc_skb(pkt->datalen + 2);
342         if (! skb) {
343             if (printk_ratelimit())
344                 printk(KERN_NOTICE "snull: packet dropped\n");
345             priv->stats.rx_dropped++;
346             snull_release_buffer(pkt);
347             continue;
348         }
349         skb_reserve(skb, 2); /* align IP on 16B boundary */  
350         memcpy(skb_put(skb, pkt->datalen), pkt->data, pkt->datalen);
351         skb->dev = dev;
352         skb->protocol = eth_type_trans(skb, dev);
353         skb->ip_summed = CHECKSUM_UNNECESSARY; /* don't check it */
354         netif_receive_skb(skb);
355         
356             /* Maintain stats */
357         npackets++;
358         priv->stats.rx_packets++;
359         priv->stats.rx_bytes += pkt->datalen;
360         snull_release_buffer(pkt);
361     }
362     /* If we processed all packets, we're done; tell the kernel and reenable ints */
363     *budget -= npackets;
364     dev->quota -= npackets;
365     if (! priv->rx_queue) {
366         //napi_complete(napi);
367         netif_rx_complete(dev);
368         snull_rx_ints(dev, 1);
369         return 0;
370     }
371     /* We couldn't process everything. */
372     //return npackets;
373     return 1;
374 }        
375         
376 /*
377  * The typical interrupt entry point
378  */
379 static void snull_regular_interrupt(int irq, void *dev_id, struct pt_regs *regs)
380 {
381     int statusword;
382     struct snull_priv *priv;
383     struct snull_packet *pkt = NULL;
384     /*
385      * As usual, check the "device" pointer to be sure it is
386      * really interrupting.
387      * Then assign "struct device *dev"
388      */
389     struct net_device *dev = (struct net_device *)dev_id;
390     /* ... and check with hw if it's really ours */
391 
392     /* paranoid */
393     if (!dev)
394         return;
395 
396     /* Lock the device */
397     priv = netdev_priv(dev);
398     spin_lock(&priv->lock);
399 
400     /* [cgw]: 判斷產生的是什麼類型的中斷,接收還是中斷 */
401     /* retrieve statusword: real netdevices use I/O instructions */
402     statusword = priv->status;
403     
404     printk("priv->status = %d\n", priv->status);
405     
406     priv->status = 0;
407     /* [cgw]: 接收完成中斷 */
408     if (statusword & SNULL_RX_INTR) {
409         /* send it to snull_rx for handling */
410         pkt = priv->rx_queue;
411         if (pkt) {
412             priv->rx_queue = pkt->next;
413             /* [cgw]: 網卡接收到數據,上報給應用層 */
414             snull_rx(dev, pkt);
415         }
416     }
417     /* [cgw]: 發送完成中斷 */
418     if (statusword & SNULL_TX_INTR) {
419         /* [cgw]: 統計已發送的包數和總位元組數,並釋放這個包的記憶體 */
420         /* a transmission is over: free the skb */
421         priv->stats.tx_packets++;
422         priv->stats.tx_bytes += priv->tx_packetlen;
423         dev_kfree_skb(priv->skb);
424     }
425 
426     /* Unlock the device and we are done */
427     spin_unlock(&priv->lock);
428     if (pkt) snull_release_buffer(pkt); /* Do this outside the lock! */
429 
430     printk("snull_regular_interrupt\n");
431 
432     return;
433 }
434 
435 /*
436  * A NAPI interrupt handler.
437  */
438 static void snull_napi_interrupt(int irq, void *dev_id, struct pt_regs *regs)
439 {
440     int statusword;
441     struct snull_priv *priv;
442 
443     /*
444      * As usual, check the "device" pointer for shared handlers.
445      * Then assign "struct device *dev"
446      */
447     struct net_device *dev = (struct net_device *)dev_id;
448     /* ... and check with hw if it's really ours */
449 
450     printk("snull_napi_interrupt\n");
451 
452     /* paranoid */
453     if (!dev)
454         return;
455 
456     /* Lock the device */
457     priv = netdev_priv(dev);
458     spin_lock(&priv->lock);
459 
460     /* retrieve statusword: real netdevices use I/O instructions */
461     statusword = priv->status;
462     priv->status = 0;
463     if (statusword & SNULL_RX_INTR) {
464         snull_rx_ints(dev, 0);  /* Disable further interrupts */
465         //napi_schedule(&priv->napi);
466         netif_rx_schedule(dev);
467     }
468     if (statusword & SNULL_TX_INTR) {
469             /* a transmission is over: free the skb */
470         priv->stats.tx_packets++;
471         priv->stats.tx_bytes += priv->tx_packetlen;
472         dev_kfree_skb(priv->skb);
473     }
474 
475     /* Unlock the device and we are done */
476     spin_unlock(&priv->lock);
477     return;
478 }
479 
480 
481 /*
482  * Transmit a packet (low level interface)
483  */
484 static void snull_hw_tx(char *buf, int len, struct net_device *dev)
485 {
486     /*
487      * This function deals with hw details. This interface loops
488      * back the packet to the other snull interface (if any).
489      * In other words, this function implements the snull behaviour,
490      * while all other procedures are rather device-independent
491      */
492     struct iphdr *ih;
493     struct net_device *dest;
494     struct snull_priv *priv;
495     u32 *saddr, *daddr;
496     struct snull_packet *tx_buffer;
497     
	   

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

-Advertisement-
Play Games
更多相關文章
  • var http = require( 'http' ) var handlePaths = [] /** * 初始化路由配置數組 */ function initRotute() { handlePaths.push( '/' ) handlePaths.push( '/login' ) hand... ...
  • 1、相關環境 centos7 hadoop2.6.5 zookeeper3.4.9 jdk1.8 hbase1.2.4 本篇文章僅涉及hbase集群的搭建,關於hadoop與zookeeper的相關部署參見上篇文章http://www.cnblogs.com/learn21cn/p/6184490. ...
  • $slice 如果希望數組的最大長度是固定的,那麼可以將 $slice 和 $push 組合在一起使用,就可以保證數組不會超出設定好的最大長度。$slice 的值必須是負整數。 假設$slice的值為10,如果$push 後的數組的元素個數小於10,那麼所有元素都會保留。反之,只有最後那10個元素會 ...
  • 本文是在Cat Qi的原貼的基礎之上,經本人逐題分別在MySql資料庫中實現的筆記,持續更新... 參考原貼:http://www.cnblogs.com/qixuejia/p/3637735.html 01 表結構 Student(Sno,Sname,Sage,Ssex) 學生表 Course(C ...
  • 本文是介紹MySQL資料庫InnoDB存儲引擎重做日誌漫游 00 – Undo LogUndo Log 是為了實現事務的原子性,在MySQL資料庫InnoDB存儲引擎中,還用Undo Log來實現多版本併發控制(簡稱:MVCC)。 - 事務的原子性(Atomicity) 事務中的所有操作,要麼全部完 ...
  • 1.環境準備 手動添加資料庫依賴: 在package.json的dependencies中新增, “mysql” : “latest”, 使用命令安裝mysql並添加依賴: 2.官方例子: 運行node ...
  • 一:在Dos里切換盤符 a:在電腦左下角右擊顯示圖片;(我用的是win10系統,其他系統類似) b:點擊運行,輸入cmd; c:點擊確定: d:輸入盤符:(如f:) 或F: 只寫字母,不寫分號是不行的!如下圖: cd f:(F:)是不行的如下圖: ...
  • 我在 "Linux字元設備驅動框架" 一文中已經簡單的介紹了字元設備驅動的基本的編程框架,這裡我們來探討一下Linux內核(以4.8.5內核為例)是怎麼管理字元設備的,即當我們獲得了設備號,分配了 cdev 結構,註冊了驅動的操作方法集,最後進行 cdev_add() 的時候,究竟是將哪些內容告訴了 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...