10057 WSA Исключение при SendBuf через сокет

Клиент:

//is called when the client tries to log in
    procedure TLogin_Form.btnLoginClick(Sender: TObject);
    var LoginQuery: TQuery;
    begin
        //If socket not open, open it
        if not LoginSocket.Active then
        begin
            LoginSocket.Open;
        end;

      //create package
      LoginQuery.Login := ledtName.Text;
      LoginQuery.Passwort := ledtPasswort.Text;
      LoginQuery.IP := LoginSocket.Socket.LocalAddress;
      //send package
      LoginSocket.Socket.SendBuf(LoginQuery, SizeOf(LoginQuery));
    end;

Сервер:

    //This procedure is executed when I click on start server button
    procedure TServer_Form.btnStartStopClick(Sender: TObject);
    begin
        //If not open, open it
        if not ServerSocket.Active then
        begin
          btnStartStop.Caption := 'stop server'; 
          //Open ServerSocket
          ServerSocket.Open;
        end
      else
      begin
        //If Socket open, close it, but watch for active connctions.
        if ServerSocket.Socket.ActiveConnections > 0 then
          begin
            ShowMessage('Clients still logged in');
          end
        else
          begin
            //If no clients connected, close socket
            ServerSocket.Close;
          end;
      end;
    end;

    //This procedure is called to verify weather the user is logged in and to send the verification back
    procedure UserCheckExist(Login, Passwort: string);
    var LoginReply: TReply;
    begin
        begin
          //Connect to DB
          DBConnect(true);
          DM.AQ_LOGIN.Close;
          DM.AQ_LOGIN.SQL.Clear;
          //Count of BLOGINs
          DM.AQ_LOGIN.SQL.Add('select count(BLOGIN) from BENU where BLOGIN = ''' + Login + ''' AND BPW = ''' + Passwort + '''');
          DM.AQ_LOGIN.Open;
          //LoginReply.Action tells the client then what to do with the LoginReply.Value
          LoginReply.Action := 0;
          //if user unique
          if DM.AQ_LOGIN.Fields[0].AsInteger = 1 then
            begin
              //LoginReply.Value = 1 means the client is allowed to log in
              LoginReply.Value := 1;
              //THIS RETURNS THE WSA 10057 EXCEPTION of user is unique
              Server_Form.ServerSocket.Socket.SendBuf(LoginReply, SizeOf(LoginReply));
            end
          else
            begin
                //LoginReply.Value = 0 means the client is NOT allowed to log in
                LoginReply.Value := 0;

                //THIS RETURNS THE WSA 10057 EXCEPTION if user is NOT unique
                Server_Form.ServerSocket.Socket.SendBuf(LoginReply, SizeOf(LoginReply));
            end;
          //Close ADOQuery
          DM.AQ_LOGIN.Close;
          //Close DB Connection
          DBConnect(false);
        end;
    end;

    //Is called when something is in the socket connection
    procedure TServer_Form.ServerSocketClientRead(Sender: TObject;
      Socket: TCustomWinSocket);
    var Query: TQuery;
    begin
        //Reads from the Socket (cant use ServerSocket.Socket.ReceiveBuf whysoever, but this is another thread)
        Socket.ReceiveBuf(Query, SizeOf(Query));
      case Query.Action of
        //If Query.Action = 0, which means the client tries to login call UserCheckExist
        0:  UserCheckExist(Query.Login, Query.Passwort);
      //Otherwise, getfuckedup
      else ShowMessage('Query Action not defined');
      end;
    end;

Одна странная вещь заключается в том, что я должен отправить логин + pw с клиента два раза.

При первой отправке (Client) я получаю onClientConnect и onAccept на сервере. Во второй раз, когда я отправляю (Клиент), сервер выполняет код до отмеченной строки. Я получаю 10057 WSA Exception.

Почему я получаю эту ошибку? Странно, однако, что если я открою сокет на сервере прямо перед строкой, где я получаю Исключение, говорящее, что "сокет не открыт", я все равно получу его

1 ответ

Решение

Код, который вы показали, не будет работать на стороне клиента и сервера из-за нескольких ошибок в вашем коде.

Когда TClientSocket установлен в режим ctNonBlocking (который, как я предполагаю, вы используете), Open() не будет вызывать событие OnConnect до тех пор, пока btnLoginClick() не завершится и поток не вернется в очередь сообщений. Недопустимо чтение или запись данных из сокета до тех пор, пока не сработает событие OnConnect. Поэтому вы должны переместить свой отправляющий код в само событие OnConnect. Вы также должны принять во внимание, что SendBuf() не сможет отправить все данные в одном пакете. Если SendBuf() возвращает -1, а WSAGetLastError() впоследствии возвращает WSAEWOULDBLOCK (что всегда будет истинно, если событие OnError не было инициировано), тогда данные не были отправлены полностью. Вы должны буферизовать все неотправленные байты где-нибудь, а затем подождать, пока не сработает событие OnWrite, прежде чем снова попытаться записать буферизованные байты или что-либо еще в этом отношении в сокет.

Что касается кода вашего сервера, вы пытаетесь записать исходящие данные в неправильный объект. Вы должны читать и записывать данные, используя объект TCustomWinSocket, предоставленный событием OnRead. Вместо этого вы пытаетесь записать данные в объект сервера TServerWinSocket, который не представляет действительную конечную точку сокета для любого подключенного клиента. Вам также необходимо взглянуть на возвращаемое значение ReceiveBuf() для обработки частичной передачи.

Попробуйте что-то вроде следующего:

Общие:

type
  // helper class that holds buffered input/output data
  SocketBuffers = class
  public
    constructor Create;
    destructor Destroy;
    Inbound: TMemoryStream;
    Outbound: TMemoryStream;
  end;

constructor SocketBuffers.Create;
begin
  inherited;
  Inbound := TMemoryStream.Create;
  Outbound := TMemoryStream.Create;
end;

destructor SocketBuffers.Destroy;
begin
  Inbound.Free;
  Outbound.Free;
  inherited;
end;

// removes processed bytes from a buffer
procedure CompactBuffer(Buffer: TMemoryStream);
begin
  if Buffer.Position > 0 then
  begin
    // bytes have been processed, remove them from the buffer...
    if Buffer.Position < Buffer.Size then
    begin
      // move unprocessed bytes to the front of the buffer...
      Move(Pointer(Longint(Buffer.Memory)+Buffer.Position)^, Buffer.Memory^, Buffer.Size - Buffer.Position);
      // reduce the buffer size just the remaining bytes...
      Buffer.Size := Buffer.Size - Buffer.Position;
    end else
    begin
      // all bytes have been processed, clear the buffer...
      Buffer.Clear;
    end;
  end;
end;

// sends raw bytes to the specified socket, buffering any unsent bytes
function SendDataToSocket(Socket: TCustomWinSocket; Data: Pointer; DataSize: Integer; Buffer: TMemoryStream): Integer;
var
  DataPtr: PByte;
  NumSent: Integer;
begin
  Result := 0;
  DataPtr := PByte(Data);
  if DataSize > 0 then
  begin
    if Buffer.Size = 0 then
    begin
      // the buffer is empty, send as many bytes as possible...
      repeat
        NumSent := Socket.SendBuf(DataPtr^, DataSize);
        if NumSent <= 0 then Break; // error or disconnected
        Inc(DataPtr, NumSent);
        Dec(DataSize, NumSent);
        Inc(Result, NumSent);
      until DataSize = 0;
      if DataSize = 0 then Exit; // nothing left to send or buffer
    end;
    // add unsent bytes to the end of the buffer...
    Buffer.Seek(0, soFromEnd);
    Buffer.WriteBuf(DataPtr^, DataSize);
    Inc(Result, DataSize);
  end;
end;

// sends buffered bytes to the specified socket
procedure SendBufferToSocket(Socket: TCustomWinSocket; Buffer: TMemoryStream);
var
  DataPtr: PByte;
  NumSent: Integer;
begin
  // start at the beginning of the buffer
  Buffer.Position := 0;
  DataPtr := PByte(Buffer.Memory);
  while Buffer.Position < Buffer.Size do
  begin
    NumSent := Socket.SendBuf(DataPtr^, Buffer.Size - Buffer.Position);
    if NumSent <= 0 then Break; // error or disconnected
    Inc(DataPtr, NumSent);
    Buffer.Seek(NumSent, soFromCurrent);
  end;
  // remove bytes that were sent...
  CompactBuffer(Buffer);
end;

// reads raw bytes from the specified socket ands buffers them
procedure ReadBufferFromSocket(Socket: TCustomWinSocket; Buffer: TMemoryStream);
var
  NumRecv: Integer;
  OldSize: Integer;
begin
  repeat
    NumRecv := Socket.ReceiveLength;
    if NumRecv <= 0 then Exit; // error or no data available

    // increase the size of the buffer
    OldSize := Buffer.Size;
    Buffer.Size := Buffer.Size + NumRecv;

    // read bytes into the new memory space
    NumRecv := Socket.ReceiveBuf(Pointer(Longint(Buffer.Memory)+OldSize)^, NumRecv);
    if NumRecv <= 0 then
    begin
      // nothing read, free the unused memory
      Buffer.Size := OldSize;
      Exit;
    end;
  until False;
end;

Клиент:

var
  Buffers: SocketBuffers = nil;

procedure TLogin_Form.FormCreate(Sender: TObject);
begin
  Buffers := SocketBuffers.Create;
end;

procedure TLogin_Form.FormDestroy(Sender: TObject);
begin
  LoginSocket.Close;
  Buffers.Free;
end;

procedure TLogin_Form.btnLoginClick(Sender: TObject);
begin
  if not LoginSocket.Active then
  begin
    Buffers.Inbound.Clear;
    Buffers.Outbound.Clear;
    LoginSocket.Open;
  end;
end;

procedure TLogin_Form.LoginSocketConnect(Sender: TObject; Socket: TCustomWinSocket);
var
  LoginQuery: TQuery;
begin
  LoginQuery.Login := ledtName.Text;
  LoginQuery.Passwort := ledtPasswort.Text;
  LoginQuery.IP := LoginSocket.Socket.LocalAddress;

  // send query, buffering unsent bytes if needed...
  SendDataToSocket(Socket, @LoginQuery, SizeOf(LoginQuery), Buffers.Outbound);
end;

procedure TLogin_Form.LoginSocketRead(Sender: TObject; Socket: TCustomWinSocket);
var
  Buffer: TmemoryStream;
  Available: Integer;
  Query: TQuery;
begin
  Buffer := Buffers.Inbound;

  // read available bytes into the buffer...
  ReadBufferFromSocket(Socket, Buffer);

  // process complete queries, ignore unfinished queries until later...
  Buffer.Position := 0;
  repeat
    Available := Buffer.Size - Buffer.Position;
    if Available < SizeOf(Query) then Break;
    Buffer.ReadBuf(Query, SizeOf(Query));
    // process query as needed ...
  until False;

  // remove processed bytes from the buffer...
  CompactBuffer(Buffer);
end;

procedure TLogin_Form.LoginSocketWrite(Sender: TObject; Socket: TCustomWinSocket);
begin
  // can send any buffered bytes now...
  SendBufferToSocket(Socket, Buffers.Outbound);
end;

Сервер:

procedure TServer_Form.btnStartStopClick(Sender: TObject);
begin
  if not ServerSocket.Active then
  begin
    btnStartStop.Caption := 'stop server'; 
    ServerSocket.Open;
  end
  else if ServerSocket.Socket.ActiveConnections > 0 then
  begin
    ShowMessage('Clients still logged in');
  end
  else
  begin
    ServerSocket.Close;
  end;
end;

procedure UserCheckExist(Socket: TCustomWinSocket; Login, Password: string);
var
  LoginReply: TReply;
begin
  ...
  LoginReply.Value := ...;

  // send query, buffering unsent bytes if needed...
  SendDataToSocket(Socket, @LoginReply, Sizeof(LoginReply), SocketBuffers(Socket.Data).Outbound);
  ...
end;


procedure TServer_Form.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  Socket.Data := SocketBuffers.Create;
end;

procedure TServer_Form.ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket);
begin
  SocketBuffers(Socket.Data).Free;
  Socket.Data := nil;
end;

procedure TServer_Form.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket);
var
  Buffer: TmemoryStream;
  Available: Integer;
  Query: TQuery;
begin
  Buffer := SocketBuffers(Socket.Data).Inbound;

  // read available bytes into the buffer...
  ReadBufferFromSocket(Socket, Buffer);

  // process complete queries, ignore unfinished queries until later...
  Buffer.Position := 0;
  repeat
    Available := Buffer.Size - Buffer.Position;
    if Available < SizeOf(Query) then Break;
    Buffer.ReadBuf(Query, SizeOf(Query));
    // process query as needed ...
    case Query.Action of
      0: UserCheckExist(Socket, Query.Login, Query.Password);
      ...
    end;
  until False;

  // remove processed bytes from the buffer...
  CompactBuffer(Buffer);
end;

procedure TServer_Form.ServerSocketClientWrite(Sender: TObject; Socket: TCustomWinSocket);
begin
  // can send any buffered bytes now...
  SendBufferToSocket(Socket, SocketBuffers(Socket.Data).Outbound);
end;
Другие вопросы по тегам