1. Ein einfachstes serverseitiges BeispielWie wir alle wissen, erfordert die Einrichtung eines serverseitigen Sockets vier Schritte: Socket, Binden, Listen und Akzeptieren. Der Code lautet wie folgt: void start_server(){ // Server-FD int sockfd_server; // FD akzeptieren int sockfd; int call_err; Struktur sockaddr_in sock_addr; sockfd_server = socket(AF_INET,SOCK_STREAM,0); memset(&sock_addr,0,Größevon(sock_addr)); sock_addr.sin_family = AF_INET; sock_addr.sin_addr.s_addr = htonl(INADDR_ANY); sock_addr.sin_port = htons(SERVER_PORT); // Das ist unser heutiger Fokus, binden call_err = binden(sockfd_server, (Struktur sockaddr*)(&sock_addr), Größe von(sock_addr)); wenn(call_err == -1){ fprintf(stdout,"Bindungsfehler!\n"); Ausgang (1); } // Hören call_err = abhören(sockfd_server,MAX_BACK_LOG); wenn(call_err == -1){ fprintf(stdout,"Abhörfehler!\n"); Ausgang (1); } } Zuerst erstellen wir einen Socket durch den Socket-Systemaufruf, in dem SOCK_STREAM angegeben ist und der letzte Parameter 0 ist, was bedeutet, dass ein normaler TCP-Socket eingerichtet wird. Hier geben wir direkt die dem TCP-Socket entsprechenden Operationen an, also die Operationsfunktion. 2. Systemaufruf bindenBind weist einem Socket eine lokale Protokolladresse (Protokoll:IP:Port) zu. Beispielsweise eine 32-Bit-IPv4-Adresse oder eine 128-Bit-IPv6-Adresse + eine 16-Bit-TCP- oder UDP-Portnummer. #include <sys/socket.h> // Gibt 0 zurück, wenn erfolgreich, -1, wenn ein Fehler auftritt int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen); Okay, gehen wir direkt zum Aufrufstapel des Linux-Quellcodes.
2.1, inet_bindDie Funktion inet_bind führt hauptsächlich zwei Vorgänge aus: Der eine besteht darin, festzustellen, ob Binden zulässig ist, und der andere darin, die verfügbare Portnummer abzurufen. Dies ist hier erwähnenswert. Wenn wir die zu bindende Portnummer auf 0 setzen, hilft uns der Kernel, zufällig eine verfügbare Portnummer für die Bindung auszuwählen! // Das System wählt zufällig eine verfügbare Portnummer aus sock_addr.sin_port = 0; call_err = binden(sockfd_server, (Struktur sockaddr*)(&sock_addr), Größe von(sock_addr)); Schauen wir uns den Prozess von inet_bind an Es ist erwähnenswert, dass wir den Root-Benutzer verwenden oder der ausführbaren Datei die Berechtigung CAP_NET_BIND_SERVICE erteilen müssen, wenn auf Port 80 lauscht wird (z. B. beim Starten von nginx), da CAP_NET_BIND_SERVICE für Portnummern < 1024 erforderlich ist.
Unser Bind ermöglicht die Bindung an die Adresse 0.0.0.0, die INADDR_ANY ist (normalerweise verwendet), was bedeutet, dass der Kernel die IP-Adresse auswählt. Die unmittelbarsten Auswirkungen auf uns werden in der folgenden Abbildung dargestellt: Als nächstes betrachten wir die nächste komplexere Funktion, nämlich den Prozess der Auswahl der verfügbaren Portnummer, inet_csk_get_port 2.2, inet_csk_get_portWenn im ersten Abschnitt der Bind-Port 0 ist, wird nach dem Zufallsprinzip nach einer verfügbaren Portnummer gesucht Direkt im Quellcode ist der erste Abschnitt des Codes der Suchvorgang für Portnummer 0 // Wenn snum als 0 angegeben ist, wird ein Port zufällig ausgewählt inet_csk_get_port(struct sock *sk, unsigned short snum) { ...... // Hier verwendet net_random() prandom_u32, eine Pseudozufallszahl smallest_rover = rover = net_random() % remainder + low; kleinste_Größe = -1; // snum=0, wähle den Zweig des Ports zufällig aus, wenn (!sum) { // Den vom Kernel festgelegten Portnummernbereich abrufen, der dem Kernelparameter /proc/sys/net/ipv4/ip_local_port_range entspricht inet_get_local_port_range(&niedrig,&hoch); ...... Tun{ wenn (inet_ist_reservierter_lokaler_Port(rover) goto next_nonlock; // Wählen Sie nicht die reservierte Portnummer aus...... inet_bind_bucket_for_each(tb, &Kopf->Kette) // Derselbe Port wie der Port Rover, den Sie auswählen möchten, existiert im selben Netzwerk-Namespace wenn (net_eq(ib_net(tb), net) und tb->port == Rover) { // Sowohl beim bestehenden als auch beim neuen Sock ist SO_REUSEADDR aktiviert und der aktuelle Sock-Status lautet „Nicht zuhören“ // oder // Sowohl der vorhandene Sock als auch der neue Sock haben SO_REUSEPORT aktiviert und beide sind derselbe Benutzer, wenn (((tb->fastreuse > 0 && sk->sk_reuse && sk->sk_state != TCP_LISTEN) || (tb->Fastreueport > 0 && sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && (tb->Anzahl_Besitzer < kleinste_Größe || kleinste_Größe == -1)) { // Hier wählen wir einen Port mit den kleinsten num_owners aus, also einen Port mit der geringsten Anzahl gleichzeitiger Bind- oder Listen-Anfragen // Weil eine Portnummer (Port) von mehreren Prozessen gleichzeitig verwendet werden kann, nachdem so_reuseaddr/so_reuseport aktiviert wurde, smallest_size = tb->num_owners; kleinster_rover = Rover; wenn (atomic_read(&hashinfo->bsockets) > (hoch - niedrig) + 1 && !inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) { // Das Betreten dieses Zweigs zeigt an, dass die verfügbare Portnummer nicht ausreicht. Gleichzeitig steht die aktuelle Portnummer nicht im Konflikt mit dem zuvor verwendeten Port, daher wählen wir diese Portnummer (die kleinste). snum = kleinster_rover; gehe zu tb_found; } } // Wenn die Portnummer keinen Konflikt verursacht, wähle diesen Port aus, if (!inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, false)) { snum = Rover; gehe zu tb_found; } gehe zum nächsten; } brechen; // Bis alle verfügbaren Ports durchlaufen sind } während (--restlich > 0); } ....... } Da wir bei der Verwendung von Bind selten zufällige Portnummern verwenden (insbesondere für TCP-Server), werde ich diesen Code kommentieren. Im Allgemeinen verwenden nur einige spezielle Remote Procedure Calls (RPCs) zufällige serverseitige Portnummern. Der zweite Abschnitt findet die Portnummer oder wurde bereits angegeben habe_snum: inet_bind_bucket_for_each(tb, &Kopf->Kette) wenn (net_eq(ib_net(tb), net) und tb->port == snum) gehe zu tb_found; } tb = NULL; gehe zu tb_not_found tb_gefunden: // Wenn dieser Port gebunden wurde wenn (!hlist_empty(&tb->owners)) { // Wenn auf erzwungene Wiederverwendung eingestellt, ist es direkt erfolgreich, wenn (sk->sk_reuse == SK_FORCE_REUSE) gehe zum Erfolg; } wenn (((tb->fastreuse > 0 && sk->sk_reuse und sk->sk_state != TCP_LISTEN) || (tb->Fastreueport > 0 && sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && kleinste_Größe == -1) { // Dieser Zweig zeigt an, dass der zuvor gebundene Port und der aktuelle Sock beide auf Wiederverwendung eingestellt sind und der aktuelle Sock-Status nicht abhört // Oder sowohl Reuseport als auch UID werden gleichzeitig festgelegt (beachten Sie, dass Sie nach dem Festlegen von Reuseport gleichzeitig auf demselben Port lauschen können). gehe zum Erfolg; } anders { ret = 1; // Überprüfen Sie, ob ein Portkonflikt vorliegt, if (inet_csk(sk)->icsk_af_ops->bind_conflict(sk, tb, true)) { wenn (((sk->sk_reuse && sk->sk_state != TCP_LISTEN) || (tb->Fastreueport > 0 && sk->sk_reuseport && uid_eq(tb->fastuid, uid))) && kleinste_Größe != -1 und --Versuche >= 0) { // Wenn ein Konflikt vorliegt, aber der Reuse-Nicht-Listen-Status gesetzt ist oder der Reuse-Port gesetzt ist und es sich um denselben Benutzer handelt, // können Sie spin_unlock(&head->lock); erneut versuchen. gehe nochmal zu; } gehe zu fail_unlock; } // Kein Konflikt, folgen Sie der folgenden Logik } tb_nicht_gefunden: wenn (!tb && (tb = inet_bind_bucket_create(hashinfo->bind_bucket_cachep, net, head, snum)) == NULL) gehe zu fail_unlock; // Fastreuse einrichten //Fastreuseport festlegen Erfolg: ...... // Verknüpfen Sie den aktuellen Sock mit tb->owner und tb->num_owners++ inet_bind_hash(sk, tb, snum); ret = 0; // Gib Bind (Bindung) erfolgreich zurück return ret; 3. Stellen Sie fest, ob die Portnummer in Konflikt stehtIm obigen Quellcode lautet der Code zur Ermittlung, ob ein Portnummernkonflikt vorliegt: inet_csk(sk)->icsk_af_ops->bind_conflict, auch bekannt als inet_csk_bind_conflict int inet_csk_bind_conflict(const struct sock *sk, const struct inet_bind_bucket *tb, bool relax){ ...... sk_for_each_bound(sk2, &tb->Besitzer) { // Diese Beurteilung zeigt, dass für den nächsten internen Zweig dieselbe Schnittstelle (dev_if) verwendet werden muss, d. h. Ports, die sich nicht auf derselben Schnittstelle befinden, geraten nicht in Konflikt, wenn (sk != sk2 && !inet_v6_ipv6only(sk2) && (!sk->sk_bound_dev_if || !sk2->sk_bound_dev_if || sk->sk_bound_dev_if == sk2->sk_bound_dev_if)) { wenn ((!wiederverwenden || !sk2->sk_wiederverwenden || sk2->sk_state == TCP_LISTEN) && (!reuseport || !sk2->sk_reuseport || (sk2->sk_state != TCP_TIME_WAIT && !uid_eq(uid, sock_i_uid(sk2))))) { // Wenn eine Partei die Wiederverwendung nicht festlegt und Sock2 sich im Listenzustand befindet // Gleichzeitig legt eine Partei den Wiederverwendungsport nicht fest und Sock2 befindet sich nicht im Time_Wait-Zustand und die UIDs der beiden sind unterschiedlich const __be32 sk2_rcv_saddr = sk_rcv_saddr(sk2); wenn (!sk2_rcv_saddr || !sk_rcv_saddr(sk) || // Die IP-Adressen sind gleich, was als Konflikt betrachtet wird sk2_rcv_saddr == sk_rcv_saddr(sk)) brechen; } // Im nicht entspannten Modus wird es nur dann als Konflikt betrachtet, wenn die IP-Adressen gleich sind … gibt sk2 zurück != NULL; } ...... } Die Logik des obigen Codes ist in der folgenden Abbildung dargestellt: 4. SO_REUSEADDR und SO_REUSEPORTDer obige Code ist etwas verwirrend, daher möchte ich kurz darauf eingehen, worauf wir bei unserer täglichen Entwicklung achten sollten. Im obigen Bind sehen wir häufig die beiden Socket-Flags sk_reuse und sk_reuseport. Diese beiden Flags können bestimmen, ob die Bindung erfolgreich sein kann. Die Einstellungen dieser beiden Flags werden im folgenden Code in der Sprache C angezeigt: setockopt(sockfd_server, SOL_SOCKET, SO_REUSEADDR, &(int){ 1 }, sizeof(int)); setsockopt(sockfd_server, SOL_SOCKET, SO_REUSEPORT, &(int){ 1 }, sizeof(int)); In nativem JAVA // In Java 8 unterstützen native Sockets so_reuseport nicht ServerSocket-Server = neuer ServerSocket(Port); server.setReuseAddress(true); In Netty (Netty-Version >= 4.0.16 und Linux-Kernel-Version >= 3.9 oder höher) kann SO_REUSEPORT verwendet werden. SO_REUSEADDRIm vorherigen Quellcode haben wir gesehen, dass bei der Beurteilung, ob ein Bindungskonflikt besteht, ein solcher Zweig vorhanden ist (!wiederverwenden || !sk2->sk_wiederverwenden || sk2->sk_state == TCP_LISTEN) /* Reuseport vorübergehend ignorieren */){ //Eine Partei hat es nicht festgelegt} Wenn sich sk2 (d. h. der gebundene Socket) im Status TCP_LISTEN befindet oder sowohl für sk2 als auch für den neuen SK _REUSEADDR nicht festgelegt ist, kann dies als Konflikt betrachtet werden. Wir können daraus schließen, dass, wenn sowohl der ursprüngliche Sock als auch der neue Sock mit SO_REUSEADDR festgelegt sind, sie erfolgreich gebunden werden können, solange sich der ursprüngliche Sock nicht im Listen-Status befindet, sogar im ESTABLISHED-Status! In unserer täglichen Arbeit ist die häufigste Situation, dass sich der ursprüngliche Sock im Status TIME_WAIT befindet, was normalerweise auftritt, wenn wir den Server herunterfahren. Wenn SO_REUSEADDR nicht festgelegt ist, schlägt die Bindung fehl und der Dienst wird nicht gestartet. Allerdings ist SO_REUSEADDR festgelegt und der Vorgang ist erfolgreich, da es sich nicht um TCP_LISTEN handelt. Diese Funktion ist sehr nützlich für einen Notfallneustart und das Offline-Debugging und es wird empfohlen, sie zu aktivieren. 6. SO_REUSEPORTSO_REUSEPORT ist eine neue Funktion, die in Linux Version 3.9 eingeführt wurde.
Schauen wir uns das allgemeine Reactor-Thread-Modell an. Offensichtlich wird es bei seinem einfädigen Listen/Accept zu einem Engpass kommen (wenn ein mehrfädiges Epoll-Accept verwendet wird, führt dies zu einer Gruppenpanik, und das Hinzufügen von WQ_FLAG_EXCLUSIVE kann einen Teil des Problems lösen), insbesondere bei der Verwendung kurzer Links. wenn(!reuseport || !sk2->sk_reuseport || (sk2->sk_state != TCP_TIME_WAIT && !uid_eq(uid, sock_i_uid(sk2)) Dieser Code ermöglicht uns mehrere Bindungen ohne Fehler, wenn SO_REUSEPORT gesetzt ist. Das bedeutet, dass wir die Möglichkeit haben, mehrere Threads (Prozesse) zu binden/abzuhören. Wie in der folgenden Abbildung dargestellt: Nachdem SO_REUSEPORT aktiviert wurde, sieht der Codestapel wie folgt aus: tcp_v4_rcv |->__inet_lookup_skb |->__inet_lookup |->__inet_lookup_listener /* Verwende Punkte und Pseudozufallszahlen, um einen Listen-Sock auszuwählen */ Struktur sock *__inet_lookup_listener(......) { ...... wenn (Punktzahl > höchster Punktestand) { Ergebnis = sk; hiscore = Punktzahl; Wiederverwendungsport = sk->sk_wiederverwendungsport; if (Wiederverwendungsport) { phash = inet_ehashfn(net, daddr, hnum, saddr, Sport); Übereinstimmungen = 1; } } sonst wenn (Punktzahl == Höchstpunktzahl && Wiederverwendungsbericht) { Streichhölzer++; wenn (((u64)phash * übereinstimmt) >> 32 == 0) Ergebnis = sk; phash = nächster_pseudo_random32(phash); } ...... } Führen Sie den Lastenausgleich direkt auf Kernelebene durch und verteilen Sie die Akzeptanzaufgaben auf verschiedene Sockets verschiedener Threads (Sharding). Dadurch werden zweifellos Multi-Core-Funktionen genutzt und die Socket-Verteilungsfunktionen nach einer erfolgreichen Verbindung erheblich verbessert. Nginx verwendet bereits SO_REUSEPORT Nginx hat SO_REUSEPORT in Version 1.9.1 eingeführt und die Konfiguration ist wie folgt: http { Server { hören Sie 80 Wiederverwendungsport; Servername localhost; # ... } } Strom { Server { hören Sie 12345 Wiederverwendungsport; # ... } } VII. FazitDer Quellcode des Linux-Kernels ist umfangreich und tiefgründig. Ein scheinbar einfacher Bind-Systemaufruf enthält tatsächlich so viele Details, dass Sie daraus etwas herauslesen können. Ich teile dies hier in der Hoffnung, dass es für die Leser hilfreich sein wird. Oben finden Sie eine ausführliche Erklärung der Socket (TCP)-Bindung aus dem Linux-Quellcode. Weitere Informationen zur Linux Socket (TCP)-Bindung finden Sie in den anderen verwandten Artikeln auf 123WORDPRESS.COM! Das könnte Sie auch interessieren:
|
<<: Textmodus im IE! Einführung in die Rolle von DOCTYPE
>>: Einige Indikatoren für exzellentes Web-Frontend-Design
Dieser Artikel veranschaulicht anhand von Beispie...
Das Festlegen der Schriftart für die gesamte Site...
Die Fähigkeiten, die Front-End-Entwickler beherrs...
In Google Chrome werden Sie nach der erfolgreiche...
Die Tags dd und dt werden für Listen verwendet. N...
Wie unten dargestellt: Wählen Sie Produktname, Pr...
In diesem Artikelbeispiel wird der spezifische Co...
Wer King of Glory gespielt hat, sollte mit der Wi...
Ich habe online gesucht und festgestellt, dass in...
1. Klären Sie die Designrichtung <br />Zuers...
Verwenden Sie Javascript, um ein Message Board-Be...
Tipp 1: Konzentriert bleiben Die besten mobilen A...
Inhaltsverzeichnis MySQL-Client/Server-Protokoll ...
Als ich vor ein paar Tagen ein Programm schrieb, w...
Inhaltsverzeichnis Schritt 1: Melden Sie sich als...