Φροντιστήριο Τζόνι του Winsock

0

Αν έχετε σκόπιμα έφτασε στο Winsock φροντιστήριο, έχεις πιθανότατα βρήκε την ιδέα από τις δικές σας εφαρμογές στην επικοινωνία μέσω του Διαδικτύου, όπως τη συναρπαστική προοπτική, όπως εγώ. Ήίσως κάποιος άλλος έχει βρει την προοπτική εξίσου ενδιαφέρουσα και σας έχει ανατεθεί να φέρει αυτό το όραμα σε πραγματικότητα. Σε κάθε περίπτωση, το Winsock υπηρεσία δικτύου και για αυτό το σεμινάριο θα σας βοηθήσει στην επίτευξη των στόχων σας των εμπορικών επιχειρήσεων, απλά να εξερευνήσετε το βασίλειο του προγραμματισμού του δικτύου για προσωπική χρήση, ή κάτι στο μεταξύ.

Εδώ είναι τι θα πρέπει να καλύπτει:

Αν και μπορεί να είναι πρόθυμοι να φθάσουν σε αυτό το δέος-εμπνέει σημείο στο οποίο η εφαρμογή σας με επιτυχία, κάνει την πρώτη σύνδεση, πρέπει να γνωρίζετε τις έννοιες πίσω από τον κώδικα. Προσπαθήστε να αποφύγετε απλά το χειρισμό του δεδομένου κώδικα για να ταιριάζει με άμεσες ανάγκες σας και αντί να αναγνωρίσει τις απαιτήσεις της εφαρμογής σας και μόνο στη συνέχεια να εφαρμόσει ό, τι φαίνεται να είναι η καλύτερη λύση. Αρκετά από το Ζεν της Ανάπτυξης Λογισμικού συμβουλές για τώρα, ας κάνουμε τον προγραμματισμό του δικτύου

Μη διστάσετε να κατεβάσετε το ολόκληρο το φροντιστήριο λίστα κώδικα. Να θυμάστε ότι κάθε κωδικός που παρουσιάζονται σε αυτό το σεμινάριο, θα πρέπει να συνδέεται με το Winsock βιβλιοθήκη, συνήθως wsock32.lib ή κάτι παρόμοιο όνομα. Επίσης, όταν χρησιμοποιείτε κώδικα, ακριβώς όπως παρουσιάζονται στο φροντιστήριο στο δικό σας IDE (DevC++, Microsoft VC++, C++ Builder, κ. λπ.), επιλέξτε να δημιουργήσετε ένα Windows έργου με WinMain() για να αποφύγετε λάθη.

Δημιουργώντας ένα Ακρόαση Καρυδάκι (Listening Socket)

Εφαρμογές συντήρησης εκτός μηχανές ονομάζονται διακομιστές. Εφαρμογές διακομιστή ακούσει για τους πελάτες από την προετοιμασία μία ή περισσότερες υποδοχές ακρόασης. Όταν ένας υπολογιστής-πελάτης συνδέεται σε μία από αυτές τις υποδοχές ακρόασης, ο server λαμβάνει μια ειδοποίηση από το Winsock, δέχεται τη σύνδεση, και αρχίζει να αποστέλλει και να υποκλέψει τα μηνύματα προς και από το νέο πρόγραμμα-πελάτη. Ίσως η πιο απλοϊκή μέθοδος με την οποία servers χειριστεί πολλαπλές πελάτες της είναι να γεννήσει ένα νέο νήμα για κάθε σύνδεση υπολογιστή-πελάτη. Αυτό το μοντέλο διακομιστή πιο συχνά χρησιμοποιεί κλείδωμα υποδοχές, το οποίο διακόψετε προσωρινά να περιμένετε για τα εισερχόμενα δεδομένα, μια νέα σύνδεση, και άλλες εκδηλώσεις του δικτύου. Πρώτα, ας προσδιορίσουμε κάποιες δομές θα πρέπει να προετοιμάσει μια υποδοχή αποκλεισμού:

Το πρώτο πεδίο είναι ο τύπος του πρωτοκόλλου, η οποία είναι συνήθως AF_INET (TCP/IP). Ως listening socket δεν ασχολείται με τη διεύθυνση δικτύου του υπολογιστή στον οποίο βρίσκεται, Winsock εκχωρεί αυτόματα μια διεύθυνση IP και τον αριθμό θύρας για να ακούτε τις υποδοχές κατά την δημιουργία.

Θα φτιάξουμε το πρώτο μας ακούει server με τις παραπάνω δομές και ένα μικρό στρατό των λειτουργιών του δικτύου:

#include <windows.h>

#include <winsock.h>

#include <stdio.h>



#define NETWORK_ERROR -1

#define NETWORK_OK     0



void ReportError(int, const char *);





int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)

{

	WORD sockVersion;

	WSADATA wsaData;

	int nret;



	sockVersion = MAKEWORD(1, 1);			// We'd like Winsock version 1.1





	// We begin by initializing Winsock

	WSAStartup(sockVersion, &wsaData);





	// Next, create the listening socket

	SOCKET listeningSocket;



	listeningSocket = socket(AF_INET,		// Go over TCP/IP

			         SOCK_STREAM,   	// This is a stream-oriented socket

				 IPPROTO_TCP);		// Use TCP rather than UDP



	if (listeningSocket == INVALID_SOCKET)

	{

		nret = WSAGetLastError();		// Get a more detailed error

		ReportError(nret, "socket()");		// Report the error with our custom function



		WSACleanup();				// Shutdown Winsock

		return NETWORK_ERROR;			// Return an error value

	}





	// Use a SOCKADDR_IN struct to fill in address information

	SOCKADDR_IN serverInfo;



	serverInfo.sin_family = AF_INET;

	serverInfo.sin_addr.s_addr = INADDR_ANY;	// Since this socket is listening for connections,

							// any local address will do

	serverInfo.sin_port = htons(8888);		// Convert integer 8888 to network-byte order

							// and insert into the port field





	// Bind the socket to our local server address

	nret = bind(listeningSocket, (LPSOCKADDR)&serverInfo, sizeof(struct sockaddr));



	if (nret == SOCKET_ERROR)

	{

		nret = WSAGetLastError();

		ReportError(nret, "bind()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Make the socket listen

	nret = listen(listeningSocket, 10);		// Up to 10 connections may wait at any

							// one time to be accept()'ed



	if (nret == SOCKET_ERROR)

	{

		nret = WSAGetLastError();

		ReportError(nret, "listen()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Wait for a client

	SOCKET theClient;



	theClient = accept(listeningSocket,

			   NULL,			// Optionally, address of a SOCKADDR_IN struct

			   NULL);			// Optionally, address of variable containing

							// sizeof ( struct SOCKADDR_IN )



	if (theClient == INVALID_SOCKET)

	{

		nret = WSAGetLastError();

		ReportError(nret, "accept()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Send and receive from the client, and finally,

	closesocket(theClient);

	closesocket(listeningSocket);





	// Shutdown Winsock

	WSACleanup();

	return NETWORK_OK;

}





void ReportError(int errorCode, const char *whichFunc)

{

   char errorMsg[92];					// Declare a buffer to hold

							// the generated error message

   

   ZeroMemory(errorMsg, 92);				// Automatically NULL-terminate the string



   // The following line copies the phrase, whichFunc string, and integer errorCode into the buffer

   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);



   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);

}

Ένα πράγμα που μπορείτε να παρατηρήσετε αμέσως για τον κώδικα είναι η ποσότητα προσπάθεια για τον έλεγχο σφαλμάτων. Κάθε φορά που παρουσιάζεται ένα σφάλμα, ο κώδικας αποκτά ένα συγκεκριμένο κωδικό σφάλματος με WSAGetLastError() και αποθηκεύει το αποτέλεσμα σε nret. Ο κωδικός σφάλματος είναι στη συνέχεια αποστέλλονται μαζί με μια συμβολοσειρά που δηλώνει το όνομα της η λειτουργία απέτυχε σε μια προσαρμοσμένη λειτουργία που ονομάζεται ReportError(). Εκεί, ένα μήνυμα λάθους που κατασκευάζονται και παρουσιάζονται στο χρήστη με ένα τηλεφώνημα να MessageBox(), η οποία αποτελεί μέρος του προτύπου WinAPI. Για παράδειγμα, είχα listen() απέτυχε με κωδικό σφάλματος του 10093 (που ορίζεται ως WSANOTINITIALISED), το τελικό σφάλμα συμβολοσειρά, θα “Call to listen() returned error 10093!”. Εσύ, η συνετή προγραμματιστής, θα στη συνέχεια να εξετάσουμε τον κώδικα και να ανακαλύψετε ότι το σφάλμα επειδή μια επιτυχημένη κλήση για να WSAStartup() δεν είχε ακόμη πραγματοποιηθεί.

Αλεξάνταρ Παβλόφ να επεκταθεί αυτό ReportError() να περιλαμβάνουν περιγραφές για περίπου μια ντουζίνα κοινή υποδοχή λάθη. Χρησιμοποιώντας την αναβαθμισμένη έκδοση, θα πρέπει πλέον να αναζητήσετε ό, τι ο κώδικας σημαίνει, και το πρόγραμμά σας γίνεται πολύ πιο φιλικό προς το χρήστη με πολύ λίγη προσπάθεια από μέρους σας.

Επίσης, περιλαμβάνονται ορίζει για NETWORK_ERROR και NETWORK_OK. Αυτά μπορεί να είναι χρήσιμο κατά τον έλεγχο, η τιμή επιστροφής της δικής σας λειτουργίες δικτύωσης. Αν σας λειτουργίες επέστρεψε μία από αυτές τις αξίες, η καλούσα συνάρτηση θα μπορούσε να εκτελέσει μια απλή ισότητα δοκιμή για να αποκαλύψει τυχόν σφάλματα: αν (myNetworkingFunction() == NETWORK_ERROR) {…}. Η καλούσα συνάρτηση θα μπορούσε στη συνέχεια να αποκτήσουν ένα συγκεκριμένο κώδικα με WSAGetLastError() και να χειριστεί το σφάλμα αναλόγως. Τελικά, την εφαρμογή ενός καλού χειρισμού σφαλμάτων συστήματος τώρα θα σας εξοικονομήσει πολλές ημέρες ή εβδομάδες το χρόνο ανάπτυξης όπως θα γνωρίζετε αμέσως γιατί το πρόγραμμα έχει αποτύχει.

Εκτός από την επιστροφή ενός νέου πελάτη, σύνδεση, accept() επιτρέπει στο διακομιστή να εξαγάγετε πληροφορίες σχετικά με τον πελάτη και όχι με μεθόδους που απαιτούν επιπλέον κλήσεις συναρτήσεων ή το χρόνο (το οποίο μπορεί να γίνει ένα ζήτημα στο διακομιστές παιχνίδι, όπου η ταχύτητα του δέχεται βρόχων είναι ιδιαίτερα κρίσιμο). Για να επωφεληθείτε από αυτή τη λειτουργία, να περάσει στην διεύθυνση struct sockaddr_in cast σε ένα sockaddr δείκτη, δηλαδή (LPSOCKADDR)&aSockaddrInStructure. Επίσης, να δηλώσετε μια μεταβλητή ακέραιου, ορίστε την τιμή από το int στο μέγεθος του struct sockaddr, και να περάσει τη διεύθυνση του ακέραιος ως η τρίτη παράμετρος. Εάν η διεύθυνση πληροφοριών είναι να επιστραφεί μετά την κλήση της συνάρτησης, το μήκος της παραμέτρου πρέπει να είναι παρόντες.

jdarnold μας προειδοποιεί να μην πιστεύουν την τεκμηρίωση του MSDN σχετικά με αυτή την τρίτη παράμετρο: “Το MSDN έγγραφα υποδηλώνει ότι δεν πρέπει να περάσει σε addrlen, ότι είναι απλά μια προαιρετική παράμετρο εξόδου, αλλά κάνουν λάθος. Εισερχόμενες λέει πόσα bytes είναι το sockaddr ρυθμιστής, και εξερχόμενη [Winsock] συμπληρώνει πόσοι [Winsock] χρησιμοποιούνται. Αν περάσει το μηδέν ως το len [Winsock] δεν θα αγγίξει το ρυθμιστής.”

Αυτό δεν είναι πολύ από ένα server από το να περιμένει για ένα μόνο χρήστη για να συνδεθείτε και, στη συνέχεια, αμέσως αποσυνδέεται, αλλά αυτό είναι το πιο βασικό σχεδιασμό. Για να ξεκαθαρίσω τα πράγματα, μια κλήση για να WSAStartup() περιλαμβάνει μια λέξη που προσδιορίζει ποια έκδοση θέλετε να φορτώσετε (σε αυτή την περίπτωση είναι 1.1) και τη διεύθυνση του WSADATA δομή. Στη συνέχεια, θα καλύψει το πώς να συνδεθείτε με άλλους υπολογιστές.

Κάνοντας Τις Δικές Σας Συνδέσεις

Δημιουργώντας μια υποδοχή για να συνδεθεί με κάποιον άλλο να χρησιμοποιεί τις περισσότερες από τις ίδιες λειτουργίες, με εξαίρεση το struct HOSTENT:

Έτσι, ας πάρει το δικαίωμα να τον κώδικα:

#include <windows.h>

#include <winsock.h>

#include <stdio.h>



#define NETWORK_ERROR -1

#define NETWORK_OK     0



void ReportError(int, const char *);





int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmd, int nShow)

{

	WORD sockVersion;

	WSADATA wsaData;

	int nret;



	sockVersion = MAKEWORD(1, 1);





	// Initialize Winsock as before

	WSAStartup(sockVersion, &wsaData);





	// Store information about the server

	LPHOSTENT hostEntry;



	hostEntry = gethostbyname("www.yahoo.com");	// Specifying the server by its name;

							// another option: gethostbyaddr()



	if (!hostEntry)

	{

		nret = WSAGetLastError();

		ReportError(nret, "gethostbyname()");	// Report the error as before



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Create the socket

	SOCKET theSocket;



	theSocket = socket(AF_INET,			// Go over TCP/IP

			   SOCK_STREAM,			// This is a stream-oriented socket

			   IPPROTO_TCP);		// Use TCP rather than UDP



	if (theSocket == INVALID_SOCKET)

	{

		nret = WSAGetLastError();

		ReportError(nret, "socket()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Fill a SOCKADDR_IN struct with address information

	SOCKADDR_IN serverInfo;



	serverInfo.sin_family = AF_INET;



	// At this point, we've successfully retrieved vital information about the server,

	// including its hostname, aliases, and IP addresses.  Wait; how could a single

	// computer have multiple addresses, and exactly what is the following line doing?

	// See the explanation below.



	serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);



	serverInfo.sin_port = htons(80);		// Change to network-byte order and

							// insert into port field





	// Connect to the server

	nret = connect(theSocket,

		       (LPSOCKADDR)&serverInfo,

		       sizeof(struct sockaddr));



	if (nret == SOCKET_ERROR)

	{

		nret = WSAGetLastError();

		ReportError(nret, "connect()");



		WSACleanup();

		return NETWORK_ERROR;

	}





	// Successfully connected!





	// Send/receive, then cleanup:

	closesocket(theSocket);

	WSACleanup();

}





void ReportError(int errorCode, const char *whichFunc)

{

   char errorMsg[92];					// Declare a buffer to hold

							// the generated error message

   

   ZeroMemory(errorMsg, 92);				// Automatically NULL-terminate the string



   // The following line copies the phrase, whichFunc string, and integer errorCode into the buffer

   sprintf(errorMsg, "Call to %s returned error %d!", (char *)whichFunc, errorCode);



   MessageBox(NULL, errorMsg, "socketIndication", MB_OK);

}

Πιο περίπλοκη γραμμή στη λίστα είναι η εξής:

serverInfo.sin_addr = *((LPIN_ADDR)*hostEntry->h_addr_list);

επειδή εκτελεί πολλές λειτουργίες—ένας από τους σχετικά hidden—με τη μία. Ας το πάρουμε εκτός βήμα προς βήμα:

Στο h_addr_list μέλος του struct HOSTENT είναι βασικά ορίζεται ως char **h_addr_list, η οποία είναι μια σειρά από strings, ή char *s. gethostbyname() προσδιορίζονται και να αντιγραφούν όλες τις γνωστές διευθύνσεις του διακομιστή σε αυτή τη λίστα. Ωστόσο, η έννοια της πολλαπλές διευθύνσεις ουσιαστικά νόημα? Στην πραγματικότητα, αυτό κάνει. Υπολογιστή σας, στην πραγματικότητα, έχει μια σειρά από γενικές δικτύωσης διευθύνσεις. Την διεύθυνσή σας στο Internet μπορεί να 205.182.67.96, LAN σας διεύθυνση μπορεί να 10.0.0.2, και όλοι οι υπολογιστές στους οποίους είναι εγκατεστημένα τα Windows, φυσικά, “loopback” διεύθυνση 127.0.0.1, που χρησιμοποιείται από τον υπολογιστή για να αναφέρεται στον εαυτό του στο τοπικό δίκτυο. Το ίδιο ισχύει και στο χώρο των διευθύνσεων Internet ή IP, το οποίο είναι ο λόγος για μια λίστα, απαιτείται αντί χώρο αποθήκευσης για μια ενιαία διεύθυνση. Σημειώστε ότι η προτιμώμενη διεύθυνση, που είναι το πιο προσιτό διεύθυνση, είναι πάντα αντιγράφεται στο πρώτο στοιχείο της λίστας, ακολουθούμενη από τη δεύτερη προτιμώμενη ή άλλες διευθύνσεις.

Τι είναι *hostEntry->h_addr_list? Μπορείτε να μαντέψετε ότι ο σεβασμός χειριστή (*) χρησιμοποιείται για να αποκτήσετε πρόσβαση σε μια ενιαία διεύθυνση στη λίστα. Ωστόσο, παραλείποντας να παράσχει ένα συγκεκριμένο δείκτη, η dereference λειτουργία αυτόματα αποκαλύπτει την πρώτη, την προτιμώμενη διεύθυνση. Το συγκεκριμένο τμήμα είναι ισοδύναμο με *hostEntry->h_addr_list[0], η οποία είναι εγγυημένη για να υπάρχει, επειδή ο διακομιστής πρέπει να έχει τουλάχιστον μία διεύθυνση.

Την επόμενη, το char * επέστρεψε από το dereferencing λειτουργία ρίχνει σε μια in_addr * ή LPIN_ADDR. Τέλος, άλλη ένδειξη σεβασμού λειτουργία εκτελείται για να επιστρέψει το struct in_addr που αναφέρονται από το δείκτη, η οποία μπορεί να κρατήσει μόνο μια ενιαία διεύθυνση. Το αποτέλεσμα struct in_addr είναι στη συνέχεια να ανατεθεί σε serverInfo.sin_addr. Η επακόλουθη connect() παίρνει μία διεύθυνση ως παράμετρος που διαμορφώνει μια σύνδεση με το διακομιστή.

Εάν η διεύθυνση IP του διακομιστή είναι γνωστό, έγκυρο HOSTENT μπορεί να επιτευχθεί μέσω της χρήσης gethostbyaddr() (σε αντίθεση με την gethostbyname() που χρησιμοποιείται στην προηγούμενη λίστα):

LPHOSTENT hostEntry;

in_addr iaHost;



iaHost.s_addr = inet_addr("204.52.135.52");



hostEntry = gethostbyaddr((const char *)&iaHost, sizeof(struct in_addr), AF_INET);



if (!hostEntry)

{

	// Handle accordingly

}

Σε αυτή την περίπτωση, η inet_addr() χρησιμοποιείται για να αντιγράψετε μια συμβολοσειρά που δηλώνει την διεύθυνση IP απευθείας σε μια struct in_addr. Στη συνέχεια, η διεύθυνση του struct είναι ρίχνει σε ένα const char*, όπως απαιτείται από το gethostbyaddr(). Και οι δύο μέθοδοι αναφέρονται ως επίλυση του διεύθυνση διακομιστή από το Winsock επιστρέφει την πλήρη διεύθυνση εγγραφές από ένα μέρος των πληροφοριών.

Μερικές επιπλέον σημειώσεις: port 80 χρησιμοποιήθηκε απλά και μόνο επειδή το Internet web σελίδα μεταφορές προκύψει πάνω από αυτό το λιμάνι. Αν ήταν να στείλετε ένα string σε ένα διακομιστή web να ζητήσει ένα συγκεκριμένο αρχείο και να προσπαθήσει να πάρει κάτι πίσω, θα έχουμε ένα πολύ απλό πρόγραμμα περιήγησης στο web. Φυσικά, αυτή η συμβολοσειρά πρέπει να περιλαμβάνει ένα πλήρες HTTP εντολή. Είναι υπέροχο που μπορούμε να ακούσουμε και να συνδεθείτε με άλλους υπολογιστές, αλλά η επικοινωνία περιλαμβάνει επίσης την αποστολή και λήψη.

Αποστολή και Λήψη

Η αποστολή γίνεται, σε βολική αρκετά, από την send() λειτουργία:

int send(

  SOCKET s,

  const char * FAR buf,

  int len,

  int flags

);

Βασικά θα αντίγραφο ό, τι ήθελε σε ένα ρυθμιστής και να χρησιμοποιήσετε το send() λειτουργία σε μια συνδεδεμένη υποδοχή για να κάνει τα δεδομένα που πάει στο άλλο άκρο:

char buffer[256];		// Declaring a buffer on the stack

char *buffer = new char[256];	// or on the heap



ZeroMemory(buffer, 256);

strcpy(buffer, "Pretend this is important data.");



nret = send(theSocket,

	    buffer,

	    strlen(buffer),	// Note that this specifies the length of the string; not

				// the size of the entire buffer

	    0);			// Most often is zero, but see MSDN for other options



delete [] buffer;		// If and only if the heap declaration was used



if (nret == SOCKET_ERROR)

{

	// Get a specific code

	// Handle accordingly

	return NETWORK_ERROR;

} else {

	// nret contains the number of bytes sent

}

Την παραλαβή είναι η ίδια διαδικασία, προς τα πίσω:

char * readLine()

{

   vector theVector;

   char buffer;

   int bytesReceived;



   while (true)

   {

      bytesReceived = recv(theSocket, &buffer, 1, 0);

      if (bytesReceived <= 0)

         return NULL;



      if (buffer == '\n')

      {

         char *pChar = new char[theVector.size() + 1];

         memset(pChar, 0, theVector.size() + 1);



         for (int f = 0; f < theVector.size(); f++)

            pChar[f] = theVector[f];



         return pChar;

      } else {

         theVector.push_back(buffer);

      }

   }

}

Τι είναι ενδιαφέρον να σημειωθεί είναι ότι υπάρχει ένα κουμπί της γραμμής εργαλείων στο Microsoft Outlook με την ένδειξη “Send/Recv.” Είναι “Receive”, συντομογραφία “Recv” απλά για να εξασφαλιστεί το κουμπί που φαίνεται δεξιά, ή είναι ένας προγραμματιστής είναι συνήθεια από την πληκτρολόγηση recv() τόσες φορές? Μορφή δική σου θεωρίες συνωμοσίας (και πάλι, καλό για smalltalk σε μέρη).

Αυτό είναι όπου συνάντησα ένα μικρό πρόβλημα όταν γράφω την δική μου προγράμματα Winsock. Απλά χρησιμοποιώντας recv() είναι μεγάλη όταν ξέρεις ακριβώς πόσα στοιχεία θα πρέπει να λαμβάνουν (όπως σε ένα παιχνίδι, όπου το πρώτο byte μπορεί να είναι μια εντολή και το επόμενο byte είναι μια παράμετρος, κ. λπ.), αλλά όταν δεν ξέρεις, τι κάνεις? Εάν τα δεδομένα που λαμβάνουμε είναι καταγγελθεί από ένα χαρακτήρα νέας γραμμής (ένα κοινό πρόβλημα με Java πελάτες μιλάει C εξυπηρετητές), μπορείτε να γράψετε ένα readLine() λειτουργία για να συλλάβει τα πάντα, μέχρι και αυτό το χαρακτήρα. Εδώ είναι ό, τι χρησιμοποίησα:

// Code originally written by Nor.  Modified slightly to

// support the MessageBox() API, make logic more readable,

// align spacing, and add comments.  Posted with permission.



#define backKey '\b'					// To disable backspaces, #define backKey NULL

#define newLine '\n'

#define endStr  '\0'



char *readLine(SOCKET s)

{

	vector theVector;

	char buffer;

	char *pChar;

	int bytesReceived;



	while (true)

	{

		bytesReceived = recv(s, &buffer, 1, 0);



		if (bytesReceived <= 0)

		{

			MessageBox(NULL, "recv() returned nothing.", "socketIndication", MB_OK);

			return NULL;

		}



		switch (buffer)

		{

			case backKey:			// Handle backspace

				if (theVector.size() > 0)

					theVector.pop_back();

				break;

			case endStr:			// If end of string char reached,

			case newLine:			// or if end of line char reached,

				pChar = new char[theVector.size() + 1];

				memset(pChar, 0, theVector.size() + 1);



				for (int f = 0; f < theVector.size(); f++)

					pChar[f] = theVector[f];

				return pChar;

				break;

			default:			// Any regular char

				theVector.push_back(buffer);

				break;

		}

	}

}

Μη – μπλοκάρισμα και Ασύγχρονη Υποδοχές

Μέχρι αυτό το σημείο μιλάμε για κλείδωμα sockets, που καλεί μια συνάρτηση όπως η accept() περιμένει επ ‘ αόριστον για ένα χρήστη να συνδεθεί. Ένα μη μπλοκάρισμα υποδοχή επιστρέφει αμέσως, κάθε φορά που είναι να κάνει κάτι, είτε με ένα επιτυχημένο αποτέλεσμα, σφάλμα, ή τίποτα (που δείχνει ότι θα υπάρχει κάτι για να λάβετε αργότερα). Το μειονέκτημα από τη χρήση αυτού του τύπου είναι ότι θα πρέπει να με μη αυτόματο τρόπο ερώτημα για την πρίζα για να δεις αν το αποτέλεσμα έχει έρθει σε κάθε λειτουργία που έχετε καλέσει. Μπορείτε να περάσετε ένα σύνολο από υποδοχές για να τηνselect() λειτουργία για να δείτε ποιες είναι έτοιμος για την ανάγνωση, τη γραφή, ή έχουν επιστρέψει λάθη.

Οι λειτουργίες που χρησιμοποιούν ασύγχρονη υποδοχές, επίσης, να επιστρέψει αμέσως, αλλά μπορείτε να καθορίσετε ένα μήνυμα να στείλετε σε διαδικασία παραθύρου όταν ένα συγκεκριμένο γεγονός έχει συμβεί. Για παράδειγμα, μπορείτε να έχετε την υποδοχή στείλετε ένα SOCKET_GOTMSG μήνυμα κάθε φορά που λαμβάνει κάτι. Συνήθως είναι έξυπνος για να ελέγξετε για σφάλματα (επαχθή, αλλά αναγκαία), όταν μπορείτε να πάρετε μια υποδοχή μήνυμα για την πρόληψη προκαλεί περιττά προβλήματα αργότερα. Πρώτα, ας ορίσουμε κάποιες λειτουργίες που θα χρησιμοποιήσετε για να ορίσετε μια ασύγχρονη υποδοχή:

Οπότε, ας στήσουμε μια ασύγχρονη υποδοχή:

// We begin by creating a flag that Windows will use to contact us when something happens

#define THERE_WAS_A_SOCKET_EVENT	WM_USER + 100	// WM_USER is a base for custom messages
// Somewhere in our initialization code after CreateWindow (), we call WSAAsyncSelect ()

WSAAsyncSelect ( theSocket, hwnd, THERE_WAS_A_SOCKET_EVENT, FD_READ | FD_WRITE | FD_CONNECT | ... );



// This translates: Windows, please contact me using the THERE_WAS_A_SOCKET_EVENT flag that I

// previously defined whenever there's data to read (FD_READ), or when I'm free to send data

// (FD_WRITE), or when I've successfully connected to someone else (FD_CONNECT), or when...etc.
// Somewhere in our initialization code after CreateWindow (), we call WSAAsyncSelect ()

WSAAsyncSelect ( theSocket, hwnd, THERE_WAS_A_SOCKET_EVENT, FD_READ | FD_WRITE | FD_CONNECT | ... );



// This translates: Windows, please contact me using the THERE_WAS_A_SOCKET_EVENT flag that I

// previously defined whenever there's data to read (FD_READ), or when I'm free to send data

// (FD_WRITE), or when I've successfully connected to someone else (FD_CONNECT), or when...etc.

Σημειώστε ότι δεν μπορείτε να ορίσετε ένα μήνυμα για κάθε εκδήλωση, όπως SOCKET_GOTMSG για FD_READ και, στη συνέχεια, SOCKET_CONNECTED για FD_CONNECT. Αυτό είναι επειδή επανειλημμένες κλήσεις WSAAsyncSelect () για να ρυθμίσετε την κάθε σημαία θα ακυρώσει τα αποτελέσματα της τελευταίας κλήσης για να WSAAsyncSelect ().

Έγραψα αυτό το σεμινάριο τον δεκέμβριο του 2000, και τα επτά χρόνια από τότε έχει δει μια σταθερή ροή των επισκεπτών και βελτιώσεις. Ελπίζω ότι έχετε απολαύσει την ανάγνωση όσο το διασκέδασα γράφοντας: σας ευχαριστώ για τη χρήση του Τζόνι Winsock Φροντιστήριο. Τα παραπάνω δεν είναι παρά μια σύντομη επισκόπηση των δυνατοτήτων που μπορείτε να φτάσετε μέσω του Winsock, και άλλοι έχουν κάνει μια πολύ καλύτερη δουλειά από μένα σχολαστικά τις λεπτομέρειες για το θέμα αυτό:

Συμφωνώ με τον Τόμας Bλεκερ (MadWizard) ότι “τον προγραμματισμό του δικτύου φαίνεται πιο εύκολο από ό, τι είναι.” Δεν μπορώ να τονίσω τη σημασία της άσκησης η χρήση αυτών των λειτουργιών, μαζί με ένα πρόγραμμα εντοπισμού σφαλμάτων, ώστε να μπορείτε να δείτε τι συμβαίνει. Θα έχουν τελικά μια πολύ καλύτερη κατανόηση του πώς λειτουργούν τα πράγματα, αν μπορείτε να το πάρετε λάθος, να διερευνήσουν γιατί το πήρες στραβά και, στη συνέχεια, την εμπειρία να πάρει το σωστό. Κάνοντας λάθη, με άλλα λόγια, είναι το πώς θα μάθουν.

Πώς Μπορώ Να Βελτιώσω?

Υπάρχει κάτι που χρειάζεται διευκρίνιση? Δεν το φροντιστήριο αποτύχει να καλύψει ένα Winsock που σχετίζονται με το θέμα που ήθελε να μάθει? Έχει το φροντιστήριο γνωρίσει τις ανάγκες σας ως προγραμματιστής λογισμικού? Είναι διασκεδαστικό? έξυπνα γραμμένο? υπερβολικά απλοϊκό; ή απλά έτσι?

Αρχικά στο http://johnnie.jerrata.com/winsocktutorial/