u-boot-1.1.6環境變數

来源:https://www.cnblogs.com/053179hu/archive/2019/03/29/10619313.html
-Advertisement-
Play Games

學習目標: 1、分析u-boot-1.1.6環境變數,瞭解環境變數初始化、設置以及過程 2、為後面能夠掌握u-boot-1.1.6如何啟動內核過程打下基礎 1、環境變數的概念 在分析uboot環境變數的源碼實現之前,先介紹一下環境變數的概念。u-boot通過環境變數為用戶提供一定程度的可配置性,在不 ...


學習目標:

1、分析u-boot-1.1.6環境變數,瞭解環境變數初始化、設置以及過程

2、為後面能夠掌握u-boot-1.1.6如何啟動內核過程打下基礎


1、環境變數的概念

在分析uboot環境變數的源碼實現之前,先介紹一下環境變數的概念。u-boot通過環境變數為用戶提供一定程度的可配置性,在不改變源碼、不重新編譯的情況下,可以通過設置環境變數的值來改變uboot的一些設置,如bootdelay時間,內核啟動命令參數等。可配置性意味著環境變數是可以添加、刪除和修改的,也就是說環境變數的內容可能會頻繁變化,為了不讓這種變化對u-boot的代碼和數據造成破壞,通常的選擇是在FLASH中預留一個專門用來存儲環境變數的塊。開發者在串口終端輸入setenv命令可以設置環境量值,設置完成後使用saveenv命令將setenv命令設置好的環境變數保存在非易失存儲器中。如下圖所示,使用uboot時, 在串口終端輸入printenv命令便能夠列印uboot中的環境變數值。

2、環境變數的數據結構

#define ENV_SIZE (CFG_ENV_SIZE - ENV_HEADER_SIZE)

typedef    struct environment_s {
    unsigned long    crc;        /* CRC32 over data bytes    */
#ifdef CFG_REDUNDAND_ENVIRONMENT
    unsigned char    flags;        /* active/obsolete flags    */
#endif
    unsigned char    data[ENV_SIZE]; /* Environment data        */
} env_t;

crc變數保存上一次環境變數數據寫入flash時做crc運算的結果,當重新從flash中讀取環境變數值時,代碼會對讀取數據進行crc校驗,並將新的校驗的值與上次保存的值進行比較,如果兩次crc校驗值相同,表示存放在flash中的環境變數正常,否則,表示存放環境變數數據損壞。

數組data[ENV_SIZE]保存環境變數的值,環境變數名已經環境變數值以字元形式存放在記憶體中

3、環境變數的初始化

int  env_init(void)
{
/* 未定義CONFIG_OMAP2420H4,此處代碼不被編譯 */
#ifdef CONFIG_OMAP2420H4
    int flash_probe(void);

    if(flash_probe() == 0)
        goto bad_flash;
#endif
    /* env_t *env_ptr = (env_t *)CFG_ENV_ADDR, 此處執行嗎?,如果此處執行 */
    /* 先進入nor flash讀取環境變數,通過CRC檢測讀取環境變數是否正確 */
    if (crc32(0, env_ptr->data, ENV_SIZE) == env_ptr->crc) {
        gd->env_addr  = (ulong)&(env_ptr->data);
        gd->env_valid = 1;
        return(0);
    }
/* 未定義CONFIG_OMAP2420H4,此處代碼不被編譯 */
#ifdef CONFIG_OMAP2420H4
bad_flash:
#endif    
    /* CRC校驗後讀取環境變數不正確,此處執行,使用預設環境變數 */
    gd->env_addr  = (ulong)&default_environment[0];
    gd->env_valid = 0;
    return (0);
}

int env_inti(void)函數在uboot代碼第二階段入口函數start_armboot開始處被調用。首先,env_init函數從存放環境變數的flash記憶體地址中讀取環境變數的值,並且對讀出的環境變數數據進行crc校驗,將新校驗的crc值與寫入環境變數時的crc校驗值進行比較。如果兩個值相等,將讀取數據的地址賦值給gd指針執行的全局數據結構中的env_addr成員,並將env_valid標誌位置位。如果讀取環境變數校驗值和寫入時的校驗值不同,存儲在flash中的環境變數值損壞,將預設環境變數值賦給gd指針執行的全局數據結構中的env_addr成員。

env_ptr指針指向環境變數存放初始地址,env_t *env_ptr = (env_t *)CFG_ENV_ADDR。CFG_ENV_ADDR是一個巨集,代表環境變數在flash中存放的地址,由於我配置uboot時目標板是smdk2410,所以該巨集存放在include/configs/smdk2410.h頭文件中。預設環境變數值如下所示:

uchar default_environment[] = {
#ifdef    CONFIG_BOOTARGS
    "bootargs="    CONFIG_BOOTARGS            "\0"
#endif
#ifdef    CONFIG_BOOTCOMMAND
    "bootcmd="    CONFIG_BOOTCOMMAND        "\0"
#endif
#ifdef    CONFIG_RAMBOOTCOMMAND
    "ramboot="    CONFIG_RAMBOOTCOMMAND        "\0"
#endif
#ifdef    CONFIG_NFSBOOTCOMMAND
    "nfsboot="    CONFIG_NFSBOOTCOMMAND        "\0"
#endif
#if defined(CONFIG_BOOTDELAY) && (CONFIG_BOOTDELAY >= 0)
    "bootdelay="    MK_STR(CONFIG_BOOTDELAY)    "\0"
#endif
#if defined(CONFIG_BAUDRATE) && (CONFIG_BAUDRATE >= 0)
    "baudrate="    MK_STR(CONFIG_BAUDRATE)        "\0"
#endif
#ifdef    CONFIG_LOADS_ECHO
    "loads_echo="    MK_STR(CONFIG_LOADS_ECHO)    "\0"
#endif
#ifdef    CONFIG_ETHADDR
    "ethaddr="    MK_STR(CONFIG_ETHADDR)        "\0"
#endif
#ifdef    CONFIG_ETH1ADDR
    "eth1addr="    MK_STR(CONFIG_ETH1ADDR)        "\0"
#endif
#ifdef    CONFIG_ETH2ADDR
    "eth2addr="    MK_STR(CONFIG_ETH2ADDR)        "\0"
#endif
#ifdef    CONFIG_ETH3ADDR
    "eth3addr="    MK_STR(CONFIG_ETH3ADDR)        "\0"
#endif
#ifdef    CONFIG_IPADDR
    "ipaddr="    MK_STR(CONFIG_IPADDR)        "\0"
#endif
#ifdef    CONFIG_SERVERIP
    "serverip="    MK_STR(CONFIG_SERVERIP)        "\0"
#endif
#ifdef    CFG_AUTOLOAD
    "autoload="    CFG_AUTOLOAD            "\0"
#endif
#ifdef    CONFIG_PREBOOT
    "preboot="    CONFIG_PREBOOT            "\0"
#endif
#ifdef    CONFIG_ROOTPATH
    "rootpath="    MK_STR(CONFIG_ROOTPATH)        "\0"
#endif
#ifdef    CONFIG_GATEWAYIP
    "gatewayip="    MK_STR(CONFIG_GATEWAYIP)    "\0"
#endif
#ifdef    CONFIG_NETMASK
    "netmask="    MK_STR(CONFIG_NETMASK)        "\0"
#endif
#ifdef    CONFIG_HOSTNAME
    "hostname="    MK_STR(CONFIG_HOSTNAME)        "\0"
#endif
#ifdef    CONFIG_BOOTFILE
    "bootfile="    MK_STR(CONFIG_BOOTFILE)        "\0"
#endif
#ifdef    CONFIG_LOADADDR
    "loadaddr="    MK_STR(CONFIG_LOADADDR)        "\0"
#endif
#ifdef  CONFIG_CLOCKS_IN_MHZ
    "clocks_in_mhz=1\0"
#endif
#if defined(CONFIG_PCI_BOOTDELAY) && (CONFIG_PCI_BOOTDELAY > 0)
    "pcidelay="    MK_STR(CONFIG_PCI_BOOTDELAY)    "\0"
#endif
#ifdef  CONFIG_EXTRA_ENV_SETTINGS
    CONFIG_EXTRA_ENV_SETTINGS
#endif
    "\0"
};

4、環境變數的重定位

env_inti函數在初始化時對環境變數保存位置進行識別,並將環境變數存放的初始地址賦值給了全局數據結構gt_t成員env_addr。讀取存放在flash中的環境變數數據,進行crc校驗後有效,便使用flash中的環境變數,否則使用預設環境變數。我們知道flash操作速度記憶體慢(若環境變數存放在nand flash中,進行讀取時還需要相應的指令),對flash中環境變數進行直接讀寫不僅會影響uboot性能,而且多次擦除還會影響flash的使用壽命,因此,需要將flash中的環境變數進行重定位操作。

void env_relocate (void)
{
    DEBUGF ("%s[%d] offset = 0x%lx\n", __FUNCTION__,__LINE__,
        gd->reloc_off);

/* 未定義CONFIG_AMIGAONEG3SE巨集,此處不被編譯 */
#ifdef CONFIG_AMIGAONEG3SE
    enable_nvram();
#endif

/* 未定義ENV_IS_EMBEDDED巨集,此處不被編譯 */
#ifdef ENV_IS_EMBEDDED
    /*
     * The environment buffer is embedded with the text segment,
     * just relocate the environment pointer
     */
    env_ptr = (env_t *)((ulong)env_ptr + gd->reloc_off);
    DEBUGF ("%s[%d] embedded ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
/* ifdef語句為假,此處被編譯 */
#else
    /*
     * We must allocate a buffer for the environment
     */
    /* 在malloc區,為環境變數分配存儲空間 */
    env_ptr = (env_t *)malloc (CFG_ENV_SIZE);
    DEBUGF ("%s[%d] malloced ENV at %p\n", __FUNCTION__,__LINE__,env_ptr);
#endif

    /*
     * After relocation to RAM, we can always use the "memory" functions
     */
    env_get_char = env_get_char_memory;

    if (gd->env_valid == 0) {
#if defined(CONFIG_GTH)    || defined(CFG_ENV_IS_NOWHERE)    /* Environment not changable */
        puts ("Using default environment\n\n");
#else
        puts ("*** Warning - bad CRC, using default environment\n\n");
        SHOW_BOOT_PROGRESS (-1);
#endif

        if (sizeof(default_environment) > ENV_SIZE)
        {
            puts ("*** Error - default environment is too large\n\n");
            return;
        }

        memset (env_ptr, 0, sizeof(env_t));
        memcpy (env_ptr->data,
            default_environment,
            sizeof(default_environment));
#ifdef CFG_REDUNDAND_ENVIRONMENT
        env_ptr->flags = 0xFF;
#endif
        env_crc_update ();
        gd->env_valid = 1;
    }
    else {
        env_relocate_spec ();
    }
    gd->env_addr = (ulong)&(env_ptr->data);
/* 未定義CONFIG_AMIGAONEG3SE巨集,此處代碼不被編譯 */
#ifdef CONFIG_AMIGAONEG3SE
    disable_nvram();
#endif
}

void env_relocate (void)函數功能是對flash中環境變數的數據進行重定位,首先在uboot自定義的堆區為環境變數分配相應的記憶體空間,env_ptr指針指向這塊記憶體的初始地址,然後根據gd->env_valid值執行不同的操作(gd->env_valid在env_init函數中設置)。gd->env_valid=0代表flash中環境變數無效,使用預設環境變數,將env_ptr指針指向的記憶體地址清零,再將存放預設環境變數default_environment數組內容拷貝到env_ptr指針指向的記憶體空間中;gd->env_valid=1時flash中存放環境變數有效,調用env_relocate_spec ()函數,將flash中存放的環境變數數據複製到env_ptr指針指向的記憶體空間。最後,將環境變數中的數據在重定位記憶體中的地址賦給gd->env_addr。

5、讀取環境變數

前面介紹過在串口終端輸入printenv,執行回車後,列印環境變數值。執行printenv命令其實底層調用的是do_printenv函數,這個函數源碼如下:

int do_printenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    int i, j, k, nxt;
    int rcode = 0;

    if (argc == 1) {        /* 列印所有的環境變數    */
        for (i=0; env_get_char(i) != '\0'; i=nxt+1) {
            for (nxt=i; env_get_char(nxt) != '\0'; ++nxt)
                ;
            for (k=i; k<nxt; ++k)
                putc(env_get_char(k));
            putc  ('\n');

            if (ctrlc()) {
                puts ("\n ** Abort\n");
                return 1;
            }
        }

        printf("\nEnvironment size: %d/%d bytes\n", i, ENV_SIZE);

        return 0;
    }
  /* 遍歷輸入的參數,分別列印其環境變數值 */
    for (i=1; i<argc; ++i) {    /* print single env variables    */
        char *name = argv[i];

        k = -1;
     
        for (j=0; env_get_char(j) != '\0'; j=nxt+1) {

            for (nxt=j; env_get_char(nxt) != '\0'; ++nxt)  /* uchar (*env_get_char)(int) = env_get_char_init,env_get_char_init函數獲取相對環境變數記憶體起始地址偏移nxt長度字元 */
                ;
            k = envmatch((uchar *)name, j);  /* 檢測輸入參數名和記憶體中環境變數參數名是否匹配 */
            if (k < 0) {
                continue;
            }
            puts (name);
            putc ('=');
            while (k < nxt)
                putc(env_get_char(k++));
            putc ('\n');
            break;
        }
        if (k < 0) {
            printf ("## Error: \"%s\" not defined\n", name);
            rcode ++;
        }
    }
    return rcode;
}

1、當argc==1時(例如執行printenv命令,argc=1),列印uboot所有的環境變數

2、當argc>1時(例如執行printenv bootcmd baudrate命令,argc=3),列印環境變數bootcmd、baudrate

6、設置環境變數

同讀取環境變數一樣,執行setenv命令其實底層調用的是_do_setenv函數,這個函數源碼如下:

int do_setenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    if (argc < 2) {
        printf ("Usage:\n%s\n", cmdtp->usage);
        return 1;
    }

    return _do_setenv
(flag, argc, argv); } int _do_setenv (int flag, int argc, char *argv[]) { int i, len, oldval; int console = -1; uchar *env, *nxt = NULL; char *name; bd_t *bd = gd->bd;
  /* env_data獲取環境變數的數據部分在記憶體的初始地址 */ uchar
*env_data = env_get_addr(0); if (!env_data) /* need copy in RAM */ return 1; name = argv[1];   
  /* 檢測輸入的第二個參數為'=',列印相關錯誤信息 */
if (strchr(name, '=')) { printf ("## Error: illegal character '=' in variable name \"%s\"\n", name); return 1; }
  /* 遍歷記憶體中的環境變數數據數組,檢測配置環境變數名是否存在 */
oldval = -1; for (env=env_data; *env; env=nxt+1) { for (nxt=env; *nxt; ++nxt) ; if ((oldval = envmatch((uchar *)name, env-env_data)) >= 0) break; } /* * Delete any existing definition */ if (oldval >= 0) { #ifndef CONFIG_ENV_OVERWRITE
/* 網卡地址和串口號是固定的,如果設置是網卡地址和串口號,列印"Can't overwrite \"%s\"\n", name,代碼返回 */
if ( (strcmp (name, "serial#") == 0) || ((strcmp (name, "ethaddr") == 0) #if defined(CONFIG_OVERWRITE_ETHADDR_ONCE) && defined(CONFIG_ETHADDR) && (strcmp ((char *)env_get_addr(oldval),MK_STR(CONFIG_ETHADDR)) != 0) #endif /* CONFIG_OVERWRITE_ETHADDR_ONCE && CONFIG_ETHADDR */ ) ) { printf ("Can't overwrite \"%s\"\n", name); return 1; } #endif /* Check for console redirection */ if (strcmp(name,"stdin") == 0) { console = stdin; } else if (strcmp(name,"stdout") == 0) { console = stdout; } else if (strcmp(name,"stderr") == 0) { console = stderr; } if (console != -1) { if (argc < 3) { /* Cannot delete it! */ printf("Can't delete \"%s\"\n", name); return 1; } /* Try assigning specified device */ if (console_assign (console, argv[2]) < 0) return 1; #ifdef CONFIG_SERIAL_MULTI if (serial_assign (argv[2]) < 0) return 1; #endif } /* 如果是參數=波特率,重新設置相應波特率值 */ if (strcmp(argv[1],"baudrate") == 0) { int baudrate = simple_strtoul(argv[2], NULL, 10); int i;
       /* 檢測輸入波特率值是否合法,如果不合法,列印錯誤,返回函數 */
for (i=0; i<N_BAUDRATES; ++i) { if (baudrate == baudrate_table[i]) break; } if (i == N_BAUDRATES) { printf ("## Baudrate %d bps not supported\n", baudrate); return 1; } printf ("## Switch baudrate to %d bps and press ENTER ...\n", baudrate); udelay(50000); gd->baudrate = baudrate; #ifdef CONFIG_PPC gd->bd->bi_baudrate = baudrate; #endif serial_setbrg (); udelay(50000); for (;;) { if (getc() == '\r') break; } } if (*++nxt == '\0') { if (env > env_data) { env--; } else { *env = '\0'; } } else { for (;;) { *env = *nxt++; if ((*env == '\0') && (*nxt == '\0')) break; ++env; } } *++env = '\0'; } #ifdef CONFIG_NET_MULTI if (strncmp(name, "eth", 3) == 0) { char *end; int num = simple_strtoul(name+3, &end, 10); if (strcmp(end, "addr") == 0) { eth_set_enetaddr(num, argv[2]); } } #endif /* Delete only ? */ if ((argc < 3) || argv[2] == NULL) { env_crc_update (); return 0; } /* * Append new definition at the end */ for (env=env_data; *env || *(env+1); ++env) ; if (env > env_data) ++env; /* * Overflow when: * "name" + "=" + "val" +"\0\0" > ENV_SIZE - (env-env_data) */ len = strlen(name) + 2; /* add '=' for first arg, ' ' for all others */ for (i=2; i<argc; ++i) { len += strlen(argv[i]) + 1; } if (len > (&env_data[ENV_SIZE]-env)) { printf ("## Error: environment overflow, \"%s\" deleted\n", name); return 1; } while ((*env = *name++) != '\0') env++; for (i=2; i<argc; ++i) { char *val = argv[i]; *env = (i==2) ? '=' : ' '; while ((*++env = *val++) != '\0')  /* 將設置的新的參數寫入到重定位的環境變數數據中 */ ; } /* end is marked with double '\0' */ *++env = '\0'; /* Update CRC */ env_crc_update ();   /* 如果設置環境變數是ethaddr、ipaddr、loadaddr、bootfile、vga_fg_color 根據新的環境值,立即更新相應全局變數、更新底層硬體 */ if (strcmp(argv[1],"ethaddr") == 0) { char *s = argv[2]; /* always use only one arg */ char *e; for (i=0; i<6; ++i) { bd->bi_enetaddr[i] = s ? simple_strtoul(s, &e, 16) : 0; if (s) s = (*e) ? e+1 : e; } #ifdef CONFIG_NET_MULTI eth_set_enetaddr(0, argv[2]); #endif return 0; } if (strcmp(argv[1],"ipaddr") == 0) { char *s = argv[2]; /* always use only one arg */ char *e; unsigned long addr; bd->bi_ip_addr = 0; for (addr=0, i=0; i<4; ++i) { ulong val = s ? simple_strtoul(s, &e, 10) : 0; addr <<= 8; addr |= (val & 0xFF); if (s) s = (*e) ? e+1 : e; } bd->bi_ip_addr = htonl(addr); return 0; } if (strcmp(argv[1],"loadaddr") == 0) { load_addr = simple_strtoul(argv[2], NULL, 16); return 0; } #if (CONFIG_COMMANDS & CFG_CMD_NET) if (strcmp(argv[1],"bootfile") == 0) { copy_filename (BootFile, argv[2], sizeof(BootFile)); return 0; } #endif /* CFG_CMD_NET */ #ifdef CONFIG_AMIGAONEG3SE if (strcmp(argv[1], "vga_fg_color") == 0 || strcmp(argv[1], "vga_bg_color") == 0 ) { extern void video_set_color(unsigned char attr); extern unsigned char video_get_attr(void); video_set_color(video_get_attr()); return 0; } #endif /* CONFIG_AMIGAONEG3SE */ return 0; }

7、保存設置的環境變數

串口輸入setenv命令,系統是將新的環境變數保存到重定位的記憶體中,我們知道記憶體在系統掉電後數據丟失,如果想要將設置的環境變數永久保存,還是要將新的環境變數寫入到存儲環境變數的flash地址中。執行saveenv命令就是將分配記憶體中的環境變數保存到flash中,執行saveenv命令其實底層調用的是do_printenv函數,這個函數源碼如下:

int do_saveenv (cmd_tbl_t *cmdtp, int flag, int argc, char *argv[])
{
    extern char * env_name_spec;

    printf ("Saving Environment to %s...\n", env_name_spec);

    return (saveenv() ? 1 : 0);
}

/* 由於saveenv函數過長,下麵刪去不被包含的巨集內的代碼 */
int saveenv(void) { int len, rc; ulong end_addr; ulong flash_sect_addr; uchar *env_buffer = (uchar *)env_ptr; int rcode = 0;
  /*
static env_t *flash_addr = (env_t *)CFG_ENV_ADDR; */ flash_sect_addr = (ulong)flash_addr; /* flash_sect_addr 保持環境變數在flash中起始地址 */ len = CFG_ENV_SIZE; end_addr = flash_sect_addr + 0x20000 - 1; debug ("Protect off %08lX ... %08lX\n", (ulong)flash_sect_addr, end_addr); if (flash_sect_protect (0, flash_sect_addr, end_addr)) return 1; puts ("Erasing Flash..."); if (flash_sect_erase (flash_sect_addr, end_addr)) return 1; puts ("Writing to Flash... "); rc = flash_write((char *)env_buffer, flash_sect_addr, len); if (rc != 0) { flash_perror (rc); rcode = 1; } else { puts ("done\n"); } /* try to re-protect */ (void) flash_sect_protect (1, flash_sect_addr, end_addr); return rcode; }

1、env_buffer指針存放環境變數在記憶體中的初始位置

2、寫入flash之前應該擦除想要寫入的扇區,調用flash_sect_erase函數擦除環境變數所做flash的扇區

3、再調用flash_write,寫入新的環境變數


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

-Advertisement-
Play Games
更多相關文章
  • 1、複製下列代碼,拷貝到控制器中。 2、複製以下圖片標記到視圖中 3、驗證方式,使用 Session["SecurityCode"] 判斷填寫的是否相等,txtCode自己輸入的驗證碼。 ...
  • 前期準備 [1]註冊nuget官網賬號;註冊地址:https://www.nuget.org/ [2]下載Nuget.exe文件;下載地址:https://www.nuget.org/downloads [3]將Nuget.exe所在路徑配置到系統環境變數,控制面板\所有控制面板項\系統,單擊高級系 ...
  • 背景 前兩天看見有小哥介紹windows下安裝skywalking的介紹 "地址在這" 。 正好最近也在搭建linux環境的SkyWalking,順便把linux環境搭建的經驗分享下,幫助下使用linux部署DotNetCore項目的同學。 介紹 SkyWalking是開源的apm工具,伺服器端使用 ...
  • 5、Ubuntu安裝好之後,就可以進行開發環境的搭建。(堅持看完有彩蛋,(>--..--<).jpg)。 1)首先安裝Nodejs和Npm。 打開瀏覽器輸入nodejs.org,進入頁面會提示下載,如下圖,選擇LTS(Long Term Support,長期支持版)版本,點擊下載; 2)下載完成後, ...
  • "用openssl為WEB伺服器生成證書(自簽名CA證書,伺服器證書)" 來源: "https://www.cnblogs.com/osnosn/p/10608455.html" 來自 "osnosn的博客" 寫於: 2019 03 28. 不想用自簽名證書,想在網上申請一個免費伺服器證書,見這篇: ...
  • 使用的Linux發行版本為Redhat 1. "Linux(RedHat)基礎學習—命令行使用入門" 2. "Linux(RedHat)基礎學習—文件定址與管理" 3. "Linux(RadHat)基礎學習—vim編輯器及文件的輸入輸出" 4. "Linux(RadHat)基礎學習—用戶管理" 5. ...
  • 學習目標: 1、瞭解u-boot-1.1.6中命令的實現機制 2、掌握如何在u-boot-1.1.6中添加自定義命令 1、命令的實現機制 uboot運行在命令行解析模式時,在串口終端輸入uboot命令,按下回車後,系統將執行命令的相應操作。以help命令為例,當輸入help命令,並按下回車時,串口終 ...
  • 最近公司的小程式因為高峰期訪問緩慢的問題,打算用負載均衡試試。本人是個新手,在網上找了幾篇負載均衡的文章看了看,最後還是用了寶塔面板的負載均衡插件...這個伺服器我也是剛剛接手,很多東西都是以前的同事做的,所以做些東西總是磕磕碰碰的。 問題1:創建負載均衡時不能用已經綁定的網站,必須新添加一個,但是 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...