Va bene, prima di tutto, se hai qualcosa e funziona, di solito è una buona idea lasciarlo così. Perché riparare ciò che non è rotto?
Ma se hai problemi e vuoi davvero riscrivere il tuo codice di rete, penso che tu abbia quattro scelte principali:
- Codice di blocco multithread (cosa stai facendo in questo momento)
- Prese senza blocco con notifica attivata dal livello
- Prese non bloccanti con notifica di modifica della disponibilità
- Zoccoli asincroni
Avendo scritto molti client e server multiplayer (dal peer-to-peer al multiplayer di massa), mi piace pensare che l'opzione 2 porti alla minima complessità, con prestazioni piuttosto buone, sia per le parti del gioco che per quelle del server e del client. Come secondo vicino, vorrei andare con l'opzione 4, ma questo di solito richiede di ripensare l'intero programma e lo trovo principalmente utile per i server e non per i client.
In particolare, vorrei sconsigliare il blocco dei socket in un ambiente multithread, poiché in genere ciò comporta il blocco e altre funzionalità di sincronizzazione che non solo aumentano notevolmente la complessità del codice, ma possono anche degradarne le prestazioni, poiché alcuni thread attendono altri.
Ma soprattutto, la maggior parte delle implementazioni socket (e la maggior parte delle implementazioni I / O in questo) sono non bloccanti al livello più basso. Le operazioni di blocco sono semplicemente fornite per semplificare lo sviluppo di programmi banali. Quando un socket sta bloccando, la CPU in quel thread è completamente inattiva, quindi perché creare un'astrazione non bloccante sull'astrazione bloccante su un'attività già non bloccante?
La programmazione di socket non bloccanti è un po 'scoraggiante se non l'hai provata, ma risulta abbastanza semplice e se stai già eseguendo il polling di input, hai già la mentalità di fare socket non bloccanti.
La prima cosa che vuoi fare è impostare il socket su non-blocking. Lo fai con fcntl()
.
Dopo di che, prima di farlo send()
, recv()
, sendto()
, recvfrom()
, accept()
( connect()
è un po 'diverso), o altre chiamate che potrebbero bloccare il filo, si chiama select()
sul socket. select()
indica se è possibile eseguire o meno un'operazione di lettura o scrittura successiva sul socket senza bloccarla. In tal caso, puoi tranquillamente eseguire l'operazione desiderata e il socket non si bloccherà.
Includere questo in un gioco è abbastanza semplice. Se hai già un loop di gioco, ad esempio in questo modo:
while game_is_running do
poll_input()
update_world()
do_sounds()
draw_world()
end
potresti cambiarlo in questo modo:
while game_is_running do
poll_input()
read_network()
update_world()
do_sounds()
write_network()
draw_world()
end
dove
function read_network()
while select(socket, READ) do
game.net_input.enqueue(recv(socket))
end
end
e
function write_network()
while not game.net_output.empty and select(socket, WRITE) do
send(socket, game.net_output.dequeue())
end
end
In termini di risorse, l'unico libro che penso che tutti debbano avere nei loro scaffali, anche se è l'unico libro che hanno, è " Unix Network Programming, Vol 1. " del compianto Richard Stevens. Non importa se si esegue Windows o altri sistemi operativi o la programmazione socket lingua. Non pensare di aver compreso gli zoccoli finché non hai letto questo libro.
Un'altra pagina in cui è possibile trovare una panoramica generale delle soluzioni disponibili in termini di programmazione multipla socket (principalmente rilevante per la programmazione server) è questa pagina .