注册 登录
编程论坛 VFP论坛

htons()返回不同值的问题

sam_jiang 发布于 2025-09-29 22:20, 998 次点击
同一个winapi函数htons,为什么会返回不同的值?
导致我用bintoc函数时报错,bintoc的第一个参数的值范围–32,768 到 32,767,这个值为正时恰巧超出了!
这个问题要怎么解决呢?

附上图片:
只有本站会员才能查看附件,请 登录
29 回复
#2
吹水佬2025-09-30 00:14
试试先按数值的位数判断有无符号位
如:
8位数0x7F无符号位(BITAND(0x7F,0x80)!=0x80)
8位数0x81有符号位(BITAND(0x81,0x80)==0x80)
如果无符号位取原数值 0x7F
如果有符号位取 0x81 - (2^8)
#3
吹水佬2025-09-30 07:50
写个转换函数,测试一下
程序代码:

? UnsignedToSigned(32767,16)
? UnsignedToSigned(32768,16)

** 无符号整数转有符号整数,nBitSize:81632
FUNCTION UnsignedToSigned(nValue, nBitSize)
    IF !INLIST(nBitSize, 8,16,32)
        ERROR "无效的位数"
        RETURN nValue
    ENDIF
    LOCAL nMask, nSignBit
    nMask    = IIF(nBitSize==8,0xFF, IIF(nBitSize==16,0xFFFF, IIF(nBitSize==32,0xFFFFFFFF, 0)))
    nSignBit = IIF(nBitSize==8,0x80, IIF(nBitSize==16,0x8000, IIF(nBitSize==32,0x80000000, 0)))
    IF !BETWEEN(nValue, 0,nMask)
        ERROR TRANSFORM(nValue)+"超出"+TRANSFORM(nBitSize)+"位无符号整数范围"
        RETURN nValue
    ENDIF
    nValue = BITAND(nValue, nMask)            && 保留nBitSize位数值
    IF BITAND(nValue, nSignBit) == nSignBit   && 掩码检查符号位
        RETURN nValue - (2^nBitSize)          && 有符号
    ENDIF
    RETURN nValue
ENDFUNC
#4
schtg2025-09-30 09:32
#5
sam_jiang2025-09-30 12:07
简单粗暴地用一句代码可以让它符合bintoc的要求iif(htons(8080)>32767,htons(8080)-0xffff,htons(8080))但是依然没能解释为什么htons()函数会返回2个不同的值?是vfp的问题?还是winapi的问题?还是我的问题

[此贴子已经被作者于2025-9-30 12:09编辑过]

#6
吹水佬2025-10-01 08:01
以下是引用sam_jiang在2025-9-30 12:07:19的发言:

简单粗暴地用一句代码可以让它符合bintoc的要求iif(htons(8080)>32767,htons(8080)-0xffff,htons(8080))但是依然没能解释为什么htons()函数会返回2个不同的值?是vfp的问题?还是winapi的问题?还是我的问题

可能是vfp数值类型定义的问题。
虽然vfp是用C写的软件,但vfp按位运算是没有“有符号整数”和“无符号整数”的概念,好像都定义为有符号整数。
vfp是32位编译软件,但vfp的数值类型数据是可以超出32位数值的范围。
bintoc()是按字节参数转换,即按位计算,bintoc(-1,"1")8位;bintoc(-1,"2")16位;bintoc(-1,"4")32位。
这样观察一下:
?STRCONV(BINTOC(-1,"1rs"),15)
?STRCONV(BINTOC(-1,"2rs"),15)
?STRCONV(BINTOC(-1,"4rs"),15)
但不支持无符号整数,下面语句会抛出“参数无效”异常。
?STRCONV(BINTOC(0xFF,"1rs"),15)
?STRCONV(BINTOC(0xFFFF,"2rs"),15)
?STRCONV(BINTOC(0xFFFFFFFF,"4rs"),15)





#7
easyppt2025-10-01 08:06
不懂C,C++,所以对 类似这样的一组函数: BINTOC()  及  4rs  总是很抽象,希望吹能能开个专题帖子详细讲解一下,最好带上场景和例子

再此也祝大家双节快乐!!
#8
吹水佬2025-10-01 08:21
以下是引用easyppt在2025-10-1 08:06:15的发言:

不懂C,C++,所以对 类似这样的一组函数: BINTOC()  及  4rs  总是很抽象,希望吹能能开个专题帖子详细讲解一下,最好带上场景和例子

再此也祝大家双节快乐!!

BINTOC()的那个"xRS"参数(x=1/2/4)可从上面提到的看出是转换为x个字节的字符串。
那个“.RS”(或其他)vfp帮助有说明,主要是字节顺序、符号位和数值类型等与位运算有关的参数。
可以用同一个数据观察一下不同的参数运行结果。

#9
sam_jiang2025-10-01 19:04
国庆中秋节快乐!

问了deepseek,它给出了如下答复:

在 Visual FoxPro (VFP) 中,HTONS(8080) 返回不同结果(如 ‌36895‌ 和 ‌-28641‌)的原因与 ‌有符号整数‌ 和 ‌无符号整数‌ 的存储方式有关。以下是具体解释:

1. 根本原因‌
HTONS() 函数将 ‌16 位整数‌ 从主机字节序(Host)转换为网络字节序(Network)。
8080‌ 的二进制表示:00011111 00010000(大端序)。
转换后:
无符号整数‌:36895(直接解析为 10001111 11011111)。
有符号整数‌:-28641(最高位为 1 时表示负数,补码解析)。
2. 为什么结果不同?‌
VFP 的数值存储机制‌:
VFP 默认以 ‌有符号整数‌ 处理数值,但某些情况下(如内存直接读取)会按 ‌无符号整数‌ 解析。
当数值超过 ‌32,767‌(16 位有符号整数最大值)时,VFP 可能将其解释为负数(补码形式)。
示例计算‌:
36895(无符号)的二进制:10001111 11011111。
转换为有符号补码:-28641(因为最高位 1 表示负数)。
#10
sych2025-10-01 21:26
你检查一下你两段代码中的htons定义是否一致
#11
吹水佬2025-10-01 22:23
htons()的原型:
程序代码:

u_short WSAAPI htons(
  [in] u_short hostshort
);

htons()的出入口都是16位,vfp声明htons()时应该这样:
DECLARE short htons IN Ws2_32 short


#12
吹水佬2025-10-01 22:28
翻看了以前的代码htons()的声明有错,有些声明intger、long,都不严谨很有可能运行异常。
#13
sych2025-10-02 10:11
确实如此
只有本站会员才能查看附件,请 登录
#14
sam_jiang2025-10-02 18:58
回复 11楼 吹水佬
检查了我的源代码,确实是我申明错了,厉害!

我确实申明为integer了,应该是short!@sych @吹水佬,感谢2位
#15
sam_jiang2025-10-02 18:59
回复 13楼 sych
感谢,感谢,的确是这个问题导致!

程序代码:

...
    PROTECTED PROCEDURE dodecl
        DECLARE INTEGER WSAStartup IN ws2_32 INTEGER wVersion, STRING @lpWSAData
        DECLARE INTEGER WSACleanup IN ws2_32
        DECLARE INTEGER listen IN ws2_32 INTEGER s, INTEGER backlog
        DECLARE INTEGER accept IN ws2_32 INTEGER s, STRING @addr, INTEGER @addrlen
        DECLARE INTEGER socket IN ws2_32 INTEGER af, INTEGER type, INTEGER protocol      
        DECLARE INTEGER bind IN ws2_32 as _bind INTEGER s, STRING @sockaddr, INTEGER namelen
        DECLARE INTEGER send IN ws2_32 INTEGER s, STRING buf, INTEGER len, INTEGER flags      
        DECLARE INTEGER sendto IN ws2_32;
            INTEGER s, STRING buf, INTEGER len, INTEGER flags,;
            STRING @to, INTEGER tolen      
        DECLARE INTEGER accept IN ws2_32 INTEGER s, STRING @addr, INTEGER @addrlen
        DECLARE INTEGER recvfrom IN ws2_32;
            INTEGER s, STRING @buf, INTEGER len, INTEGER flags,;
            STRING @from, INTEGER @fromlen
        DECLARE INTEGER connect IN ws2_32 INTEGER s, STRING @name, INTEGER namelen                     
        DECLARE INTEGER recv IN ws2_32 INTEGER s, STRING @buf, INTEGER len, INTEGER flags        
        DECLARE STRING inet_ntoa IN ws2_32 INTEGER in
        DECLARE INTEGER inet_addr IN ws2_32 STRING @cp
        DECLARE INTEGER ntohs IN ws2_32 INTEGER netshort
        DECLARE INTEGER htons IN ws2_32 INTEGER netshort
        DECLARE INTEGER WSACleanup IN ws2_32
        DECLARE INTEGER closesocket IN ws2_32 INTEGER s
...
#16
吹水佬6 天前 20:58
回复 13楼 sych

我的结果不一样,有问题。

只有本站会员才能查看附件,请 登录
#17
吹水佬6 天前 21:14

有点意思,看来vfp在按位运算时要格外留神和严谨。

只有本站会员才能查看附件,请 登录
#18
schtg5 天前 06:56
#19
sych前天 09:11
以前还真没留意这个细节,代码都是网上随手抄的,现在讨论发现还真是问题不小
这个htons能不能用下面的代码替换
BINTOC(nPort-2^15,"2r")
#20
sam_jiang前天 09:26
回复 19楼 sych
你这样不行
#21
吹水佬前天 09:37
按htons的定义返回的是16位无符号整数。
上面的例子36895应该才是对的,用在BINTOC函数时要判断转换为16位有符号整数。
#22
sych前天 10:01
应该是我理解错了,还是要经过htons()的转换
不过咱们应用环境比较固定,vfp9,所以bintoc(nPort-2^15,"2r")也算正确吧

[此贴子已经被作者于2025-10-8 10:16编辑过]

#23
吹水佬前天 10:20
不用htons()也可以,就是大小端转换,即16位的低8位与高8位互换。
用VFP的位运算函数就可以。
#24
sych前天 10:21
回复 23楼 吹水佬
bintoc函数中的参数“R”可以实现自动翻转功能
#25
sych前天 10:53
SET CENTURY on
SET DATE ANSI
SET HOURS TO 24
CLEAR

fh=_GetNetTime("128.138.140.44",13)
?zh(fh)
retu
retu

FUNCTION _GetNetTime(szNtpIP, ddNtpPort)
LOCAL stWsaData, stSockAddr, szBuffer, dhSocket
LOCAL i, ddLen,ret

DECLARE LONG WSAStartup  IN "Ws2_32" LONG, STRING@
DECLARE LONG WSACleanup  IN "Ws2_32"
DECLARE LONG socket      IN "Ws2_32" LONG, LONG, LONG
DECLARE LONG connect     IN "Ws2_32" LONG, STRING@, LONG
DECLARE LONG recv        IN "Ws2_32" LONG, STRING@, LONG, LONG
DECLARE LONG closesocket IN "Ws2_32" LONG
DECLARE LONG inet_addr   IN "Ws2_32" STRING@
DECLARE LONG htons       IN "Ws2_32" LONG
DECLARE INTEGER gethostbyname in WSOCK32 String@
DECLARE LONG inet_ntoa     IN "Ws2_32" LONG

stWsaData  = REPLICATE(0h00, 398)
szBuffer   = REPLICATE(0h00, 256)
stSockAddr = REPLICATE(0h00, 16)

WSAStartup(0x202, @stWsaData)
dhSocket = socket(2, 1, 6)
IF ISDIGIT(szNtpIP)
    stSockAddr = 0h0200;
        + BINTOC(htons(ddNtpPort), "2RS");
        + BINTOC(inet_addr(@szNtpIP), "4RS");
        + REPLICATE(0h00, 8)
else
    RET = CTOBIN(SYS(2600, gethostbyname(@szNtpIP) + 12, 4), "4RS")  &&偏移12,IP地址列表
    RET = CTOBIN(SYS(2600, RET, 4), "4RS")  &&IP地址
    stSockAddr = 0h0200;
        + BINTOC(htons(ddNtpPort), "2RS");
        + SYS(2600, RET, 4);
        + REPLICATE(0h00, 8)
endif
ddLen  = 0
IF connect(dhSocket, @stSockAddr, LEN(stSockAddr)) = -1
    ?"连接失败"
else
    FOR i = 1 TO 3
        ddLen = recv(dhSocket, @szBuffer, LEN(szBuffer), 0)
        IF ddLen != -1
            EXIT
        ENDIF
    ENDFOR
endif
closesocket(dhSocket)
WSACleanup()
IF ddLen > 0
    szBuffer = LEFT(szBuffer, ddLen)
    RETURN szBuffer
ELSE
*        MESSAGEBOX("网络异常,时间同步失败")
    RETURN ""
ENDIF
ENDFUNC

PROCEDURE zh
LPARAMETERS szutc
IF EMPTY(szutc)
else
    ddYear   = INT(VAL(SUBSTR(szUTC, 8, 2))) + 2000
    ddMonth  = INT(VAL(SUBSTR(szUTC, 11, 2)))
    ddDay    = INT(VAL(SUBSTR(szUTC, 14, 2)))
    ddHour   = INT(VAL(SUBSTR(szUTC, 17, 2)))
    ddMinute = INT(VAL(SUBSTR(szUTC, 20, 2)))
    ddSecond = INT(VAL(SUBSTR(szUTC, 23, 2)))
    RETURN DATETIME(ddYear,ddMonth,ddDay,ddHour,ddMinute,ddSecond)+8*3600
endif
RETURN ""
获取网络时间,代码都是从网上抄的,又随意改的,测试htons,发现用bintoc(nPort-2^15,"2")才是正确的
因为这里经过htons转换后又加了个参数r,搞不懂了
stSockAddr = 0h0200;
        + BINTOC(htons(ddNtpPort), "2RS");
        + BINTOC(inet_addr(@szNtpIP), "4RS");
        + REPLICATE(0h00, 8)
只能这样改
stSockAddr = 0h0200;
        + BINTOC(ddNtpPort-2^15, "2");
        + BINTOC(inet_addr(@szNtpIP), "4RS");
        + REPLICATE(0h00, 8)


[此贴子已经被作者于2025-10-8 10:55编辑过]

#26
sych前天 15:20
通过ntp获取时间,网上说端口是123,可我测试发现只有端口13才可以获取,123却连接失败
这几个都可以获取网络时间,但端口都是13才可以
"time-a.nist.gov"
"time-b.nist.gov"
"128.138.140.44"
#27
sych前天 17:09
这个是udp协议,最后接收失败,没有调试成功,通过NTP获取网络时间
    DECLARE INTEGER WSAGetLastError IN ws2_32
   
    * 初始化Winsock
    DECLARE INTEGER WSAStartup IN ws2_32 INTEGER, STRING @
    DECLARE INTEGER WSACleanup IN ws2_32
   
    lcWSAData = REPLI(CHR(0), 400)
    IF WSAStartup(0x202, @lcWSAData) != 0
        ? "Winsock初始化失败"
        RETURN
    ENDIF
   
    * 创建UDP Socket
    DECLARE INTEGER socket IN ws2_32 INTEGER, INTEGER, INTEGER
    DECLARE INTEGER closesocket IN ws2_32 INTEGER
   
    lnSocket = socket(2, 2, 17)  && AF_INET, SOCK_DGRAM, IPPROTO_UDP
    IF lnSocket = -1
        ? "Socket创建失败: " + TRANSFORM(WSAGetLastError())
        WSACleanup()
        RETURN
    ENDIF
   
    * 设置超时
    DECLARE INTEGER setsockopt IN ws2_32 INTEGER, INTEGER, INTEGER, STRING @, INTEGER
    lcTimeout = num2dword(5000)  && 5秒超时
    setsockopt(lnSocket, 0xFFFF, 0x1006, @lcTimeout, 4)  && SO_RCVTIMEO
   
    * 构建NTP请求
    lcNTPData = CHR(0x1B) + REPLI(CHR(0), 47)
   
    * 发送到NTP服务器
    DECLARE INTEGER sendto IN ws2_32 INTEGER, STRING @, INTEGER, INTEGER, STRING @, INTEGER
    DECLARE INTEGER inet_addr IN ws2_32 STRING
    DECLARE short htons       IN "Ws2_32" short
   
    lnServerIP = inet_addr("120.25.115.20")  && 阿里云NTP服务器IP
   
    lcSockAddr = 0h0200+ num2word(123)+num2dword(lnServerIP) + REPLI(CHR(0), 8)
    IF sendto(lnSocket, @lcNTPData, 48, 0, @lcSockAddr, 16) = -1
        ? "发送失败: " + TRANSFORM(WSAGetLastError())
        closesocket(lnSocket)
        WSACleanup()
        RETURN
    ENDIF
   
    ? "请求发送成功,等待响应..."
   
    * 接收响应
    DECLARE INTEGER recvfrom IN ws2_32 INTEGER, STRING @, INTEGER, INTEGER, STRING @, INTEGER @
   
    lcBuffer = REPLI(CHR(0), 1024)
    lcFromAddr = REPLI(CHR(0), 16)
    lnAddrLen = 16
   
    lnReceived = recvfrom(lnSocket, @lcBuffer, 1024, 0, @lcFromAddr, @lnAddrLen)
    IF lnReceived >= 48
        ? "收到响应,长度: " + TRANSFORM(lnReceived)
        * 解析时间...
    ELSE
        ? "接收失败: " + TRANSFORM(WSAGetLastError())
    ENDIF
   
    closesocket(lnSocket)
    WSACleanup()

FUNCTION num2dword(lnValue)
    LOCAL lcResult, i
    lcResult = ""
    FOR i = 3 TO 0 STEP -1
        lcResult = lcResult + CHR(BITRSHIFT(lnValue, i*8) % 256)
    ENDFOR
    RETURN lcResult
ENDFUNC

FUNCTION num2word(lnValue)
    RETURN CHR(BITRSHIFT(lnValue, 8) % 256) + CHR(lnValue % 256)
ENDFUNC
#28
sam_jiang前天 17:14
回复 25楼 sych
你这段代码htons函数声明不对,和我一样申明为long或integer了!
#29
sych前天 17:23
回复 28楼 sam_jiang
都是网上抄的代码,不知其所以然,不过可以运行一下代码试试,先调试通过再修改BUG
#30
sych前天 21:05
DECLARE INTEGER WSAGetLastError IN ws2_32

* 初始化Winsock
DECLARE INTEGER WSAStartup IN ws2_32 INTEGER, STRING @
DECLARE INTEGER WSACleanup IN ws2_32

lcWSAData = REPLI(CHR(0), 400)
IF WSAStartup(0x202, @lcWSAData) != 0
    ? "Winsock初始化失败"
    RETURN
ENDIF

* 创建UDP Socket
DECLARE INTEGER socket IN ws2_32 INTEGER, INTEGER, INTEGER
DECLARE INTEGER closesocket IN ws2_32 INTEGER

lnSocket = socket(2, 2, 17)  && AF_INET, SOCK_DGRAM, IPPROTO_UDP
IF lnSocket = -1
    ? "Socket创建失败: " + TRANSFORM(WSAGetLastError())
    WSACleanup()
    RETURN
ENDIF
* 设置超时
DECLARE INTEGER setsockopt IN ws2_32 INTEGER, INTEGER, INTEGER, STRING @, INTEGER
lcTimeout = BINTOC(5000,"4s")  && 5秒超时
setsockopt(lnSocket, 0xFFFF, 0x1006, @lcTimeout, 4)  && SO_RCVTIMEO
* 构建NTP请求
lcNTPData = CHR(0x1B) + REPLI(CHR(0), 47)

* 发送到NTP服务器
DECLARE INTEGER sendto IN ws2_32 INTEGER, STRING @, INTEGER, INTEGER, STRING @, INTEGER
DECLARE INTEGER inet_addr IN ws2_32 STRING

lnServerIP = inet_addr("120.25.115.20")  && 阿里云NTP服务器IP
*lnServerIP = inet_addr("202.120.2.101")  &&国家授时中心

lcSockAddr = 0h0200+ bintoc(123,"2s")+bintoc(lnServerIP-2^31,"4r") + REPLI(CHR(0), 8)
IF sendto(lnSocket, @lcNTPData, 48, 0, @lcSockAddr, 16) = -1
    ? "发送失败: " + TRANSFORM(WSAGetLastError())
    closesocket(lnSocket)
    WSACleanup()
    RETURN
ENDIF

? "请求发送成功,等待响应..."

* 接收响应
DECLARE INTEGER recvfrom IN ws2_32 INTEGER, STRING @, INTEGER, INTEGER, STRING @, INTEGER @

lcBuffer = REPLI(CHR(0), 1024)
lcFromAddr = REPLI(CHR(0), 16)
lnAddrLen = 16
lnReceived = recvfrom(lnSocket, @lcBuffer, 1024, 0, @lcFromAddr, @lnAddrLen)
IF lnReceived >= 48
    ? "收到响应,长度: " + TRANSFORM(lnReceived)
    lnTransmitSec = ctobin(SUBSTR(lcBuffer, 41, 4))+2^31
    lnTransmitFrac = (ctobin(SUBSTR(lcBuffer, 45, 4))+2^31)/4294967296
    ?DATETIME(1970,1,1,0,0,0)+lnTransmitSec-2208988800+8*60*60
ELSE
    ? "接收失败: " + TRANSFORM(WSAGetLastError())
ENDIF
closesocket(lnSocket)
WSACleanup()
retu
调试通过

[此贴子已经被作者于2025-10-8 22:11编辑过]

1