Vor kurzem habe ich einen etwas ungewöhnlichen Mechanismus bei der Arbeit entwickelt, bei dem mithilfe der Socket-Kommunikation Daten von einem PC mithilfe eines Android-Terminals als Server gesendet werden.
Bisher war mir "HTTP (S)" nur bekannt, wenn ich über "Kommunikation" sprach, aber ich wollte diese Gelegenheit nutzen, um mehr über Kommunikation zu erfahren, also vorerst "TCP / IP" Ich lerne an einem Ort wie "ha".
Im Rahmen dieser Studie habe ich versucht, einen HTTP-Client mithilfe der Socket-Kommunikation zu implementieren. Daher möchte ich dessen Inhalt vorstellen. Die Sprache ist Python.
Bitte lesen Sie vorerst die folgende Seite, wenn Sie überhaupt an Socket-Kommunikation interessiert sind.
Socket-Programmierung HOWTO | Python
Wie auf dieser Seite beschrieben, wird in diesem Artikel davon ausgegangen, dass "Socket-Kommunikation vorerst TCP ist".
Außerdem werde ich kurz zusammenfassen, was ich aus dem Studium gelernt habe.
Vor der Erläuterung von TCP und HTTP werde ich kurz auf das Wort "Protokoll" eingehen. (Weil ich es nicht gut verstanden habe)
Das Protokoll lautet laut Dr. Eijiro "Regeln zum Senden und Empfangen von Daten zwischen Computern".
Auf der ganzen Welt existierende Kommunikationsgeräte und die darin laufende Software (einschließlich des Betriebssystems) werden natürlich von verschiedenen Unternehmen und Personen hergestellt und entwickelt. Und jedes Gerät und jede Software wird hergestellt und entwickelt, ohne dass die Spezifikationen miteinander übereinstimmen.
Selbst wenn in einer solchen Situation "dann lassen Sie uns Daten zwischen Maschinen auf der ganzen Welt austauschen", wenn es keine gemeinsame Spezifikation gibt, "welche Art von Maschine" "wie Funkwellen gesendet werden" " Ich kann meine Hand nicht bewegen, ohne die für die Implementierung erforderlichen Informationen zu erhalten, z. B. "Welche Daten repräsentiert die Funkwelle?"
Hier wurde die "Regel" namens "TCP / IP" geboren. Solange es gemäß den in TCP / IP beschriebenen Regeln entwickelt wurde, ist es möglich, Daten zu senden und zu empfangen, ohne mit jedem Unternehmen Besprechungen abhalten zu müssen.
Und diese "Regel" wird in IT-Begriffen als "Protokoll" bezeichnet.
Wie haben Sie übrigens ein solches universelles Protokoll erstellt und verbreitet? !! Ich werde die Frage weglassen, weil sie lang sein wird. Es war leicht zu verstehen, als ich es zusammen mit dem Begriff "OSI-Referenzmodell" nachgeschlagen habe.
TCP / IP ist eine Sammlung individueller Protokolle, die für Geräte auf der ganzen Welt zur Kommunikation erforderlich sind.
Abhängig von der Kommunikationsmethode und der Verwendung gibt es verschiedene Arten von Protokollen, die je nach physischer und Softwareschicht in vier Schichten unterteilt werden. Die erste Schicht unten befindet sich nicht mehr auf der Ebene "Welche Art von Maschine sendet Funkwellen?".
"TCP" befindet sich in der dritten Schicht unter ihnen und ist ein Protokoll, das "Regeln zum zuverlässigen Senden und Empfangen von Kommunikationsinhalten zwischen zwei Maschinen unabhängig vom Dateninhalt" definiert.
Wenn man nur die Buchstaben betrachtet, ist es dasselbe, aber die Position des Wortes selbst unterscheidet sich zwischen "TCP / IP" und "TCP", und "eine der Protokolllisten mit dem Namen TCP / IP" ist TCP.
Darüber hinaus befindet sich "HTTP" in der 4. Schicht und ist eine "Regel, die durch Hinzufügen weiterer Regeln zu TCP definiert wird, um das Format und den Zeitpunkt des Sendens und Empfangens von Daten zum Surfen auf Websites zu optimieren."
Wie ich bereits erwähnt habe, ist TCP ein Protokoll für den Datenaustausch zwischen zwei Computern ohne Überschuss oder Mangel. Es spielt also keine Rolle, in welchem Format die dort gesendeten und empfangenen Daten in welcher Anwendung verwendet werden. Da ist gar nichts. Es ist HTTP, das die Regeln bestimmt, und andere Protokolle, die sich in der vierten Schicht befinden, wie "SSH" und "FTP". Das Konzept von "Client" und "Server" unterscheidet sich in der TCP-Kommunikation nicht so stark. Die Person, die die Möglichkeit geschaffen hat, zuerst eine Verbindung herzustellen, ist der "Client", und die Person, die verbunden ist, ist der "Server". Sobald die Verbindung hergestellt ist, können beide Daten auf dieselbe Weise senden und empfangen.
Allein damit können Sie sehen, dass HTTP nicht alles ist, wenn Sie "Kommunikation" sagen. Es ist möglicherweise einfacher zu verstehen, wenn Sie die folgenden Inhalte in diesem Sinne lesen.
Wie geht es dann mit der konkreten Umsetzung weiter?
Wenn es wahr ist, denke ich, dass es richtig ist, den RFC richtig zu lesen oder über das Design nachzudenken, während man sich die Implementierung anderer HTTP-Clients ansieht, aber ich denke, es durch Ausprobieren, ohne etwas zu betrachten. Ich frage mich, ob es besser ist, beim Nachdenken fortzufahren. Diesmal gehen wir also folgendermaßen vor.
In diesem Artikel schreibe ich über den ersten Teil, den ich versuchen werde, während ich für mich selbst denke.
Beginnen wir also mit der Implementierung. Der Code ist auch auf GitHub zu finden.
http_client.py
if __name__ == '__main__':
resp = ChooyanHttpClient.request('127.0.0.1', 8010)
if resp.responce_code == 200:
print(resp.body)
Zunächst sehen Sie, wie Sie Ihren eigenen HTTP-Client verwenden. Nach dem Übergeben von Host und Port möchten wir das Objekt abrufen, das die Antwortdaten enthält.
http_client.py
class ChooyanHttpClient:
def request(host, port=80):
response = ChooyanResponse()
return response
class ChooyanResponse:
def __init__(self):
self.responce_code = None
self.body = None
if __name__ == '__main__':
...Folgendes wird weggelassen
Fügen Sie als Nächstes die Klassen "ChooyanHttpClient" und "ChooyanResponse" gemäß dem obigen Verwendungsbild hinzu.
Ich habe es hinzugefügt, aber noch nichts getan.
Dieses Mal versuchen wir, den Antwortcode und den Text, der das Anforderungsergebnis sein wird, in dieses "Antwort" -Objekt zu bekommen.
Fügen Sie als Nächstes ein Socket-Modul für die Kommunikation hinzu.
http_client.py
import socket
class ChooyanHttpClient:
def request(host, port=80):
response = ChooyanResponse()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
return response
class ChooyanResponse:
...Folgendes wird weggelassen
Wie ich zuvor erklärt habe, wird das HTTP-Protokoll der 4. Schicht erstellt, indem das TCP-Protokoll der 3. Schicht verwendet und weitere Regeln hinzugefügt werden.
Dieses Mal besteht der Zweck darin, eine Bibliothek zu implementieren, die gemäß dem "HTTP" -Protokoll kommuniziert. Daher importieren wir das "Socket" -Modul, das mit dem "TCP" kommuniziert, das die Basis ist.
Informationen zur Verwendung des Socket-Moduls finden Sie unter [Socket-Programmieranleitung] ](Https://docs.python.jp/3/howto/sockets.html) Es wird auf der Seite kurz beschrieben. Auch in diesem Artikel werden wir mit der Implementierung fortfahren, während wir uns darauf beziehen.
Was wir hier hinzugefügt haben, ist die Verwendung des Socket-Moduls, um eine Verbindung mit dem Computer herzustellen, die dem angegebenen Host und Port entspricht.
Wenn Sie dies tatsächlich tun, beginnt die Kommunikation mit dem angegebenen Server. (Ich bin nicht sicher, weil nichts auf dem Bildschirm erscheint)
Nun, von hier aus ist es schwer.
Ich konnte zuvor über das Socket-Modul eine Verbindung zum Computer des angegebenen Hosts und Ports herstellen.
Es werden jedoch noch keine Daten zurückgegeben. Es ist in Ordnung, eine Verbindung herzustellen, aber es ist natürlich, da wir die "Anforderungs" -Daten noch nicht gesendet haben.
Jetzt schreibe ich den Code, um die Anfrage an den Server zu senden.
http_client.py
import socket
class ChooyanHttpClient:
def request(host, port=80):
response = ChooyanResponse()
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((request.host, request.port))
request_str = 'GET / HTTP/1.1\nHost: %s\r\n\r\n' % (host)
s.send(request_str.encode('utf-8'))
return response
class ChooyanResponse:
...Folgendes wird weggelassen
Es wurde eine Zeile zum Ausführen der Funktion "send ()" und eine Zeile zum Zusammenstellen der Zeichenfolge hinzugefügt, die einmal an sie übergeben werden soll.
Jetzt können Sie (feste) Daten an den Server senden.
Bei der Ausführung wird diese Anforderung meiner Meinung nach im Zugriffsprotokoll auf der Serverseite angezeigt.
Jetzt können Sie eine GET-Anforderung (Datenrepräsentation) an den angegebenen Host senden, dies allein reicht jedoch noch nicht aus, um mit dem Server zu kommunizieren. Dies liegt daran, dass es keinen Code gibt, der "empfangen" entspricht.
Wenn Sie sich fragen: "Nun, Sie haben eine Anfrage gesendet, damit Sie eine Antwort erhalten, oder?", Liegt dies daran, dass eine im Handel erhältliche HTTP-Client-Bibliothek ordnungsgemäß erstellt wurde. Ich habe es noch nicht richtig gemacht, daher kann ich keine Antwort erhalten.
In TCP gibt es keine besonderen Regeln bezüglich des Zeitpunkts der Datenübertragung für jeden Server und Client. Mit anderen Worten, jeder kann "seine Lieblingsdaten senden, wann er will".
Wenn jedoch nur dies entschieden wird, ist es nicht möglich zu wissen, welche Art von Daten zu welchem Zeitpunkt von Servern und Clients mit unterschiedlichen Erstellern aneinander gesendet werden, so dass die Freiheit in gewissem Umfang eingeschränkt wird und die allgemeine Anerkennung in der Regel erfolgt Haben müssen. Eine der gängigen Wahrnehmungen ist das HTTP-Protokoll.
Mit anderen Worten, in HTTP lautet die Regel "Wenn Sie eine Anfrage senden, wird eine Antwort zurückgegeben", sodass der Client "Wenn Sie eine Anfrage senden, warten Sie auf den Empfang der Antwort" implementieren muss.
Der Code sieht so aus.
http_client.py
import socket
class ChooyanHttpClient:
def request(request):
...Kürzung
s.connect((request.host, request.port))
request_str = 'GET / HTTP/1.1\nHost: %s\r\n\r\n' % (host)
s.send(request_str.encode('utf-8'))
response = s.recv(4096)
...Folgendes wird weggelassen
Der Funktion recv ()
wurde eine Zeile hinzugefügt. Diese Zeile blockiert die Verarbeitung dieses Programms, bis der Server die Daten sendet.
Dies hat jedoch immer noch Probleme.
Ich werde die Details weglassen (weil ich sie nicht richtig verstehe), aber bei der Socket-Kommunikation können nicht alle Daten gleichzeitig empfangen werden. Wie oben erwähnt, können Sie mit der Socket-Kommunikation jederzeit Ihre Lieblingsdaten senden, sodass nicht entschieden wird, wie viel Sie einmal tun können.
Daher weiß das Programm nicht, wie viele Daten sich in einer Masse befinden, und weiß nicht, wann die Verbindung getrennt werden muss. [^ 1]
Die zuvor erwähnte Funktion "recv ()" fährt auch mit dem nächsten Prozess fort, sobald sie einige Daten bis zu einem guten Punkt (oder bis zu der im Argument angegebenen Anzahl von Bytes) anstelle von "all" empfängt.
Mit anderen Worten, dieser Code kann nur Antworten bis zu 4096 Bytes akzeptieren. Ändern Sie den Code so, dass Sie genügend Daten empfangen können.
http_client.py
import socket
class ChooyanHttpClient:
def request(request):
...Kürzung
s.send(request_str.encode('utf-8'))
data = []
while True:
chunk = s.recv(4096)
data.append(chunk)
response.body = b''.join(data)
return response
...Folgendes wird weggelassen
Es empfängt bis zu 4096 Bytes in einer Endlosschleife und fügt dem Array immer mehr hinzu. Wenn Sie es verketten, können Sie die Daten vom Server empfangen, ohne sie zu verpassen.
Dies ist jedoch noch unvollständig. Wenn ich diesen Code ausführe, verlässt er die Endlosschleife nicht und kann das Ergebnis nicht an den Aufrufer zurückgeben.
Wie ich bereits geschrieben habe, hat die Socket-Kommunikation nicht das Konzept "einmal" und die Kommunikation hat kein Ende. Dies sagt dem Programm nicht, wo die Endlosschleife beendet werden soll.
Damit kann das Merkmal von HTTP "Wenn Sie es einmal senden, wird es einmal zurückgegeben" nicht realisiert werden. In HTTP wird daher entschieden, die Datengröße (im Hauptteil) mithilfe des Headers "Content-Length" anzugeben. Der folgende Code erstellt einen Mechanismus zum Lesen.
http_client.py
import socket
class ChooyanHttpClient:
def request(request):
...Kürzung
s.send(request_str.encode('utf-8'))
headerbuffer = ResponseBuffer()
allbuffer = ResponseBuffer()
while True:
chunk = s.recv(4096)
allbuffer.append(chunk)
if response.content_length == -1:
headerbuffer.append(chunk)
response.content_length = ChooyanHttpClient.parse_contentlength(headerbuffer)
else:
if len(allbuffer.get_body()) >= response.content_length:
break
response.body = allbuffer.get_body()
response.responce_code = 200
s.close()
return response
def parse_contentlength(buffer):
while True:
line = buffer.read_line()
if line.startswith('Content-Length'):
return int(line.replace('Content-Length: ', ''))
if line == None:
return -1
class ChooyanResponse:
def __init__(self):
self.responce_code = None
self.body = None
self.content_length = -1
class ResponseBuffer:
def __init__(self):
self.data = b''
def append(self, data):
self.data += data
def read_line(self):
if self.data == b'':
return None
end_index = self.data.find(b'\r\n')
if end_index == -1:
ret = self.data
self.data = b''
else:
ret = self.data[:end_index]
self.data = self.data[end_index + len(b'\r\n'):]
return ret.decode('utf-8')
def get_body(self):
body_index = self.data.find(b'\r\n\r\n')
if body_index == -1:
return None
else:
return self.data[body_index + len(b'\r\n\r\n'):]
...Folgendes wird weggelassen
Es ist lange her, aber ich werde erklären, was ich versuche, um zu tun.
Als HTTP-Antwortformat
Es wurde entschieden.
Daher erfolgt die Bestellung jedes Mal, wenn Daten empfangen werden, von vorne.
Ich mache das Jetzt können Sie die Content-Length
abrufen.
"Inhaltslänge" beschreibt jedoch nur die Größe des Körperteils. Der Header und der Antwortcode in der ersten Zeile sind nicht enthalten.
Daher versuche ich unter Verwendung aller empfangenen Daten, die Größe der Daten nach dem zweiten Zeilenumbruch in Folge (dh dem Körperteil nach der ersten Leerzeile) mit "Inhaltslänge" zu vergleichen. ..
Wenn die Größe der Inhaltslänge und die Größe des Körperteils übereinstimmen (im Code nur für den Fall, dass sie größer oder gleich der Länge des Inhalts ist), können Sie die Schleife verlassen und die Daten an den Aufrufer zurückgeben. Ich kann es schaffen
Jetzt, da wir endlich Anfragen senden und Antworten empfangen können, ist es als HTTP-Client immer noch unbrauchbar.
Die Anforderung ist schrecklich, auf die GET-Methode beschränkt, auf den Stammpfad und keinen Anforderungsheader beschränkt, und die Antwort gibt nur alle Daten einschließlich des Antwortcodes und des Headers als Bytezeichenfolge zurück.
Es gibt noch viele Dinge zu tun, wie die Formatierung der Daten hier, das Ändern des Verhaltens entsprechend dem Header, die Feinabstimmung des Sende- und Empfangszeitpunkts, die Timeout-Verarbeitung usw., aber dieser Artikel ist ziemlich lang geworden. Ich habe es getan, also würde ich das gerne das nächste Mal tun.
Vorerst habe ich versucht, eine HTTP-Client-ähnliche Verarbeitung zu implementieren, aber ich habe das Gefühl, dass ich mein Verständnis von TCP und HTTP vertiefen konnte. Es ist schwierig, eine HTTP-Client-Bibliothek zu erstellen ... Welche Art von Implementierung ist "Anfragen" oder "Urllib"?
Deshalb werde ich das nächste Mal weitermachen.
Nachdem ich diesen Artikel gelesen hatte, entschied ich mich, auf ähnliche Weise zu studieren. In diesem Artikel habe ich den HTTP "Server" erstellt, aber es gab viele Inhalte, die beim Erstellen des Clients sehr hilfreich waren.
Er erklärte die Socket-Kommunikation auf sehr leichte und leicht verständliche Weise, was für mich beim Studium der Socket-Kommunikation sehr hilfreich war. Obwohl es sich um ein Python-Dokument handelt, ist es unabhängig von der Sprache hilfreich.
[^ 1]: Ich dachte, das liegt an dem in HTTP 1.1 hinzugefügten KeepAlive-Header. Wenn Sie dies deaktivieren, wird die Verbindung zum Server getrennt, wenn die Daten an das Ende gesendet werden, und die clientseitige Funktion recv () gibt 0 zurück, sodass Sie dies erkennen und aus der Schleife ausbrechen können.
Recommended Posts