Προς το περιεχόμενο

Multiplayer socket server σε C


BonJovi

Προτεινόμενες αναρτήσεις

Δημοσ.

Καλησπέρα,

 

έχω υλοποιήσει σε C ένα πολύ απλό socket TCP/IP server, στο οποίο μπορεί να συνδεθεί κάποιος μέσω του link http://www.miniplay.gr/socket/ και οτιδήποτε γράφει ο χρήστης, επιστρέφεται σε αντίστροφη σειρά μόνο στον ίδιο. Θέλω να εξελίξω το server ώστε να στέλνει ένα συγκεκριμένο μήνυμα σε όλους τους clients ταυτόχρονα (ή σε συγκεκριμένους που πληρούν συγκεκριμένες προϋποθέσεις). Δεν το θέλω απαραίτητα για chat αλλά π.χ. για ένα απλό multiplayer κουίζ ή κάποιο άλλο είδος παιχνιδιού.

 

Τι μεθόδους προτείνετε ώστε να υπάρχει η δυνατότητα αποστολής μηνυμάτων του server σε όλους τους clients ταυτόχρονα (ή σε συγκεκριμένους)?

 

Κάποιες σκέψεις που έχω κάνει αφορούν:

 

1. τη χρήση ενός απλού πίνακα στον οποίο θα αποθηκεύω στοιχεία για κάθε client

2. τη χρήση των συναρτήσεων FD_SET, FD_ISSET, FD_CLEAR κλπ οι οποίες χρησιμοποιούνται για τη δημιουργία ενός συνόλου που περιέχει τους file descriptors για κάθε client

3. τη χρήση κάποιας δομής δεδομένων (π.χ. συνδεδεμένης λίστας) στην οποία θα κρατάω συγκεκριμένα δεδομένα για κάθε client και θα διατρέχω τη δομή προκειμένου να στέλνω ένα μήνυμα σε όλους

4. τη χρήση νημάτων (threads) την οποία δεν έχω μελετήσει ιδιαίτερα ακόμα

 

Ποια μέθοδο (από τις παραπάνω ή κάποια άλλη) προτείνετε και για ποιο λόγο?

Δημοσ.
Καλησπέρα,

 

έχω υλοποιήσει σε C ένα πολύ απλό socket TCP/IP server, στο οποίο μπορεί να συνδεθεί κάποιος μέσω του link http://www.miniplay.gr/socket/ και οτιδήποτε γράφει ο χρήστης, επιστρέφεται σε αντίστροφη σειρά μόνο στον ίδιο. Θέλω να εξελίξω το server ώστε να στέλνει ένα συγκεκριμένο μήνυμα σε όλους τους clients ταυτόχρονα (ή σε συγκεκριμένους που πληρούν συγκεκριμένες προϋποθέσεις). Δεν το θέλω απαραίτητα για chat αλλά π.χ. για ένα απλό multiplayer κουίζ ή κάποιο άλλο είδος παιχνιδιού.

 

Τι μεθόδους προτείνετε ώστε να υπάρχει η δυνατότητα αποστολής μηνυμάτων του server σε όλους τους clients ταυτόχρονα (ή σε συγκεκριμένους)?

 

Κάποιες σκέψεις που έχω κάνει αφορούν:

 

1. τη χρήση ενός απλού πίνακα στον οποίο θα αποθηκεύω στοιχεία για κάθε client

2. τη χρήση των συναρτήσεων FD_SET, FD_ISSET, FD_CLEAR κλπ οι οποίες χρησιμοποιούνται για τη δημιουργία ενός συνόλου που περιέχει τους file descriptors για κάθε client

3. τη χρήση κάποιας δομής δεδομένων (π.χ. συνδεδεμένης λίστας) στην οποία θα κρατάω συγκεκριμένα δεδομένα για κάθε client και θα διατρέχω τη δομή προκειμένου να στέλνω ένα μήνυμα σε όλους

4. τη χρήση νημάτων (threads) την οποία δεν έχω μελετήσει ιδιαίτερα ακόμα

 

Ποια μέθοδο (από τις παραπάνω ή κάποια άλλη) προτείνετε και για ποιο λόγο?

 

Θα σου πρότεινα να δείς τα threads. Όχι για έναν λόγο, αλλά για άπειρους λόγους.

Πρώτα απ'όλα η απελευθέρωσή σου από την "συγχρονη" επικοινωνία. Τα αιτήματα των clients, σου έρχονται και διαχειρίζονται ξεχωριστά από κάθε thread. Έστω κι αν έρχονται ταυτόχρονα. Άρα λειτουργείς ασύγχρονα ως προς την διάθεση της, πληροφορίας σε σένα όπως και απο εσένα προς τους clients. Αυτό σημαίνει οτι μπορείς να διαχειρίζεσαι ταυτόχρονα πολλαπλά αιτήματα και αποκρίσεις χωρίς να έχεις να ανησυχείς για το αν κάποιο socket θα περιμένει, ή θα πέσει σε timeout ή οτιδήποτε.

 

Ένας δεύτερος λόγος είναι οτι, έχεις την πληροφορία του Clients σε κάθε instance του thread ξεχωριστά. Άρα δεν χρειάζεσαι να κρατάς δευτερεύουσες και τριτεύουσες πληροφορίες σε πίνακες, λίστες κλπ κλπ. Τα sockets είναι μπροστά σου να τα διαχειριστείς.

 

Τα threads δεν είναι δύσκολα να τα μάθεις. Είναι αρκετά βατά, θα έλεγα, και σε πολλές περιπτώσεις σου λύνουν τα χέρια. Άλλωστε στο Internet θα βρείς άπειρα παραδείγματα. Σου παραθέτω ένα χαρακτηριστικό παράδειγμα του CodeProject.com εδώ

Δημοσ.

Ευχαριστώ για την απάντηση και για το παράδειγμα. Διάβασα κάπου και για τη χρήση δυαδικών σημαφόρων (mutexes) στην περίπτωση των threads. Είναι απαραίτητη η χρήση αυτή ή είναι περιττή για ένα πολύ απλό multiplayer game? ( Ξέχασα, επίσης, να αναφέρω πριν γενικώς ότι ο server είναι linux και ο client θα είναι flash).

Δημοσ.
Ευχαριστώ για την απάντηση και για το παράδειγμα. Διάβασα κάπου και για τη χρήση δυαδικών σημαφόρων (mutexes) στην περίπτωση των threads. Είναι απαραίτητη η χρήση αυτή ή είναι περιττή για ένα πολύ απλό multiplayer game? ( Ξέχασα, επίσης, να αναφέρω πριν γενικώς ότι ο server είναι linux και ο client θα είναι flash).

 

Υπάρχουν διάφοροι τρόποι για να συγχρονίσεις threads. Τα Mutexes - Semaphores & τα Critical Sections είναι τρείς από αυτούς τους τρόπους. Συγχρονισμός χρειάζεται όταν υπάρχει ζήτημα, είτε να συγχρονίσεις 2 threads (το ένα περιμένει να τελειώσει το άλλο ή να ολοκληρώσει μια διεργασία κάποιο απο τα threads προκειμένου να συνεχίσουν τα υπόλοιπα πχ), ή όταν πρέπει να γίνει χρήση, γενικά, ενός πόρου του συστήματος, όπου η ταυτόχρονη πρόσβαση δεν μπορεί να επιτευχθεί από 2 threads, πχ ένα αρχείο, που θα σήμαινε deadlock άρα και ένα βασιλικό & απείρου κάλλους GPF - Crash - ή όπως το θες πες το.

 

Θα σου πρότεινα να ρίξεις μια ματιά σε αυτό το θαυμάσιο Link για Network Programming σε περιβάλλον Linux Network Programming. Θα βρείς τις απάντησεις σου μέσα στα κεφάλαια των παραδειγμάτων σε C που εμπεριέχονται.

 

Το multitasking με χρήση Threads περιγράφεται στο κεφάλαιο 7 των παραδειγμάτων...

:-)

Δημοσ.

Λοιπόν, έριξα μια ματιά στην υλοποίηση που έχει το παραπάνω link σχετικά με τη χρήση των mutexes και νομίζω ήταν κατανοητό. Αυτή τη στιγμή σκέφτομαι δύο είδη παιχνιδιών... ένα με κουίζ στο οποίο θα απαντάνε όλοι οι χρήστες όσο τρέχει ο χρόνος και ένα ανάμεσα σε 2 παίχτες που θα είναι turn based (π.χ. σαν το Μάντεψε Ποιος http://www.miniplay.gr/?view=game&gid=63 σε multiplayer φυσικά, όπου ένας παίκτης θα μπορεί να προ(σ)καλεί κάποιον άλλον από τη λίστα με τους διαθέσιμους χρήστες online).

 

Χρειάζεται, κατά τη γνώμη σου, να κάνω συγχρονισμό και να χρησιμοποιήσω π.χ. mutexes ή αρκεί να κάνω ένα απλό tcp socket server χρησιμοποιώντας τη fork() για την εξυπηρέτηση κάθε client?

 

Αν είναι καλύτερα τα threads, πώς θα μπορούσα να το κάνω? Να δημιουργείται π.χ. ένα thread για κάθε client που συνδέεται? Αν ένας παίχτης (στο 1ο παιχνίδι) στέλνει μία απάντηση, ποιο thread θα αναλαμβάνει να τη στείλει σε όλους τους clients? Εκείνο που δημιουργήθηκε κατά τη σύνδεση του παίχτη που έστειλε την απάντηση ή θα υπάρχει ένα thread - γονέας που εξυπηρετεί όλους τους clients?

 

Δεν ξέρω αν είναι μπερδεμένες οι ερωτήσεις μου, απλά προσπαθώ κι εγώ να τα βάλω σε μία σειρά.

Δημοσ.
Λοιπόν, έριξα μια ματιά στην υλοποίηση που έχει το παραπάνω link σχετικά με τη χρήση των mutexes και νομίζω ήταν κατανοητό. Αυτή τη στιγμή σκέφτομαι δύο είδη παιχνιδιών... ένα με κουίζ στο οποίο θα απαντάνε όλοι οι χρήστες όσο τρέχει ο χρόνος και ένα ανάμεσα σε 2 παίχτες που θα είναι turn based (π.χ. σαν το Μάντεψε Ποιος http://www.miniplay.gr/?view=game&gid=63 σε multiplayer φυσικά, όπου ένας παίκτης θα μπορεί να προ(σ)καλεί κάποιον άλλον από τη λίστα με τους διαθέσιμους χρήστες online).

 

Χρειάζεται, κατά τη γνώμη σου, να κάνω συγχρονισμό και να χρησιμοποιήσω π.χ. mutexes ή αρκεί να κάνω ένα απλό tcp socket server χρησιμοποιώντας τη fork() για την εξυπηρέτηση κάθε client?

 

Αν είναι καλύτερα τα threads, πώς θα μπορούσα να το κάνω? Να δημιουργείται π.χ. ένα thread για κάθε client που συνδέεται? Αν ένας παίχτης (στο 1ο παιχνίδι) στέλνει μία απάντηση, ποιο thread θα αναλαμβάνει να τη στείλει σε όλους τους clients? Εκείνο που δημιουργήθηκε κατά τη σύνδεση του παίχτη που έστειλε την απάντηση ή θα υπάρχει ένα thread - γονέας που εξυπηρετεί όλους τους clients?

 

Δεν ξέρω αν είναι μπερδεμένες οι ερωτήσεις μου, απλά προσπαθώ κι εγώ να τα βάλω σε μία σειρά.

 

Περίμενε να βάλουμε μια βάση σε όλο αυτό ..

 

Η έννοια του threading σε αυτή τη περίπτωση όντως κάνει apply σε ένα thread ανά socket. Οι απαντήσεις μπορούν να στέλνονται από το κάθε socket στην εκάστοτε σύνδεση που περιγράφουν. Μπορείς να το κάνεις με διάφορους τρόπους ανάλογα με το τί σε βολεύει περισσότερο.

 

Για να σε βοηθήσω περισσότερο θα σου πρότεινα να δείς πως λειτουργούν τα Indy Components εδώ όπου όλοι οι servers (TCP/UDP/HTTP/ κλπ κλπ) είναι όλοι multithreaded (βάσει ιεραρχίας των components). Είναι Open Source υποστηρίζονται και απο Linux, οπότε μπορείς να δείς και κώδικα υλοποίησης.

Δημοσ.

 

Αν είναι καλύτερα τα threads, πώς θα μπορούσα να το κάνω? Να δημιουργείται π.χ. ένα thread για κάθε client που συνδέεται? Αν ένας παίχτης (στο 1ο παιχνίδι) στέλνει μία απάντηση, ποιο thread θα αναλαμβάνει να τη στείλει σε όλους τους clients? Εκείνο που δημιουργήθηκε κατά τη σύνδεση του παίχτη που έστειλε την απάντηση ή θα υπάρχει ένα thread - γονέας που εξυπηρετεί όλους τους clients?

 

Δεν ξέρω αν είναι μπερδεμένες οι ερωτήσεις μου, απλά προσπαθώ κι εγώ να τα βάλω σε μία σειρά.

 

Η socket απο μονη της ειναι σειριακη (καλυτερα "blocking socket"). Δηλαδη οταν εχει μια συνδεση server και client1, ο server θα δεχεται ΜΟΝΟ τα πακετα του client1 με αποτελεσμα να υπαρχει προβλημα στη περιπτωση που θα επιχειρησει ο client2 να συνδεθει με το server. Που ειναι αυτο το προβλημα;

Εστω ο clinet1 θελει να στειλει 100 πακετα και ο client2 1 πακετο ο server σου θα δεχτη τη συνδεση με το client1 και θα αρχησει να περνει πακετα... 1ο πακετο 2ο πακετο 3,4,5 και καπου εκει θα ερθει το πακετου του client2 αλλα επειδη ειναι blocking socket αυτο το πακετο δεν θα παει στο προγραμμα σου αλλα σε καποιον buffer του συστηματος και θα περιμενει το επομενο accept. Και εδω ειναι το προβλημα, ενω εχεις το πακετου του client2 δεν μπορεις να το χρησιμοποιησεις.

 

Εδω θες την βοηθεια του νηματος. Το νημα θα κανει μια απλη δουλεια, θα μαζευει πακετα απο την χ συνδεση (πχ server-client1), και οταν με το καλο μαζεψει αυτα που θελεις, τοτε θα σου στειλει ενα callback να σου πει οτι τελειωσε και οτι τα δεδομενα ειναι εκει. Αυτη ειναι η δουλεια του.

 

Φυσικα αυτο υπαρχει σε OS layer και ετσι αποφευγεις τα threads, αλλα δεν ξερω πως δουλευει το async socket api στα linux :-( (Μια λεξη κλειδι ειναι η select)

Δημοσ.

Από ότι είδα σε μερικά παραδείγμα για απλό chat με multiple users, όλα χρησιμοποιούσαν τη συνάρτηση select. Π.χ.

 

/* Block until input arrives on one or more active sockets. */

select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL)

 

Κάπου, μάλιστα, είδα και τη χρήση της συνάρτησης setsockopt. Π.χ.

 

/* set master socket to allow multiple connections */

setsockopt (master_socket, SOL_SOCKET, SO_REUSEADDR, (char *)&opt, sizeof(opt))

Δημοσ.
Από ότι είδα σε μερικά παραδείγμα για απλό chat με multiple users, όλα χρησιμοποιούσαν τη συνάρτηση select. Π.χ.

 

99% των προγραμμάτων κάνουν χρήση non-blocking sockets σε συνδυασμό με κάποια από τις συναρτήσεις select/poll/epoll/kqueue/devpoll κτλ. Με αυτόν τον τρόπο (το λεγόμενο multiplexing) μπορείς με ένα μόνο process-thread να διαχειριστείς έναν μεγάλο αριθμό από clients.

 

Το πρόβλημα στην προσέγγιση με threads είναι ότι εάν για παράδειγμα συνδεθούν 200 clients ταυτόχρονα θα πρέπει να είναι ενεργά 200 threads και σιγά σιγά αρχίζει να χάνεται αρκετό CPU time στο context switch μεταξύ των threads. Εκτός απο αυτό χρειάζεται προσεκτικός συγχρονισμός μεταξύ των threads και το debugging γίνεται δυσκολότερο..

 

Θα σου πρότεινα να κάτσεις να μελετήσεις απλές εφαρμογές της select με non-blocking sockets ώστε να κατανοήσεις τον τρόπο λειτουργίας (event based programming) και σιγά σιγά να υλοποιήσεις την εφαρμογή που θέλεις. Ότι χρειαστείς εδώ είμαστε για να βοηθήσουμε!

 

ΥΓ. Και ένα link που ίσως βοηθήσει: http://beej.us/guide/bgnet/output/html/multipage/advanced.html#select

 

Καλή τύχη

Δημοσ.

Σε ευχαριστώ, DiAvOl! Πολύ ενδιαφέρον έχει το θέμα με το CPU time όσον αφορά την περίπτωση των threads. Τη χρήση της select την είδα σε αρκετά παραδείγματα και μάλιστα σε 1-2 γράφουν σε σχόλιο στον κώδικα ότι γίνεται χρήση non-blocking sets. Πιθανόν να είναι και υπεραρκετό για μία απλή εφαρμογή σαν εκείνη που θέλω να υλοποιήσω. Έχουν ενδιαφέρον, όλες οι προσεγγίσεις πάντως που αναφέρει ο καθένας και τις μελετάω. By the way, πολύ καλό και το link :)

Αρχειοθετημένο

Αυτό το θέμα έχει αρχειοθετηθεί και είναι κλειστό για περαιτέρω απαντήσεις.

  • Δημιουργία νέου...