getpeername() 总是失败,错误代码为 WSAENOTCONN

getpeername() always fails with error code WSAENOTCONN

我正在尝试使用 getpeerinfo 来确保我可以在连接后获取对等信息。

它失败了:

WSAENOTCONN (10057)

Socket is not connected.

A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using sendto) no address was supplied.

基本流程是:

我做错了什么?

program Project1;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  Winapi.Winsock2;

procedure Main;
var
    hSocket: TSocket;
    wsData: TWSAData;
    nodeName: string;
    serviceName: string;
    localAddressLength: Cardinal;
    localAddress: TSockAddr;
    remoteAddressLength: Cardinal;
    remoteAddress: TSockAddr;
    name: TSockAddr;
    nameLen: Integer;
    errorCode: Integer;
    bRes: Boolean;
begin
    WSAStartup(02, {var}wsData);

    hSocket := socket(AF_INET, SOCK_STREAM, 0);

    nodeName := 'whosebug.com';
    serviceName := '80';

    bRes := WSAConnectByNameW(hSocket, PChar(nodeName), PChar(serviceName),
            {var}localAddressLength, {var}localAddress,
            {var}remoteAddressLength, {var}remoteAddress,
            nil, nil);
    if not bRes then
    begin
        errorCode := WSAGetLastError;
        RaiseLastOSError(errorCode);
    end;

    //If no error occurs, getpeername returns zero.
    //Otherwise, a value of SOCKET_ERROR is returned,
    //and a specific error code can be retrieved by calling WSAGetLastError.
    nameLen := sizeof(name);
    errorCode := getpeername(hSocket, {var}name, {var}nameLen);
    if errorCode <> 0 then
    begin
        errorCode := WSAGetLastError;
        RaiseLastOSError(errorCode);
    end;
end;

begin
    try
        Main;
    except
        on E: Exception do
            Writeln(E.ClassName, ': ', E.Message);
    end;
end.

我们知道连接已连接,因为:

红利阅读

答案在 WinSock 文档中。

WSAConnectByNameW() function

When the WSAConnectByName function returns TRUE, the socket s is in the default state for a connected socket. The socket s does not enable previously set properties or options until SO_UPDATE_CONNECT_CONTEXT is set on the socket. Use the setsockopt function to set the SO_UPDATE_CONNECT_CONTEXT option.

因此,当 WSAConnectByNameW() returns 为真时,getpeername() 失败并返回 WSAENOTCONN 因为您没有调用 setsockopt(SO_UPDATE_CONNECT_CONTEXT) 将套接字置于正确的状态. SOL_SOCKET Socket Options 文档对此进行了说明:

SO_UPDATE_CONNECT_CONTEXT

This option is used with the ConnectEx, WSAConnectByList, and WSAConnectByName functions. This option updates the properties of the socket after the connection is established. This option should be set if the getpeername, getsockname, getsockopt, setsockopt, or shutdown functions are to be used on the connected socket.

试试这个:

bRes := WSAConnectByNameW(hSocket, PChar(nodeName), PChar(serviceName),
  {var}localAddressLength, {var}localAddress,
  {var}remoteAddressLength, {var}remoteAddress,
  nil, nil);
if not bRes then
begin
  errorCode := WSAGetLastError;
  RaiseLastOSError(errorCode);
end;

// ADD THIS..
errorCode := setsockopt(hSocket, SOL_SOCKET, SO_UPDATE_CONNECT_CONTEXT, nil, 0);
if errorCode <> 0 then
begin
  errorCode := WSAGetLastError;
  RaiseLastOSError(errorCode);
end;

...

也就是说,没有必要在您的示例中使用 getpeername(),因为它 returns 与变量中 WSAConnectByNameW() 已经 returns 相同的信息正在传递给它的 RemoteAddress 参数。