2009-07-22 5 views
1

클라이언트 :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를 전송해야한다는 것입니다 .

send (Client)를 처음 사용할 때 서버에서 onClientConnect 및 onAccept가 표시됩니다. 두 번째로 (클라이언트)를 보내면 서버는 내가 표시된 행까지 코드를 실행합니다. 나는 10057 WSA Exception을 얻는다.

왜이 오류가 발생합니까? 그러나 이상하게도, 소켓 바로 앞에 '소켓이 열려 있지 않습니다'라는 예외가 발생하는 경우, 그 소켓을 열면 어쨌든 알 수 있습니다.

답변

5

표시된 코드는 두 가지 모두에서 작동하지 않습니다. 클라이언트 및 서버 측면에서 귀하의 코드에 여러 가지 버그가 있습니다.

TClientSocket이 ctNonBlocking 모드 (사용 중이라고 가정)로 설정되면 btnLoginClick()이 종료되고 흐름이 메시지 대기열로 반환 될 때까지 Open()이 OnConnect 이벤트를 트리거하지 않습니다. OnConnect 이벤트가 발생할 때까지 소켓에서 데이터를 읽거나 쓰는 것은 유효하지 않습니다. 따라서 전송 코드를 OnConnect 이벤트 자체로 옮겨야합니다. SendBuf()가 모든 데이터를 단일 패킷으로 보낼 수 없다는 점도 고려해야합니다. SendBuf()가 -1을 반환하고 WSAGetLastError()가 WSAEWOULDBLOCK을 나중에 반환하면 (OnError 이벤트가 트리거되지 않은 경우 항상 true), 데이터가 전체적으로 전송되지 않습니다. 어딘가에 보내지 않은 바이트를 버퍼링 한 다음 OnWrite 이벤트가 발생하여 버퍼링 된 바이트를 다시 쓰거나 소켓에 문제가 발생하기를 기다립니다.

서버 코드에 대해 잘못된 개체에 아웃 바운드 데이터를 쓰려고합니다. OnRead 이벤트가 제공하는 TCustomWinSocket 개체를 사용하여 데이터를 읽고 쓰어야합니다. 대신 서버의 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; 
+0

당신이 어떤 의견을 삽입 할 수 있을까요? 특히 공통 부분에? – Acron

+0

코멘트를 추가했습니다. –

+0

LoginReply는 실제로 레코드입니다. 내가 그 기록에 선포한다면 그 메시지 : 문자열; 해당 문자열을 파일 (10MB)의 데이터로 채우고 보내려고하면 서버에서 액세스 위반이 발생합니다. – Acron