Seite 3 von 3

Re: ins Netzwerk senden mit Threads

Verfasst: 03.07.2015 12:11
von Sven
mhs hat geschrieben:Ich hab es mit einem kurzen Beispiel getestet... sieht so aus, als ob du Recht hast. Das bedeutet aber auch, dass wenn ein Empfänger seinen Empfangspuffer aus irgendeinem Grund nicht leert, der Sender bei SendNetworkString wartet bis er schwarz wird und du nicht darauf reagieren kannst...
Kann ich nachvollziehen. Ist auch mit SendNetworkData so.

Das ist ja selten dämlich! Damit kann mir ein nicht reagierender Server den Client einfrieren, oder umgekehrt. Sowas darf doch nicht sein.

Das kann man aber auch mit dem Rückgabewert von SendNetworkString nicht verhindern, denn der ist ja bis zur Blockade immer gleich der Stringlänge.

Das könnte man nur durch ein Acknowledge des Empfängers verhindern, jedesmal wenn er den Empfangspuffer geleert hat. Und erst dann darf der Sender wieder Daten schicken.

Re: ins Netzwerk senden mit Threads

Verfasst: 03.07.2015 12:21
von auser
Sven hat geschrieben:das geht mich nichts an. Das handelt der TCP/IP-Treiber von Windows aus.
TCP hilft dir bei der Nummerierung und hat 'ne Fehlerkorrektur, das ist schon 'ne ganze Menge. Sehr oft wird auch alles durch gehen was du reinschiebst. Es hat aber schon seinen Sinn daß Funktionen wie "send()" (oder die PB-Ableitungen davon) eine Größe an Bytes zurück geben. Wenn du 100 bytes verschicken willst und dir dein PB-Send sagt da gingen jetzt nur 50 durch dann ist es sowohl falsch die Schuld auf den Treiber zu schieben wie auch falsch nochmal alle 100 bytes zu schicken.

Hier ist z.B. ein schlankes Beispiel in C:
http://stackoverflow.com/questions/1418 ... s-are-sent


Hier ist ein Testschnipsel zum Rumspielen basierend auf berkely Sockets für PB (Linux u. Windows) wo alles in Threads ist (sogar der Test-Client der auch auf 'nem anderem PC laufen könnte) - und mit mit 'nem Switch für Blocking Sockets.

Code: Alles auswählen

EnableExplicit

Structure serverdata
  address.s
  port.u
  blocking.i
EndStructure   
  
Structure clientstuff
  socket.i
  IP.i
  port.u
  blocking.i
EndStructure 

CompilerIf #PB_Compiler_OS <> #PB_OS_Windows
  ;{ 
  ImportC ""
    errno_location() As "__errno_location"
  EndImport
  
  #AF_INET = 2
  #INADDR_ANY = $00000000
  #SOCK_STREAM = 1
  #SOL_SOCKET = 1
  #SO_REUSEADDR = 2
  #SO_RCVTIMEO = 20
  #SO_SNDTIMEO = 21
  
  Structure hostent
    *h_name
    *h_aliases
    h_addrtype.l
    h_length.l
    *h_addr_list
  EndStructure 
  
  Structure timeval
    tv_sec.i
    tv_usec.i
  EndStructure 
  
  Structure sockaddr
    sa_family.u
    sa_data.b[14]
  EndStructure
  
  Structure in_addr
    s_addr.l
  EndStructure
  
  Structure sockaddr_in
    sin_family.u 
    sin_port.u
    sin_addr.l
    sin_zero.b[SizeOf(sockaddr)-2-SizeOf(unicode)-SizeOf(in_addr)]
  EndStructure
  
  #SOCK_DGRAM = 2
  #SO_ERROR = 4
  
  Structure pollfd
    fd.l
    events.w
    revents.w
  EndStructure

  #POLLIN     = $001
  #POLLPRI    = $002
  #POLLOUT    = $004
  #POLLRDNORM = $040
  #POLLRDBAND = $080
  #POLLWRNORM = $100
  #POLLWRBAND = $200
  
  #POLLERR    = $008
  #POLLHUP    = $010
  #POLLNVAL   = $020
  
  PrototypeC proto_poll(*fds.pollfd,num.i,timeout.l)
  Global libc = OpenLibrary(#PB_Any,"libstdc++.so.6")
  If Not libc 
    libc = OpenLibrary(#PB_Any,"libc.so.6")
  EndIf 
  If Not libc
    End 1
  EndIf 
  Global poll.proto_poll = GetFunction(libc,"poll")
  If Not poll
    End 1
  EndIf
  ;}
CompilerEndIf 
  
Procedure SelectSocket(socket.l,blocking.i=1)
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    Protected checkin.fd_set
    Protected sel_time.timeval
    
    checkin\fd_array[0] = socket
    checkin\fd_count = 1
    
    If blocking
      ProcedureReturn(select_(socket+1,@checkin,#Null,#Null,#Null))
    Else
      sel_time\tv_usec = 1
      ProcedureReturn(select_(socket+1,@checkin,#Null,#Null,@sel_time))
    EndIf 
  
  CompilerElse
    Protected checkin.pollfd
    Protected ret.l
      
    checkin\fd = socket
    checkin\events = (#POLLIN|#POLLRDBAND|#POLLERR|#POLLHUP|#POLLNVAL)
    If blocking 
      ret = poll(@checkin,1,-1)
    Else 
      ret = poll(@checkin,1,1)
    EndIf 
    If checkin\revents &(#POLLERR|#POLLHUP|#POLLNVAL)
      ret = -1
    EndIf 
    
    ProcedureReturn(ret)
  CompilerEndIf  
  
EndProcedure

Procedure Send(socket.i,*buf,size.l)
  Protected tx.l 
  Protected sent.l
  
  Repeat
    sent = send_(socket,*buf+tx,size-tx,0)
                     
    If sent = -1
      ProcedureReturn(-1)
    Else
      tx + sent
    EndIf   
   
  Until tx = size
  
  ProcedureReturn(tx)

EndProcedure

Procedure Close(socket)
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    shutdown_(socket,2) 
    closesocket_(socket)
  CompilerElse   
    Protected con_error.i
    Protected con_error_len.i
    getsockopt_(socket,#SOL_SOCKET,#SO_ERROR,@con_error,@con_error_len)
    shutdown_(socket,2)
    close_(socket);
  CompilerEndIf 
EndProcedure

Procedure SetSocketNonBlocking(socket,val.l)  
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    ioctlsocket_(socket , #FIONBIO, @val)
  CompilerElse 
    ioctl_(socket, $5421, @val)
  CompilerEndIf 
EndProcedure 

Procedure.s GetError()
  CompilerIf #PB_Compiler_OS = #PB_OS_Windows
    ProcedureReturn(Str(WSAGetLastError_()))
  CompilerElse    
    ProcedureReturn(PeekS(strerror_(PeekL(errno_location()))))
  CompilerEndIf 
EndProcedure 

Procedure ClientNWThread(*client.clientstuff)
  Protected socket_check
  Protected incoming_bytes
  Protected *rx_buf = AllocateMemory(65536)
  
  If *rx_buf
    Repeat
      PrintN("Server (Thread for "+IPString(*client\IP)+":"+Str(*client\port)+"): Loop - blocking = "+Str(*client\blocking))
      socket_check = SelectSocket(*client\socket,*client\blocking)
      If socket_check > 0
        incoming_bytes = recv_(*client\socket,*rx_buf,65536,0)
        If incoming_bytes > 0
          PrintN("Server (Thread for "+IPString(*client\IP)+":"+Str(*client\port)+"): Got "+Str(incoming_bytes)+" bytes from client: "+PeekS(*rx_buf,incoming_bytes))          
          Send(*client\socket,@"pong",StringByteLength("pong"))
        ElseIf incoming_bytes = 0
          PrintN("Server (Thread for "+IPString(*client\IP)+":"+Str(*client\port)+"): Client gracefully closed the connection")
          Break
        Else 
          PrintN("Server (Thread for "+IPString(*client\IP)+":"+Str(*client\port)+"): Socket error "+GetError())
          Break
        EndIf
      EndIf
    ForEver 
  EndIf 
  
  Close(*client\socket)
  FreeMemory(*client)
  ProcedureReturn(0)
EndProcedure   

Procedure ServerNWThread(*server.serverdata)
  Protected *host.HOSTENT
  Protected srv_sd.l
  Protected srv_addr.sockaddr_in
  Protected cli_sd.l
  Protected cli_addr.sockaddr_in
  Protected cli_addr_size.i = SizeOf(cli_addr)
  Protected *newclient.clientstuff
  Protected onvar.l = 1
    
  *host = gethostbyname_(*server\address.s)
  
  srv_addr\sin_family = #AF_INET
  srv_addr\sin_port = htons_(*server\port)
  srv_addr\sin_addr = PeekL(PeekL(*host\h_addr_list))
  
  srv_sd = SOCKET_(#AF_INET,#SOCK_STREAM,0)
  If Not *server\blocking 
    SetSocketNonblocking(srv_sd,1)
  EndIf 
  bind_(srv_sd,@srv_addr,SizeOf(srv_addr))
  listen_(srv_sd,5)
   
  Repeat
    PrintN("Server (Serverthread): Loop - blocking = "+Str(*server\blocking))
    cli_sd = accept_(srv_sd,@cli_addr,@cli_addr_size)
    If cli_sd > 0
      PrintN("Server (Serverthread): New client connected from "+IPString(PeekI(@cli_addr\sin_addr))+":"+Str(ntohs_(cli_addr\sin_port)))
      *newclient = AllocateMemory(SizeOf(clientstuff))
      If *newclient
        *newclient\socket = cli_sd
        *newclient\IP = PeekI(@cli_addr\sin_addr)
        *newclient\port = ntohs_(cli_addr\sin_port)
        *newclient\blocking = *server\blocking
        CreateThread(@ClientNWThread(),*newclient)
      EndIf 
    EndIf 
    If Not *server\blocking
      Delay(1)
    EndIf 
  ForEver 
EndProcedure 

Procedure TestClient(*void)
  Protected *host.HOSTENT
  Protected sd.l
  Protected addr.sockaddr_in
  Protected onvar.l = 1
  
  Protected socket_check
  Protected incoming_bytes
  Protected *rx_buf = AllocateMemory(65536)
    
  *host = gethostbyname_("localhost")
  
  addr\sin_family = #AF_INET
  addr\sin_port = htons_(443)
  addr\sin_addr = PeekL(PeekL(*host\h_addr_list))  
  
  sd = SOCKET_(#AF_INET,#SOCK_STREAM,0)
  ;SetSocketNonBlocking(socket,onvar.l) 
  
  If connect_(sd,@addr,SizeOf(sockaddr_in)) = 0
    Send(sd,@"ping",StringByteLength("ping"))
    
    Delay(1000)

    Repeat
      socket_check = SelectSocket(sd)
      If socket_check > 0
        incoming_bytes = recv_(sd,*rx_buf,65536,0)
        If incoming_bytes > 0
          PrintN("Testclient "+Str(*void)+" (this or another PC): Got "+Str(incoming_bytes)+" bytes from server: "+PeekS(*rx_buf,incoming_bytes))
          Break
        ElseIf incoming_bytes = 0
          PrintN("Testclient "+Str(*void)+" (this or another PC): Server gracefully closed the connection")
          Break
        Else
          PrintN("Testclient "+Str(*void)+" (this or another PC): Socket error "+GetError())
          Break
        EndIf
      EndIf
    ForEver  
    
    Close(sd)
    ProcedureReturn(0)
    
  EndIf 
  
EndProcedure 


InitNetwork()
OpenConsole()

Define newserver.serverdata
newserver\address = "0.0.0.0"
newserver\port = 443
newserver\blocking = 1 ; Switch to 0 for non-blocking

CreateThread(@ServerNWThread(),@newserver)
Delay(1000)
CreateThread(@TestClient(),1)
CreateThread(@TestClient(),2)
CreateThread(@TestClient(),3)

; Mainthread
Repeat
  ; Important other server stuff
  Delay(1000)
ForEver 

Re: ins Netzwerk senden mit Threads

Verfasst: 03.07.2015 22:08
von auser
Ach ja, hier noch ein Nachtrag nachdem du geschrieben hast daß du das lernen willst. Wenn du mit dem Kartenspiel fertig bist und ein bisschen tiefer in die Materie einsteigen willst dann hätte ich noch zwei Tips für dich:

1.) OpenSource Spiel mit samt 'ner schönen Trennung an wichtigen und unwichtigen Packeten (UDP), Prediction (wichtig!) usw. - Man kann zum Lernen (C++ bzw. älteren Versionen sind viel C) auch erst mal 'n Mod schreiben (macht Spaß):
https://www.teeworlds.com/ - Source: https://github.com/teeworlds/teeworlds

2.) Hervoragende Engine C++ und/oder grafisches Nodebasiertes Programmieren (was insbesondere zum Erstellen von Locomotion, Animations-Blendvorgängen usw. sehr effizient und übersichtlich ist):
https://www.unrealengine.com/what-is-unreal-engine-4 - Hat ein "Museum" in dem man mit 'ner 3D Figur rumlaufen kann in denen es unter anderem in zwei großen Sälen nur um die Netzwerk-Thematik geht und du bei jedem Stand die Blueprints dazu begutachten kannst. Wenn man hier schon mal selbst Hand am Source von 'nem UDP-Netzwerk-Spiel angelegt hat sollte es da ständig "Pling" machen ;). Das hat nicht nur 'ne Trennung von wichtig und unwichtig sondern geht noch weiter mit Events bzw. wann und warum man was wo hin verschicken sollte, welche Daten ein Client kriegen darf, über welche der Server die Herrschaft behalten muss usw.

Das soll nicht heißen PB ist schlecht für Spiele. Man kann sich auch die Abläufe angucken um 'ne Idee für das eigene Konzept in PB zu kriegen. Ich denke PB ist nicht unprofessionell weil es "Basic" ist. Ich schätze PB durchaus, weil es mich in vielen Belangen ganz einfach schnell macht. Und das ist viel Wert. Inifile schreiben? Fenster zeichnen? Drucken? Alles kein Problem mit PB. Da muss ich nicht erst irgendwelche Libs zusammen suchen sondern ich mach das einfach, weil das Rad hier und an anderen Stellen mit der PB-Lib schon mal erfunden wurde. Und darum komme ich auch immer wieder darauf zurück (auch wenn ich mir damit in Linux z.B. teilweise in der reichlich gedeckten C++ Welt wie ein Alien vorkomme).

Genau so wurde das Rad in Sachen Spiele-entwicklung aber an anderer Stelle schon (viel) weiter entwickelt. Oder anders gesagt, meine sämtlichen Anläufe zusammen gerechnet für Ogre 'ne brauchbare Pipeline zusammen zu basteln und alles andere zusammen zu kramen was man so braucht sind zeitlich gesehen horrend. In 'nem Bruchteil der Zeit hab ich mit der UDK nicht nur längst 'ne Import/Export Pipline stehen sondern 'ne Figur (trotz anderem Skelett) über Retargetting mit Animationen befüllt die vom Stand weg laufen gehen und von 'ner Kante springen kann und das nicht nur allein sondern Netzwerkfähig - wo ich mir den Kopf nicht mehr über Prediction (Link heise.de) usw. zerbrechen muss. Und das schon 'mit dem Start-template was nicht viel mehr ist als "Neues Dokument" in Word zu klicken. Trotz alledem (was dich schon sehr weit bringt und dir enorm viel Zeit erspart) ist bis zu 'nem fertigen Spiel noch tonnenweise zu tun und die Chance immer noch gegeben daß du nie fertig wirst. Du kannst dir da aber schon den Kopf über wesentlicheres und nicht minder komplexeres zerbrechen und der Vergleich ist in etwa so wie wenn du mit C erst mal 'nen Parser schreiben musst um ein Inifile zu laden (anstatt PB zu verwenden) und zig andere Sachen. Ob man eine Scheife mit "Next" oder Klammern beendet ist jetzt nicht wirklich _der_ große Unterschied.

Re: ins Netzwerk senden mit Threads

Verfasst: 04.07.2015 16:12
von Sven
Ich hab nochmal bißchen rumgespielt: Es hängt außerdem von der Datenrichtung ab:

Schickt der Client an den Server, und der Server liest den Speicher nicht aus, bleiben SendNotworkString und SendnetworkData hängen. Was den Client blockiert. Als Ergebnis kommt bei SendnetworkData immer die Anzahl aller Bytes, sprich die Pufferlänge zurück, oder garnichts, weil es ja hängt.

Schickt der Server an den Client, und der Client liest nicht aus, kommt die Anzahl übertragener Daten oder -1, wenn keine Daten übertragen wurden. Die Anzahl übertragener Daten war aber in meinen Tests immer die Bufferlänge. Es werden also auch entweder alle Daten übertragen oder garnichts. Auch der Client liest genau diese Anzahl übertragener Daten in der Summe, wenn auch nicht immer in einem Ritt.

Zum Beispiel schickt der Server:

10000 Byte
10000 Byte
10000 Byte
10000 Byte
-1
-1
-1
...

Der Client liest:
10000 Byte
5600 Byte
14400 Byte
10000 Byte

Die Summe 40000 Byte stimmt, aber die Paketlängen sind unterschiedlich.

Zumindest heißt das für mich erstmal, das ein hängender Client nicht den Server blockieren kann, wohl aber ein hängender Server den Client verrecken läßt.

Ist das einen Bug-Report wert, oder muß man halt damit leben?

Und nein, ich möchte mir eigentlich nicht meine eigene Network-lib schreiben, in die ich dann wieder andere blöde Fehler einbaue. Ich möchte einfach mit den PB-eigenen Befehlen arbeiten, dafür sind sie meiner Meinung nach da.

Re: ins Netzwerk senden mit Threads

Verfasst: 05.07.2015 01:41
von auser
Sven hat geschrieben: Ist das einen Bug-Report wert, oder muß man halt damit leben?
PB verhält sich hier genau so wie send() wenn es mit Blocking Sockets verwendet wird. Das ist völlig ok so. Ich glaub sogar daß du da nach (vielen, vielen) Minuten irgendwann mal 'n default Timeout kriegst.

Und nein, ich möchte mir eigentlich nicht meine eigene Network-lib schreiben, in die ich dann wieder andere blöde Fehler einbaue.
*zuckt mit den Schultern*

Code: Alles auswählen

Procedure SetSocketSendTimeout(socket,seconds)
  Protected tv.timeval
  tv\tv_sec = seconds
  setsockopt_(socket, #SOL_SOCKET, #SO_SNDTIMEO, @tv, SizeOf(timeval))
EndProcedure

; P.S. - Socket kriegtste mit ConnectionID()