新聞中心
在Linux C編程中,ntohl函數是網絡字節(jié)序和主機字節(jié)序之間轉換的重要函數。由于網絡中傳輸的數據在通信過程中可能會出現字節(jié)序不一致的情況,因此需要通過ntohl函數將字節(jié)序進行轉換,從而保證數據的正確傳輸和處理。本文將深入剖析ntohl函數的使用、作用原理及其在網絡編程中的應用。

10年的無為網站建設經驗,針對設計、前端、開發(fā)、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。全網營銷推廣的優(yōu)勢是能夠根據用戶設備顯示端的尺寸不同,自動調整無為建站的顯示方式,使網站能夠適用不同顯示終端,在瀏覽器中調整網站的寬度,無論在任何一種瀏覽器上瀏覽網站,都能展現優(yōu)雅布局與設計,從而大程度地提升瀏覽體驗。成都創(chuàng)新互聯公司從事“無為網站設計”,“無為網站推廣”以來,每個客戶項目都認真落實執(zhí)行。
一、ntohl函數的使用
ntohl函數是Linux系統(tǒng)中的網絡字節(jié)序轉換函數之一,其函數原型如下:
uint32_t ntohl(uint32_t netlong);
參數netlong為需要轉換的32位無符號整型數據,函數返回值為轉換后的32位無符號整型數據。ntohl函數會將參數netlong從網絡字節(jié)序轉換為主機字節(jié)序,通常情況下,主機字節(jié)序是指大端字節(jié)序(MSB在前,LSB在后)。
在網絡編程中,ntohl函數通常和htons、ntohs、htonl等函數搭配使用,用于對傳輸的數據進行字節(jié)序的轉換。比如,如果需要將一個32位整數在網絡中傳輸,需要把它轉換為網絡字節(jié)序,使用htons函數可以將它轉換為16位網絡字節(jié)序,使用htonl函數可以將它轉換為32位網絡字節(jié)序,傳輸過程中再通過ntohl/ntohs函數將它轉換為主機字節(jié)序。
二、ntohl函數的作用原理
在計算機中,數據在存儲時以字節(jié)為單位進行存儲,不同的計算機硬件架構決定了不同的字節(jié)序。大端字節(jié)序(MSB在前,LSB在后)也稱為網絡字節(jié)序,小端字節(jié)序(LSB在前,MSB在后)也稱為主機字節(jié)序。不同的系統(tǒng)(Windows、Linux、Mac OS等)具有不同的字節(jié)序,因此在網絡通信中,需要將不同字節(jié)序的數據進行轉換,以保證數據的正確傳輸和處理。
ntohl函數的作用就是將32位無符號整型數據從網絡字節(jié)序轉換為主機字節(jié)序,其轉換原理如下:
假設我們要對一個32位無符號整型值0x12345678進行字節(jié)序轉換,這個值的二進制表示為
00010010 00110100 01010110 01111000
– 在大端字節(jié)序的計算機中,該值按照從高到低的順序存儲,因此它在計算機中的表示為0x12345678,在網絡中的表示也應該為0x12345678。
– 在小端字節(jié)序的計算機中,該值按照從低到高的順序存儲,因此它在計算機中的表示為0x78563412,在網絡中的表示應該為0x78563412。
– 當數據從小端字節(jié)序計算機發(fā)送到大端字節(jié)序計算機時,需要把該值轉換為大端字節(jié)序,先將低位字節(jié)0x78放在之一個字節(jié),高位字節(jié)0x12放在第四個字節(jié),中間兩個字節(jié)以此類推,得到的結果為0x78563412。然后接收方可以根據該值的字節(jié)序將它轉換為主機字節(jié)序。
– 當數據從大端字節(jié)序計算機發(fā)送到小端字節(jié)序計算機時,需要把該值轉換為小端字節(jié)序,方法與上述相仿。
ntohl函數的作用就是將參數netlong從網絡字節(jié)序轉換為主機字節(jié)序,其實現原理與上述類似。當ntohl函數在大端字節(jié)序計算機中執(zhí)行時,該函數不做任何轉換,直接將參數netlong返回;當ntohl函數在小端字節(jié)序計算機中執(zhí)行時,該函數將參數netlong的字節(jié)序進行轉換,然后將轉換結果返回。
三、ntohl函數在網絡編程中的應用
在網絡編程中,ntohl函數通常和htonl、htons、ntohs等函數搭配使用,用于對傳輸的數據進行字節(jié)序的轉換。以下是ntohl函數在網絡編程中的應用示例:
– 客戶端向服務器發(fā)送一個32位整數數據:
uint32_t data = 0x12345678;
data = htonl(data); // 將數據轉換為網絡字節(jié)序
send(sockfd, &data, sizeof(data), 0); // 通過網絡發(fā)送數據
– 服務器接收到客戶端發(fā)送的數據后進行處理:
uint32_t data;
recv(sockfd, &data, sizeof(data), 0); // 從網絡接收數據
data = ntohl(data); // 將數據從網絡字節(jié)序轉換為主機字節(jié)序
// 對數據進行處理
通過使用ntohl函數,程序可以輕松地對傳輸的數據進行字節(jié)序轉換,使得數據在不同的計算機之間正確傳輸和處理,在網絡編程中具有重要的實用價值。
結語
本文深入剖析了Linux C編程中的ntohl函數的用法、作用原理及其在網絡編程中的應用。我們了解了ntohl函數的轉換原理,了解了網絡通訊中字節(jié)序轉換的必要性,通過本文,我們可以更好地掌握ntohl函數的使用技巧,并在實際編程中更加熟練地運用它。
相關問題拓展閱讀:
- linux little endian和big endian是什么意思
- Ubuntu編譯了新的內核,進入新內核時一直顯示載入Linux 5.6.7,載入初始化內存盤咋回事?
linux little endian和big endian是什么意思
1.故事的起源 “endian”這個詞出自《格列佛游記》。小人國的內戰(zhàn)就源于吃雞蛋時是究竟從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開,由此曾發(fā)生過六次叛亂,其中一個皇帝送了命,另一個丟了王位。 我們一般將endian翻譯成“字節(jié)序”,將big endian和little endian稱作“大尾”和“小尾”。 2.什么是Big Endian和Little Endian? 在設計計算伏辯機系統(tǒng)的時候,有兩種處理內存中數據的方法。一種叫為little-endian,存放在內存中更低位的數值是來自數據的最右邊部分(也就是數據的更低位部分)。 比如某些文件需要在不同平臺處理,或者通過Socket通信。這方面我們可以借助ntohl(), ntohs(), htonl(), and htons()函數進行格式轉換, 個人補充:一個操作數作htonl或ntohl結果不一定相同,當機器字節(jié)序跟網絡字節(jié)序剛好是僅僅big endian和little endian的區(qū)別時是相同的。 3. 如何理解Big Endian和Little Endian 舉個例子: int a = 1; a這個數本身的16進制表示是0x在內存中怎么存儲呢? 如果你的CPU是intel x86架構的(基本上就是通常我們說的奔廳寬騰cpu),那么就是0x01 0x00 0x00 0x00 , 這也就是所謂扮廳亮的little-endian, 低字節(jié)存放在內存的低位. 如果你的CPU是老式AMD系列的(很老很老的那種,因為最新的AMD系列已經是x86架構了), 它的字節(jié)序就是big-endian, 其內存存儲就是 0x00 0x00 0x00 0x01在內存中從高字節(jié)開始存放。 現在世界上絕大多數的CPU都是little-endian。 4. 了解big-endian和little-endian有什么作用? 一個重要的作用就是了解在網絡上不同的機器間的數據如何傳輸。 假設我們在網絡上有兩臺機器A和B,
Ubuntu編譯了新的內核,進入新內核時一直顯示載入Linux 5.6.7,載入初始化內存盤咋回事?
概述====1)當內核配置了內存盤時, 內核在初始化時可以將
軟盤
加載到內存盤中作為根盤.當同時配置了初始化內存盤(Initail RAM Disk)時, 內核在初始化時可以在安裝主盤之前,通過引導程序所加載的initrd文件建立一個內存初始化盤, 首先將它安裝成根
文件系統(tǒng)
, 然后執(zhí)行其
根目錄
下的linuxrc 文件,可用于在安裝主盤之前加載一些內核模塊. 等到linuxrc 程序退出后, 再將主盤安裝成根文件系統(tǒng),并將內存初始化盤轉移安皮銀裝到其/initrd目錄下.2)當主盤就是initrd所生成的內存初始化盤時, 不再進行重新安裝,在DOS下用loadlin加載的搶救盤就是這種工作方式.3)引導程序所加載的initrd為文件系統(tǒng)的映象文件, 可以是gzip壓縮的, 也可以是不壓縮的.能旁枝夠識別的文件系統(tǒng)有minix,ext2,romfs三種.4)當內核的根盤為軟盤時,內核初始化運握敏時會測試軟盤的指定部位是否存在文件系統(tǒng)或壓縮文件映象, 然后將之加載或解壓到內存盤中作為根盤. 這是單張搶救軟盤的工作方式.有關代碼========; init/main.c#ifdef CONFIG_BLK_DEV_INITRDkdev_t real_root_dev; 啟動參數所設定的根盤設備#endifalinkage void __init start_kernel(void){ char * command_line; unsigned long mempages; extern char saved_command_line; lock_kernel(); printk(linux_banner); setup_arch(&command_line);arch/i386/kernel/setup.c中,初始化initrd_start和initrd_end兩個變量 …#ifdef CONFIG_BLK_DEV_INITRD if (initrd_start && !initrd_below_start_ok && initrd_start need_resched = 1; cpu_idle();}static int init(void * unused){ lock_kernel(); do_basic_setup(); /* * Ok, we have completed the initial bootup, and * we’re essentially up and running. Get rid of the * initmem segments and start the user-mode stuff.. */ free_initmem(); unlock_kernel(); if (open(“/dev/console”, O_RDWR, 0) 0) while (pid != wait(&i)); 等待linuxrc進程退出 if (MAJOR(real_root_dev) != RAMDISK_MAJOR|| MINOR(real_root_dev) != 0) { ; 如果原來的根盤不是0號內存盤,則使用原來的根文件系統(tǒng), ; 并且將內存盤轉移到其/initrd目錄下 error = change_root(real_root_dev,”/initrd”); if (error) printk(KERN_ERR “Change root to /initrd: “”error %d/n”,error); } }#endif}#ifdef CONFIG_BLK_DEV_INITRDstatic int do_linuxrc(void * shell){ static char *argv = { “l(fā)inuxrc”, NULL, }; close(0);close(1);close(2); setsid(); 設置新的session號 (void) open(“/dev/console”,O_RDWR,0); (void) dup(0); (void) dup(0); return execve(shell, argv, envp_init);}#endif; arch/i386/kernel/setup.c#define RAMDISK_IMAGE_START_MASK 0x07FF#define RAMDISK_PROMPT_FLAG 0x8000#define RAMDISK_LOAD_FLAG 0x4000 #define PARAM ((unsigned char *)empty_zero_page)#define RAMDISK_FLAGS (*(unsigned short *) (PARAM+0x1F8)) 可用rdev設置的參數#define LOADER_TYPE (*(unsigned char *) (PARAM+0x210))#define INITRD_START (*(unsigned long *) (PARAM+0x218)) 初始化盤映象起始物理地址#define INITRD_SIZE (*(unsigned long *) (PARAM+0x21c)) 初始化盤字節(jié)數void __init setup_arch(char **cmdline_p){ …#ifdef CONFIG_BLK_DEV_RAM rd_image_start = RAMDISK_FLAGS & RAMDISK_IMAGE_START_MASK; 以塊為單位 rd_prompt = ((RAMDISK_FLAGS & RAMDISK_PROMPT_FLAG) != 0); rd_doload = ((RAMDISK_FLAGS & RAMDISK_LOAD_FLAG) != 0);#endif …#ifdef CONFIG_BLK_DEV_INITRD if (LOADER_TYPE && INITRD_START) { if (INITRD_START + INITRD_SIZE 0x%08lx)/ndisabling initrd/n”,INITRD_START + INITRD_SIZE,max_low_pfn (rd_length >> BLOCK_SIZE_BITS)) { ; 如果輸入盤的尺寸超過了輸出內存盤的允許尺寸 printk(“RAMDISK: image too big! (%d/%ld blocks)/n”,nblocks, rd_length >> BLOCK_SIZE_BITS); goto done; } /* * OK, time to copy in the data */ buf = kmalloc(BLOCK_SIZE, GFP_KERNEL); if (buf == 0) { printk(KERN_ERR “RAMDISK: could not allocate buffer/n”); goto done; } if (blk_size) devblocks = blk_size; 取輸入盤的容量#ifdef CONFIG_BLK_DEV_INITRD if (MAJOR(device) == MAJOR_NR && MINOR(device) == INITRD_MINOR) devblocks = nblocks; 如果輸入是初始化內存盤,則盤的容量為它的實際尺寸#endif if (devblocks == 0) { printk(KERN_ERR “RAMDISK: could not determine device size/n”); goto done; } printk(KERN_NOTICE “RAMDISK: Loading %d blocks into ram disk… “, nblocks, ((nblocks-1)/devblocks)+1, nblocks>devblocks ? “s” : “”); for (i=0; i release) infile.f_op->release(inode, &infile); printk(“Please insert disk #%d and press ENTER/n”, i/devblocks+1); wait_for_keypress(); if (blkdev_open(inode, &infile) != 0) { printk(“Error opening disk./n”); goto done; } infile.f_pos = 0; printk(“Loading disk #%d… “, i/devblocks+1); } infile.f_op->read(&infile, buf, BLOCK_SIZE, &infile.f_pos); outfile.f_op->write(&outfile, buf, BLOCK_SIZE, &outfile.f_pos);#if !defined(CONFIG_ARCH_S390) if (!(i % 16)) { printk(“%c/b”, rotator); rotate++; }#endif } printk(“done./n”); kfree(buf);successful_load: invalidate_buffers(device); ROOT_DEV = MKDEV(MAJOR_NR, unit); 將根盤設備設置為當前加載的內存盤 if (ROOT_DEVICE_NAME != NULL) strcpy (ROOT_DEVICE_NAME, “rd/0″);done: if (infile.f_op->release) infile.f_op->release(inode, &infile); set_fs(fs); return;free_inodes: /* free inodes on error */ iput(out_inode); blkdev_put(inode->i_bdev, BDEV_FILE);free_inode: iput(inode);}int __init identify_ramdisk_image(kdev_t device, struct file *fp, int start_block){ const int size = 512; struct minix_super_block *minix; struct ext2_super_block *ext2; struct romfs_super_block *romf; int nblocks = -1; unsigned char *buf; buf = kmalloc(size, GFP_KERNEL); if (buf == 0) return -1; minix = (struct minix_super_block *) buf; ext2 = (struct ext2_super_block *) buf; romf = (struct romfs_super_block *) buf; memset(buf, 0xe5, size); /* * Read block 0 to test for gzipped kernel */ if (fp->f_op->llseek) fp->f_op->llseek(fp, start_block * BLOCK_SIZE, 0); fp->f_pos = start_block * BLOCK_SIZE; fp->f_op->read(fp, buf, size, &fp->f_pos); ; 讀取offset開始的512字節(jié) /* * If it matches the gzip magic numbers, return -1 */ if (buf == 037 && ((buf == 0213) || (buf == 0236))) { printk(KERN_NOTICE”RAMDISK: Compressed image found at block %d/n”,start_block); nblocks = 0; goto done; } /* romfs is at block zero too */ if (romf->word0 == ROMSB_WORD0 && romf->word1 == ROMSB_WORD1) { printk(KERN_NOTICE”RAMDISK: romfs filesystem found at block %d/n”,start_block); nblocks = (ntohl(romf->size)+BLOCK_SIZE-1)>>BLOCK_SIZE_BITS; goto done; } /* * Read block 1 to test for minix and ext2 superblock */ if (fp->f_op->llseek) fp->f_op->llseek(fp, (start_block+1) * BLOCK_SIZE, 0); fp->f_pos = (start_block+1) * BLOCK_SIZE; fp->f_op->read(fp, buf, size, &fp->f_pos); /* Try minix */ if (minix->s_magic == MINIX_SUPER_MAGIC || minix->s_magic == MINIX_SUPER_MAGIC2) { printk(KERN_NOTICE”RAMDISK: Minix filesystem found at block %d/n”,start_block); nblocks = minix->s_nzones s_log_zone_size; goto done; } /* Try ext2 */ if (ext2->s_magic == cpu_to_le16(EXT2_SUPER_MAGIC)) { printk(KERN_NOTICE”RAMDISK: ext2 filesystem found at block %d/n”,start_block); nblocks = le32_to_cpu(ext2->s_blocks_count); goto done; } printk(KERN_NOTICE”RAMDISK: Couldn’t find valid RAM disk image starting at %d./n”,start_block);done: if (fp->f_op->llseek) fp->f_op->llseek(fp, start_block * BLOCK_SIZE, 0); fp->f_pos = start_block * BLOCK_SIZE; kfree(buf); return nblocks;}; fs/super.cvoid __init mount_root(void){ struct file_system_type * fs_type; struct super_block * ; struct vfount *vfnt; struct block_device *bdev = NULL; mode_t mode; int retval; void *handle; char path; int path_start = -1;#ifdef CONFIG_BLK_DEV_FD if (MAJOR(ROOT_DEV) == FLOPPY_MAJOR) { 當根盤還是軟盤,表示沒有加載過內存盤#ifdef CONFIG_BLK_DEV_RAM extern int rd_doload; extern void rd_load_secondary(void);#endif floppy_eject();#ifndef CONFIG_BLK_DEV_RAM printk(KERN_NOTICE “(Warning, this kernel has no ramdisk support)/n”);#else /* rd_doload is 2 for a dual initrd/ramload setup */ ; 只有當加載了initrd但沒有釋放到內存盤中(mount_inird=0)才有可能到這一步 if(rd_doload==2) rd_load_secondary(); 加載另一張軟盤到1號內存盤作為根盤 else#endif { printk(KERN_NOTICE “VFS: Insert root floppy and press ENTER/n”); wait_for_keypress(); } }#endif devfs_make_root (root_device_name); handle = devfs_find_handle (NULL, ROOT_DEVICE_NAME, MAJOR (ROOT_DEV), MINOR (ROOT_DEV),DEVFS_SPECIAL_BLK, 1); if (handle) /* Sigh: bd*() functions only paper over the cracks */ { unsigned major, minor; devfs_get_maj_min (handle, &major, &minor); ROOT_DEV = MKDEV (major, minor); } /* * Probably pure paranoia, but I’m less than happy about delving into * devfs crap and checking it right now. Later. */ if (!ROOT_DEV) panic(“I have no root and I want to scream”); bdev = bdget(kdev_t_to_nr(ROOT_DEV)); if (!bdev) panic(__FUNCTION__ “: unable to allocate root device”); bdev->bd_op = devfs_get_ops (handle); path_start = devfs_generate_path (handle, path + 5, sizeof (path) – 5); mode = FMODE_READ; if (!(root_mountflags & MS_RDON)) mode |= FMODE_WRITE; retval = blkdev_get(bdev, mode, 0, BDEV_FS); if (retval == -EROFS) { root_mountflags |= MS_RDON; retval = blkdev_get(bdev, FMODE_READ, 0, BDEV_FS); } if (retval) {/* * Allow the user to distinguish between failed open * and bad superblock on root device. */ printk (“VFS: Cannot open root device /”%s/” or %s/n”, root_device_name, kdevname (ROOT_DEV)); printk (“Please append a correct /”root=/” boot option/n”); panic(“VFS: Unable to mount root fs on %s”, kdevname(ROOT_DEV)); } check_disk_change(ROOT_DEV); = get_super(ROOT_DEV); 取根盤的超級塊 if () { fs_type = ->s_type; goto mount_it; } read_lock(&file_systems_lock); for (fs_type = file_systems ; fs_type ; fs_type = fs_type->next) { if (!(fs_type->fs_flags & FS_REQUIRES_DEV)) continue; 根文件系統(tǒng)必須依賴于塊設備 if (!try_inc_mod_count(fs_type->owner)) continue; 當文件系統(tǒng)模塊正在刪除過程中 read_unlock(&file_systems_lock); = read_super(ROOT_DEV,bdev,fs_type,root_mountflags,NULL,1);建立根盤的超級塊結構 if () goto mount_it; read_lock(&file_systems_lock); put_filesystem(fs_type); 釋放對文件系統(tǒng)模塊的引用 } read_unlock(&file_systems_lock); panic(“VFS: Unable to mount root fs on %s”, kdevname(ROOT_DEV));mount_it: printk (“VFS: Mounted root (%s filesystem)%s./n”, fs_type->name, (->s_flags & MS_RDON) ? ” readonly” : “”); if (path_start >= 0) { devfs_mk_symlink (NULL, “root”, DEVFS_FL_DEFAULT, path + 5 + path_start, NULL, NULL); memcpy (path + path_start, “/dev/”, 5); vfnt = add_vfnt(NULL, ->s_root, path + path_start); } else vfnt = add_vfnt(NULL, ->s_root, “/dev/root”); 建立根盤的安裝結構 /* FIXME: if something will try to umount us right now… */ if (vfnt) { set_fs_root(current->fs, vfnt, ->s_root); 設置當前進程的根盤和根目錄 set_fs_pwd(current->fs, vfnt, ->s_root); 設置當前進程的當前盤和當前目錄 if (bdev) bdput(bdev); /* holds a reference */ return; } panic(“VFS: add_vfnt failed for root fs”);}#ifdef CONFIG_BLK_DEV_INITRDint __init change_root(kdev_t new_root_dev,const char *put_old){ 以new_root_dev作為根盤重新安裝根文件系統(tǒng),原來的根轉移到put_old目錄下 struct vfount *old_rootmnt; struct nameidata devfs_nd, nd; int error = 0; read_lock(¤t->fs->lock); old_rootmnt = mntget(current->fs->rootmnt); 取當前進程的根盤安裝結構 read_unlock(¤t->fs->lock); /* First unmount devfs if mounted */ if (path_init(“/dev”, LOOKUP_FOLLOW|LOOKUP_POSITIVE, &devfs_nd)) error = path_walk(“/dev”, &devfs_nd); if (!error) { if (devfs_nd.mnt->mnt_->s_magic == DEVFS_SUPER_MAGIC && devfs_nd.dentry == devfs_nd.mnt->mnt_root) { dput(devfs_nd.dentry); down(&mount_sem); /* puts devfs_nd.mnt */ do_umount(devfs_nd.mnt, 0, 0); up(&mount_sem); } else path_release(&devfs_nd); } ROOT_DEV = new_root_dev; mount_root(); 改變根盤設備重新安裝根文件系統(tǒng)#if 1 shrink_dcache(); 清除目錄項緩沖中所有自由的目錄項 printk(“change_root: old root has d_count=%d/n”,atomic_read(&old_rootmnt->mnt_root->d_count));#endif mount_devfs_fs (); /* * Get the new mount directory */ error = 0; if (path_init(put_old, LOOKUP_FOLLOW|LOOKUP_POSITIVE|LOOKUP_DIRECTORY, &nd)) error = path_walk(put_old, &nd); 在新的根盤中尋找put_old目錄 if (error) { int blivet; printk(KERN_NOTICE “Trying to unmount old root … “); blivet = do_umount(old_rootmnt, 1, 0); 卸載原始的根盤 if (!blivet) { printk(“okay/n”); return 0; } printk(KERN_ERR “error %d/n”, blivet); return error; } /* FIXME: we should hold i_zombie on nd.dentry */ move_vfnt(old_rootmnt, nd.dentry, nd.mnt, “/dev/root.old”); mntput(old_rootmnt); path_release(&nd); return 0;}#endifstatic struct vfount *add_vfnt(struct nameidata *nd, 在虛擬文件系統(tǒng)中的安裝點 struct dentry *root, 安裝盤的根目錄項 const char *dev_name) 安裝盤名稱{ struct vfount *mnt;
————————————————
版權聲明:本文為CSDN博主「huanghaibin」的原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權協(xié)議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:
成都創(chuàng)新互聯科技有限公司,是一家專注于互聯網、IDC服務、應用軟件開發(fā)、網站建設推廣的公司,為客戶提供互聯網基礎服務!
創(chuàng)新互聯(www.cdcxhl.com)提供簡單好用,價格厚道的香港/美國云服務器和獨立服務器。創(chuàng)新互聯——四川成都IDC機房服務器托管/機柜租用。為您精選優(yōu)質idc數據中心機房租用、服務器托管、機柜租賃、大帶寬租用,高電服務器托管,算力服務器租用,可選線路電信、移動、聯通機房等。
新聞標題:深入剖析LinuxC編程中的ntohl函數(linuxcntohl)
標題來源:http://fisionsoft.com.cn/article/dhiphhp.html


咨詢
建站咨詢
