Hvordan internettet taler

En historie om kommunikation

Har du nogensinde spekuleret på, hvordan Internettet faktisk taler? Hvordan "taler" en computer til en anden computer via Internettet?

Når folk kommunikerer med hinanden, bruger vi ord, der er trukket ind i tilsyneladende meningsfulde sætninger. Sætningerne giver kun mening, fordi vi er enige om en betydning for disse sætninger. Vi har så at sige defineret en kommunikationsprotokol.

Det viser sig, at computere taler til hinanden på en lignende måde over internettet. Men vi kommer foran os selv. Folk bruger deres mund til at kommunikere, lad os finde ud af, hvad munden på computeren er først.

Gå ind i stikkontakten

Stikkontakten er et af de mest grundlæggende begreber inden for datalogi. Du kan opbygge hele netværk af indbyrdes forbundne enheder ved hjælp af stikkontakter.

Som alle andre ting inden for datalogi er en stikkontakt et meget abstrakt begreb. Så i stedet for at definere, hvad et stik er, er det langt lettere at definere, hvad et stik gør.

Så hvad gør et stik? Det hjælper to computere med at kommunikere med hinanden. Hvordan gør det dette? Det har to metoder, der er defineret, kaldet send()og recv()for at sende og modtage henholdsvis.

Okay, det er alt sammen godt, men hvad gør send()og recv()sender og modtager man faktisk? Når folk bevæger deres mund, udveksler de ord. Når stikkontakter bruger deres metoder, udveksler de bits og bytes.

Lad os illustrere metoderne med et eksempel. Lad os sige, at vi har to computere, A og B. Computer A forsøger at sige noget til Computer B. Derfor prøver Computer B at lytte til, hvad Computer A siger. Sådan ser det ud.

Læsning af bufferen

Ser lidt underligt ud, ikke? For det første peger begge computere på en bjælke i midten med titlen 'buffer'.

Hvad er bufferen? Bufferen er en hukommelsesstak. Det er her, data for hver computer er gemt og tildeles af kernen.

Dernæst, hvorfor peger de begge på den samme buffer? Nå, det er faktisk ikke helt nøjagtigt. Hver computer har sin egen buffer tildelt af sin egen kerne, og netværket transporterer dataene mellem de to separate buffere. Men jeg vil ikke komme ind på netværksdetaljer her, så vi antager, at begge computere har adgang til den samme buffer placeret "et eller andet sted i tomrummet imellem".

Okay, nu hvor vi ved, hvordan dette ser ud visuelt, lad os abstrakte det væk i kode.

#Computer A sends data computerA.send(data) 
#Computer B receives data computerB.recv(1024)

Dette kodestykke gør nøjagtigt det samme som billedet ovenfor repræsenterer. Bortset fra en nysgerrighed siger vi ikke computerB.recv(data). I stedet angiver vi et tilsyneladende tilfældigt tal i stedet for data.

Årsagen er enkel. Data over et netværk overføres til bits. Derfor, når vi recv i computerB, specificerer vi antallet af bits, vi er villige til at modtage på et givet tidspunkt.

Hvorfor valgte jeg 1024 byte til at modtage på én gang? Ingen specifik grund. Det er normalt bedst at specificere antallet af byte, du vil modtage med en styrke på 2. Jeg valgte 1024, hvilket er 2¹⁰.

Så hvordan finder bufferen det ud? Computer A skriver eller sender de data, der er gemt sammen med dem, i bufferen. Computer B beslutter at læse eller modtage de første 1024 byte af, hvad der er gemt i denne buffer.

Okay, fantastisk! Men hvordan ved disse to computere at tale med hinanden? For eksempel, når Computer A skriver til denne buffer, hvordan ved det, at Computer B vil hente den? For at omformulere det, hvordan kan det sikre, at en forbindelse mellem to computere har en unik buffer?

Portering til IP'er

Ovenstående billede viser de samme to computere, som vi har arbejdet med sammen med en yderligere detalje tilføjet i. Der er en masse tal opført foran hver computer i længden af ​​en bjælke.

Overvej den lange bjælke foran hver computer som den router, der forbinder en bestemt computer til internettet. Disse numre på hver bjælke kaldes porte . Din computer har tusindvis af porte til rådighed lige nu. Hver port tillader en stikforbindelse. Jeg har kun vist 6 porte i billedet ovenfor, men du får ideen.

Porte under 255 er generelt forbeholdt systemopkald og forbindelser på lavt niveau. Det tilrådes generelt at åbne en forbindelse på en port i de høje 4-cifre, som 8000. Jeg har ikke tegnet bufferen i billedet ovenfor, men du kan antage, at hver port har sin egen buffer.

Selve bjælken har også et nummer tilknyttet. Dette nummer kaldes IP-adressen. IP-adressen har en masse porte tilknyttet. Tænk på det på følgende måde:

 127.0.0.1 / | \ / | \ / | \ 8000 8001 8002

Godt, lad os oprette en forbindelse på en bestemt port mellem Computer A og Computer B.

# computerA.pyimport socket 
computerA = socket.socket() 
# Connecting to localhost:8000 computerA.connect(('127.0.0.1', 8000)) string = 'abcd' encoded_string = string.encode('utf-8') computerA.send(encoded_string)

Her er koden til computerB.py

# computerB.py import socket 
computerB = socket.socket() 
# Listening on localhost:8000 computerB.bind(('127.0.0.1', 8000)) computerB.listen(1) 
client_socket, address = computerB.accept() data = client_socket.recv(2048) print(data.decode('utf-8'))

Det ser ud til, at vi er sprang lidt foran med hensyn til koden, men jeg går igennem det. Vi ved, at vi har to computere, A og B. Derfor har vi brug for en til at sende data og en til at modtage data.

Jeg har vilkårligt valgt A til at sende data og B til at modtage data. I denne linje computerA.connect((‘127.0.0.1’, 8000)opretter jeg computerA-forbindelse til port 8000 på IP-adresse 127.0.0.1.

Bemærk: 127.0.0.1 betyder typisk localhost, der refererer til din maskine

Så for computerB gør jeg det til at binde til port 8000 på IP-adresse 127.0.0.1. Nu spekulerer du sandsynligvis på, hvorfor jeg har den samme IP-adresse til to forskellige computere.

Det er fordi jeg snyder. Jeg bruger en computer til at demonstrere, hvordan du kan bruge stikkontakter (jeg tilslutter mig grundlæggende fra og til den samme computer for enkelheds skyld). Typisk vil to forskellige computere have to forskellige IP-adresser.

We already know that only bits can be sent as part of a data packet, which is why we encode the string before sending it over. Similarly, we decode the string on Computer B. If you decide to run the above two files locally, make sure to run computerB.py file first. If you run the computerA.py file first, you will get a connection refused error.

Serving The Clients

I’m sure its been pretty clear to many of you that what I’ve been describing so far is a very simplistic client-server model. In fact you can see that from the above image, all I’ve done is replace Computer A as the client and Computer B as the server.

There is a constant stream of communication that goes on between clients and servers. In our prior code example, we described a one shot of data transfer. Instead, what we want is a constant stream of data being sent from the client to the server. However, we also want to know when that data transfer is complete, so we know we can stop listening.

Let’s try to use an analogy to examine this further. Imagine the following conversation between two people.

Two people are trying to introduce themselves. However, they will not try to talk at the same time. Let’s assume that Raj goes first. John will then wait until Raj has finished introducing himself before he begins introducing himself. This is based on some learned heuristics but we can generally describe the above as a protocol.

Our clients and servers need a similar protocol. Or else, how would they know when it’s their turn to send packets of data?

We’ll do something simple to illustrate this. Let’s say we want to send some data which happens to be an array of strings. Let’s assume the array is as follows:

arr = ['random', 'strings', 'that', 'need', 'to', 'be', 'transferred', 'across', 'the', 'network', 'using', 'sockets']

The above is the data that is going to be written from the client to the server. Let’s create another constraint. The server needs to accept data that is exactly equivalent to the data occupied by the string that is going to be sent across at that instant.

So, for instance, if the client is going to send across the string ‘random’, and let’s assume each character occupies 1 byte, then the string itself occupies 6 bytes. 6 bytes is then equal to 6*8 = 48 bits. Therefore, for the string ‘random’ to be transferred across sockets from the client to the server, the server needs to know that it has to access 48 bits for that specific packet of data.

This is a good opportunity to break the problem down. There are a couple of things we need to figure out first.

How do we figure out the number of bytes a string occupies in Python?

Well, we could start by figuring out the length of a string first. That’s easy, it’s just a call to len(). But, we still need to know the number of bytes occupied by a string, not just the length.

We’ll convert the string to binary first, and then find the length of the resulting binary representation. That should give us the number of bytes used.

len(‘random’.encode(‘utf-8’)) will give us what we want

How do we send the number of bytes occupied by each string to the server?

Easy, we’ll convert the number of bytes (which is an integer) into a binary representation of that number, and send it to the server. Now, the server can expect to receive the length of a string before receiving the string itself.

How does the server know when the client has finished sending all the strings?

Remember from the analogy of the conversation, there needs to be a way to know if the data transfer has completed. Computers don’t have their own heuristics they can rely on. So, we’ll provide a random rule. We’ll say that when we send across the string ‘end’, that means the server has received all the strings and can now close the connection. Of course, this means that we can’t use the string ‘end’ in any other part of our array except the very end.

Here’s the protocol we’ve designed so far:

Strengens længde vil være 2 bytes efterfulgt af selve den aktuelle streng, der har variabel længde. Det afhænger af strenglængden, der blev sendt i den forrige pakke, og vi vil skifte mellem at sende strenglængderne og selve strengen. EOT står for slutningen af ​​transmission, og at sende strengen 'ende' betyder, at der ikke er flere data at sende.

Bemærk: Før vi fortsætter, vil jeg påpege noget. Dette er en meget enkel og dum protokol. Hvis du vil se, hvordan en veldesignet protokol ser ud, skal du ikke lede længere end HTTP-protokollen.

Lad os kode det ud. Jeg har inkluderet kommentarer i koden nedenfor, så det er selvforklarende.

Godt, vi har en klient, der kører. Dernæst har vi brug for serveren.

Jeg vil forklare et par specifikke kodelinjer i ovenstående gister. Den første fra clientSocket.pyfilen.

len_in_bytes = (len_of_string).to_bytes(2, byteorder="little")

What the above does is convert a number into bytes. The first parameter passed to the to_bytes function is the number of bytes allocated to the result of converting len_of_string to its binary representation.

The second parameter is used to decide whether to follow the Little Endian format or the Big Endian format. You can read more about it here. For now, just know that we will always stick with little for that parameter.

The next line of code I want to take a look at is:

client_socket.send(string.encode(‘utf-8’))

We’re converting the string to a binary format using the‘utf-8’ encoding.

Next, in the serverSocket.py file:

data = client_socket.recv(2) str_length = int.from_bytes(data, byteorder="little")

The first line of code above receives 2 bytes of data from the client. Remember that when we converted the length of the string to a binary format in clientSocket.py, we decided to store the result in 2 bytes. This is why we’re reading 2 bytes here for that same data.

Next line involves converting the binary format to an integer. The byteorder here is “little”, to match the byteorder we used on the client.

If you go ahead and run the two sockets, you should see that the server will print out the strings the client sends across. We established communication!

Conclusion

Okay, vi dækkede en hel del indtil videre. Hvad er stikkontakter, hvordan vi bruger dem, og hvordan man designer en meget enkel og dum protokol. Hvis du vil lære mere om, hvordan stikkontakter fungerer, anbefaler jeg stærkt, at du læser Beejs vejledning til netværksprogrammering. Den e-bog har mange gode ting i sig.

Du kan selvfølgelig tage det, du læser i denne artikel hidtil, og anvende det på mere komplekse problemer som streaming af billeder fra et RaspberryPi-kamera til din computer. Hav det sjovt med det!

Hvis du vil, kan du følge mig på Twitter eller GitHub. Du kan også tjekke min blog her. Jeg er altid tilgængelig, hvis du vil kontakte mig!

Oprindeligt offentliggjort på //redixhumayun.github.io/networking/2019/02/14/how-the-internet-speaks.html den 14. februar 2019.