驅動02.按鍵

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

一.以查詢方式實現 1.寫出驅動框架 1.1 仿照其他程式加一些必要的頭文件 1.2 構造一個結構體file_operations 1.3 根據file_operations的所選項寫出所需的函數,並構建出來 1.4 入口函數、出口函數的註冊和卸載 1.5 修飾入口函數和出口函數 1.6 給sysf ...


一.以查詢方式實現

1.寫出驅動框架
  1.1 仿照其他程式加一些必要的頭文件
  1.2 構造一個結構體file_operations
  1.3 根據file_operations的所選項寫出所需的函數,並構建出來
  1.4 入口函數、出口函數的註冊和卸載
  1.5 修飾入口函數和出口函數
  1.6 給sysfs提供更多的信息,並有udev機制自動創建/dev/xxx設備節點
2.硬體操作
  2.1 sch原理圖
  2.2 2440手冊
  2.3 編程:物理地址到虛擬地址的映射

  1 /*
  2   *以查詢的方式實現按鍵驅動程式
  3   */
  4 
  5 #include <linux/module.h>
  6 #include <linux/kernel.h>
  7 #include <linux/fs.h>
  8 #include <linux/init.h>
  9 #include <linux/delay.h>
 10 #include <asm/uaccess.h>
 11 #include <asm/irq.h>
 12 #include <asm/io.h>
 13 #include <asm/arch/regs-gpio.h>
 14 #include <asm/hardware.h>
 15 
 16 
 17 static struct class *g_ptSeconddrvCls;
 18 static struct class_device *g_ptSeconddrvClsDev;
 19 
 20 volatile unsigned long *gpfcon;
 21 volatile unsigned long *gpfdat;
 22 
 23 volatile unsigned long *gpgcon;
 24 volatile unsigned long *gpgdat;
 25 
 26 static int seconddrv_open(struct inode *inode, struct file *file)
 27 {
 28     /* 配置GPF0,2為輸入引腳 */
 29     *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
 30 
 31     /* 配置GPG3,11為輸入引腳 */
 32     *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
 33 
 34     return 0;
 35 }
 36 
 37 static ssize_t seconddrv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 38 {
 39     /* 返回4個引腳的電平 */
 40     unsigned char key_vals[4];
 41     int regval;
 42 
 43     if (size != sizeof(key_vals))
 44         return -EINVAL;
 45 
 46     /* 讀GPF0,2 */
 47     regval = *gpfdat;
 48     key_vals[0] = (regval & (1<<0)) ? 1 : 0;
 49     key_vals[1] = (regval & (1<<2)) ? 1 : 0;
 50     
 51 
 52     /* 讀GPG3,11 */
 53     regval = *gpgdat;
 54     key_vals[2] = (regval & (1<<3)) ? 1 : 0;
 55     key_vals[3] = (regval & (1<<11)) ? 1 : 0;
 56 
 57     copy_to_user(buf, key_vals, sizeof(key_vals));
 58     
 59     return sizeof(key_vals);
 60 }
 61 
 62 static struct file_operations seconddrv_fops = {
 63     .owner = THIS_MODULE,
 64     .open   = seconddrv_open,     
 65     .read   =    seconddrv_read,
 66 };
 67 
 68 
 69 int g_iMajor;
 70 static int seconddrv_init(void)
 71 {
 72     g_iMajor = register_chrdev(0, "second_drv", &seconddrv_fops);
 73 
 74     g_ptSeconddrvCls = class_create(THIS_MODULE, "second_drv");
 75 
 76     g_ptSeconddrvClsDev = class_device_create(g_ptSeconddrvCls, NULL, MKDEV(g_iMajor, 0), NULL, "buttons"); /* /dev/buttons */
 77 
 78     gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
 79     gpfdat = gpfcon + 1;
 80 
 81     gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
 82     gpgdat = gpgcon + 1;
 83 
 84     return 0;
 85 }
 86 
 87 static int seconddrv_exit(void)
 88 {
 89     unregister_chrdev(g_iMajor, "second_drv");
 90     class_device_unregister(g_ptSeconddrvClsDev);
 91     class_destroy(g_ptSeconddrvCls);
 92     iounmap(gpfcon);
 93     iounmap(gpgcon);
 94     return 0;
 95 }
 96 
 97 module_init(seconddrv_init);
 98 
 99 module_exit(seconddrv_exit);
100 MODULE_LICENSE("GPL");
查詢方式的按鍵驅動程式
 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <stdio.h>
 5 
 6 /* 2nddrvtest 
 7   */
 8 int main(int argc, char **argv)
 9 {
10     int fd;
11     unsigned char key_vals[4];
12     int cnt = 0;
13     
14     fd = open("/dev/buttons", O_RDWR);
15     if (fd < 0)
16     {
17         printf("can't open!\n");
18     }
19 
20     while (1)
21     {
22         read(fd, key_vals, sizeof(key_vals));
23         if (!key_vals[0] || !key_vals[1] || !key_vals[2] || !key_vals[3])
24         {
25             printf("%04d key pressed: %d %d %d %d\n", cnt++, key_vals[0], key_vals[1], key_vals[2], key_vals[3]);
26         }
27     }
28     
29     return 0;
30 }
測試程式

 二、使用中斷方式實現按鍵驅動程式

使用查詢方式實現時,程式一直占用CPU資源,CPU利用率低。

改進的辦法:使用中斷的方式實現

1.linux中斷異常體系分析

  1.1 異常向量的設置trap_init()
    trap_init被用來設置各種異常的處理向量,trap_init函數將異常向量複製到0xffff0000處
    trap_init
        memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
            vector_irq
                vector_stub(巨集)//計算返回地址,保存一些寄存器
                    __irq_usr     //根據被中斷的工作模式進入相應的某個跳轉分支->進入管理模式
                        usr_entry(巨集)//保存一些寄存器的值
                            .maro irq_handler(巨集)
                                asm_do_IRQ //異常處理函數
  1.2 異常處理函數asm_do_IRQ
  asm_do_IRQ
      定義了一個結構體數組desc[NR_IRQS]
          irq_enter
          desc_handle_irq:desc->handle_irq
        
  1.3 結構數組desc[NR_IRQS]的構建    
  s3c24xx_init_irq
      set_irq_chip    //chip=s3c_irq_eint0t4,可設置觸發方式,使能中斷,禁止中斷等底層晶元相關的操作
      set_irq_flags
      set_irq_handler(irqno, handle_edge_irq)//handle_irq=handle_edge_irq
          __set_irq_handler
              desc_handle_irq
  

  中斷處理函數handle_edge_irq
    handle_edge_irq
        desc->chip->ack(irq)//清中斷
        handle_IRQ_event//取出action鏈表中的成員,執行action->handler
  1.4 用戶註冊中斷處理函數的過程request_irq
  request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)
          //用戶通過request_irq向內核註冊中斷處理函數:a.分配了irqaction結構b.調用setup_irq函數
      setup_irq(irq,action)//將新建的irqaction結構鏈入irq_desc[irq]結構的action鏈表中P405
      desc->chip->settype//定義引腳為中斷引腳
      desc->chip->startup/enable//引腳使能
  1.5 free_irq(irq,dev_id)//卸載中斷服務程式
      a.出鏈
      b.禁止中斷

  1.6 總結:handle_irq是這個中斷的處理函數入口,發生中斷時,總入口函數asm_do_IRQ函數將根據中斷號調用相應irq_desc數組項的handle_irq。handle_irq使用chip結構體中的函數來清除、屏蔽或者重新enable中斷,還一一調用用戶在action鏈表中註冊的中斷處理函數。

2.編寫代碼

  1 /*
  2   *用中斷方式實現按鍵驅動程式
  3   */
  4 
  5 #include <linux/module.h>
  6 #include <linux/kernel.h>
  7 #include <linux/fs.h>
  8 #include <linux/init.h>
  9 #include <linux/delay.h>
 10 #include <linux/irq.h>
 11 #include <asm/uaccess.h>
 12 #include <asm/irq.h>
 13 #include <asm/io.h>
 14 #include <asm/arch/regs-gpio.h>
 15 #include <asm/hardware.h>
 16 
 17 
 18 static struct class *g_ptThirddrvCls;
 19 static struct class_device *g_ptThirddrvClsDev;
 20 
 21 volatile unsigned long *gpfcon;
 22 volatile unsigned long *gpfdat;
 23 
 24 volatile unsigned long *gpgcon;
 25 volatile unsigned long *gpgdat;
 26 
 27 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
 28 
 29 /* 中斷事件標誌, 中斷服務程式將它置1,third_drv_read將它清0 */
 30 static volatile int ev_press = 0;
 31 
 32 struct pin_desc{
 33     unsigned int pin;
 34     unsigned int key_val;
 35 };
 36 
 37 static unsigned char key_val;
 38 
 39 struct pin_desc pins_desc[4] = {
 40     {S3C2410_GPF0, 0x01},
 41     {S3C2410_GPF2, 0x02},
 42     {S3C2410_GPG3, 0x03},
 43     {S3C2410_GPG11, 0x04},
 44 };
 45 
 46 
 47 /*
 48   * 確定按鍵值
 49   */
 50 static irqreturn_t buttons_irq(int irq, void *dev_id)
 51 {
 52     struct pin_desc * pindesc = (struct pin_desc *)dev_id;
 53     unsigned int pinval;
 54     
 55     pinval = s3c2410_gpio_getpin(pindesc->pin);
 56 
 57     if (pinval)
 58     {
 59         /* 鬆開 */
 60         key_val = 0x80 | pindesc->key_val;
 61     }
 62     else
 63     {
 64         /* 按下 */
 65         key_val = pindesc->key_val;
 66     }
 67 
 68     ev_press = 1;                  /* 表示中斷發生了 */
 69     wake_up_interruptible(&button_waitq);   /* 喚醒休眠的進程 */
 70 
 71     
 72     return IRQ_RETVAL(IRQ_HANDLED);
 73 }
 74 
 75 static int thirddrv_open(struct inode *inode, struct file *file)
 76 {
 77     request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
 78     request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
 79     request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
 80     request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);    
 81 
 82     return 0;
 83 }
 84 
 85 static ssize_t thirddrv_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
 86 {
 87     
 88     if (size != 1)
 89         return -EINVAL;
 90 
 91     /* 如果沒有按鍵動作, 休眠 */
 92     wait_event_interruptible(button_waitq, ev_press);
 93 
 94     /* 如果有按鍵動作, 返回鍵值 */
 95     copy_to_user(buf, &key_val, 1);
 96     ev_press = 0;
 97     
 98     return 1;
 99 }
100 
101 static int thirddrv_close (struct inode * inode, struct file * file)
102 {
103     free_irq(IRQ_EINT0,&pins_desc[0]);
104     free_irq(IRQ_EINT2,&pins_desc[1]);
105     free_irq(IRQ_EINT11,&pins_desc[2]);
106     free_irq(IRQ_EINT19,&pins_desc[3]);
107 
108     return 0;
109 }
110 
111 static struct file_operations thirddrv_fops = {
112     .owner = THIS_MODULE,
113     .open   = thirddrv_open,     
114     .read   =    thirddrv_read,
115     .release = thirddrv_close,
116 };
117 
118 
119 int g_iMajor;
120 static int thirddrv_init(void)
121 {
122     g_iMajor = register_chrdev(0, "third_drv", &thirddrv_fops);
123 
124     g_ptThirddrvCls = class_create(THIS_MODULE, "third_drv");
125 
126     g_ptThirddrvClsDev = class_device_create(g_ptThirddrvCls, NULL, MKDEV(g_iMajor, 0), NULL, "buttons"); /* /dev/buttons */
127 
128     gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
129     gpfdat = gpfcon + 1;
130 
131     gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
132     gpgdat = gpgcon + 1;
133 
134     return 0;
135 }
136 
137 static int thirddrv_exit(void)
138 {
139     unregister_chrdev(g_iMajor, "third_drv");
140     class_device_unregister(g_ptThirddrvClsDev);
141     class_destroy(g_ptThirddrvCls);
142     iounmap(gpfcon);
143     iounmap(gpgcon);
144     return 0;
145 }
146 
147 module_init(thirddrv_init);
148 
149 module_exit(thirddrv_exit);
150 MODULE_LICENSE("GPL");
驅動程式
 1 #include <sys/types.h>
 2 #include <sys/stat.h>
 3 #include <fcntl.h>
 4 #include <stdio.h>
 5 #include <unistd.h>
 6 
 7 /* thirddrvtest 
 8   */
 9 int main(int argc, char **argv)
10 {
11     int fd;
12     unsigned char key_val;
13     
14     fd = open("/dev/buttons", O_RDWR);
15     if (fd < 0)
16     {
17         printf("can't open!\n");
18     }
19 
20     while (1)
21     {
22         //read(fd, &key_val, 1);
23         //printf("key_val = 0x%x\n", key_val);
24         sleep(5);
25     }
26     
27     return 0;
28 }
測試程式

三、在上述基礎上添加poll機制

  雖然我們實現了中斷式的按鍵驅動,但是我們發覺,測試程式是一個無限的迴圈。在實際應用中並不存在這樣的情況,所以我們要進一步優化——poll機制。

1.poll機制的分析

1.1 函數原型

int poll(struct pollfd *fds,nfds_t nfds, int timeout)
//Poll機制會判斷fds中的文件是否可讀,如果可讀則會立即返回,返回的值就是可讀fd的數量,如果不可讀,那麼就進程就會
休眠timeout這麼長的時間,然後再來判斷是否有文件可讀,如果有,返回fd的數量,如果沒有,則返回0.  
1.2 poll機制在內核實現分析:
應用程式調用poll
    sys_poll
        do_sys_poll
            poll_initwait(&table)//table->pt->qproc = qproc = __pollwait
            do_poll(nfds, head, &table, timeout)
                for (;;) {
                if (do_pollfd(pfd, pt)) {     //return mask;mask=file->f_op->poll(file,pwait)
                    count++;
                    pt = NULL;
                    }
                if (count || !*timeout || signal_pending(current))
                        break;//break的條件是:count非0,超時,有信號在等待處理
                    __timeout = schedule_timeout(__timeout)//休眠
                    }
1.3 總結
①poll > sys_poll > do_sys_poll >poll_initwait,poll_initwait函數註冊一下回調函數__pollwait,它就是我們的驅動程式執行poll_wait時,真正被調用的函數。
②接下來執行file->f_op->poll,即我們驅動程式里自己實現的poll函數它會調用poll_wait把自己掛入某個隊列,這個隊列也是我們的驅動自己定義的;它還判斷一下設備是否就緒。
③如果設備未就緒,do_sys_poll里會讓進程休眠一定時間,這個時間是應用提供的“超時時間”
④進程被喚醒的條件有2:一是上面說的“一定時間”到了,二是被驅動程式喚醒。驅動程式發現條件就緒時,就把“某個隊列”上掛著的進程喚醒,這個隊列,就是前面通過poll_wait把本進程掛過去的隊列。
⑤如果驅動程式沒有去喚醒進程,那麼schedule_timeout(__timeout)超時後,會重覆2、3動作1
次,然後返回。

 2.寫代碼
①在測試程式中加入以下代碼
while (1)
    {
        ret = poll(fds, 1, 5000);
        if (ret == 0)
        {
            printf("time out\n");
        }
int main()
{
    int ret;
    struct pollfd fds[1]
}
②在file_operations中增加一行
.poll    =  forth_drv_poll
③寫函數forth_drv_poll
forth_drv_poll
    poll_wait
        p->qproc(file,wait_address,p)//相當於調用了__pollwait(file,&button_waitq,p)
                                        //把當前進程掛到button_waitq隊列里去
④在增加如下代碼
if (ev_press)
        mask |= POLLIN | POLLRDNORM;

  1 /*
  2   *使用poll 機制實現按鍵驅動程式
  3   */
  4 
  5 #include <linux/module.h>
  6 #include <linux/kernel.h>
  7 #include <linux/fs.h>
  8 #include <linux/init.h>
  9 #include <linux/delay.h>
 10 #include <linux/irq.h>
 11 #include <asm/uaccess.h>
 12 #include <asm/irq.h>
 13 #include <asm/io.h>
 14 #include <asm/arch/regs-gpio.h>
 15 #include <asm/hardware.h>
 16 
 17 
 18 static struct class *g_ptForthdrvCls;
 19 static struct class_device *g_ptForthdrvClsDev;
 20 
 21 volatile unsigned long *gpfcon;
 22 volatile unsigned long *gpfdat;
 23 
 24 volatile unsigned long *gpgcon;
 25 volatile unsigned long *gpgdat;
 26 
 27 static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
 28 
 29 /* 中斷事件標誌, 中斷服務程式將它置1,forth_drv_read將它清0 */
 30 static volatile int ev_press = 0;
 31 
 32 struct pin_desc{
 33     unsigned int pin;
 34     unsigned int key_val;
 35 };
 36 
 37 static unsigned char key_val;
 38 
 39 struct pin_desc pins_desc[4] = {
 40     {S3C2410_GPF0, 0x01},
 41     {S3C2410_GPF2, 0x02},
 42     {S3C2410_GPG3, 0x03},
 43     {S3C2410_GPG11, 0x04},
 44 };
 45 
 46 
 47 /*
 48   * 確定按鍵值
 49   */
 50 static irqreturn_t buttons_irq(int irq, void *dev_id)
 51 {
 52     struct pin_desc * pindesc = (struct pin_desc *)dev_id;
 53     unsigned int pinval;
 54     
 55     pinval = s3c2410_gpio_getpin(pindesc->pin);
 56 
 57     if (pinval)
 58     {
 59         /* 鬆開 */
 60         key_val = 0x80 | pindesc->key_val;
 61     }
 62     else
 63     {
 64         /* 按下 */
 65         key_val = pindesc->key_val;
 66     }
 67 
 68     ev_press = 1;                  /* 表示中斷發生了 */
 69     wake_up_interruptible(&button_waitq);   /* 喚醒休眠的進程 */
 70 
 71     
 72     return IRQ_RETVAL(IRQ_HANDLED);
 73 }
 74 
 75 static int forthdrv_open(struct inode *inode, struct file *file)
 76 {
 77     request_irq(IRQ_EINT0,  buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
 78     request_irq(IRQ_EINT2,  buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
 79     request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2	   

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

-Advertisement-
Play Games
更多相關文章
  • Navigation bar 的註意事項 Bar button item 使用 button 作為 custom view,初始化 isEnabled 為 false,註意順序 需要設置 bar button item 的 custom view 為 button,但一開始 isEnabled 要為 ...
  • 自增和自減運算 自增和自減運算 比較運算符與比較表達式 比較運算符與比較表達式 邏輯運算符與邏輯表達式 邏輯運算符與邏輯表達式 IF結構 IF結構 變數的作用域 變數的作用域 ...
  • 當應用伺服器受到攻擊,我們採取的措施大致分為以下幾個步驟: 1、伺服器隔離 檢查埠,禁掉網卡 2、修改賬號、密碼,防火牆策略等 3、殺毒 4、應用重新部署 當然,這些工作大部分是由伺服器維護人員來做,對於我們開發人員來說,也是可以貢獻自己一份力量的, 比如部署應用配置項的時候資料庫連接採用加密手段 ...
  • twemproxy概述 twemproxy是搭建分散式緩存集群的重要組件之一。他能將來自客戶端的redis包通過key分片發送到不同的redis伺服器,而不是發到單個redis伺服器上。因此,可以使本來集中到一個redis上的信息被分流到幾個redis上,這就使得 twemproxy能支持redis ...
  • 本文地址 分享提綱: 1. 概述 2. 詳解配置文件 3. 詳解日誌 1.概述 MySQL配置文件在Windows下叫my.ini,在MySQL的安裝根目錄下;在Linux下叫my.cnf,該文件位於/etc/my.cnf。 2. 詳解配置文件 basedir = path 使用給定目錄作為根目錄( ...
  • MySQL 約束 作用:保證數據的完整性和一致性按照約束的作用範圍分為:表級約束和行級約束。常見的約束類型包括: Not null(非空約束) Primary key (主鍵約束) Unique key(唯一約束) Default (預設約束) foreign key(外鍵約束) 外鍵約束 1.父表 ...
  • 最近在學習linux,在某個用戶(xxx)下使用sudo的時候,提示以下錯誤:xxx is not in the sudoers file. This incident will be reported。 百度了下,究其原因是用戶沒有加入到sudo的配置文件里。 解決方法如下: 1、切換到root用 ...
  • 第八節 Linux 文件的屬性(下半部分) 標簽(空格分隔): Linux教學筆記 [更多相關資料請點我查看][1] 第1章 鏈接的概念 在linux系統中,鏈接可分為兩種:一種為硬鏈接(Hard Link),另一種為軟連接或符號鏈接(Symbolic Link or Soft link)。我們在前 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...