Unix Socket - Kurzanleitung
Sockets ermöglichen die Kommunikation zwischen zwei verschiedenen Prozessen auf demselben oder verschiedenen Computern. Genauer gesagt können Sie mit Standard-Unix-Dateideskriptoren mit anderen Computern kommunizieren. Unter Unix wird jede E / A-Aktion durch Schreiben oder Lesen eines Dateideskriptors ausgeführt. Ein Dateideskriptor ist nur eine Ganzzahl, die einer geöffneten Datei zugeordnet ist. Es kann sich um eine Netzwerkverbindung, eine Textdatei, ein Terminal oder etwas anderes handeln.
Für einen Programmierer sieht ein Socket ähnlich aus und verhält sich ähnlich wie ein Dateideskriptor auf niedriger Ebene. Dies liegt daran, dass Befehle wie read () und write () mit Sockets genauso funktionieren wie mit Dateien und Pipes.
Sockets wurden zuerst in 2.1BSD eingeführt und anschließend mit 4.2BSD in ihre aktuelle Form gebracht. Die Sockets-Funktion ist jetzt mit den meisten aktuellen UNIX-Systemversionen verfügbar.
Wo wird die Steckdose verwendet?
Ein Unix-Socket wird in einem Client-Server-Anwendungsframework verwendet. Ein Server ist ein Prozess, der auf Anfrage eines Clients einige Funktionen ausführt. Die meisten Protokolle auf Anwendungsebene wie FTP, SMTP und POP3 verwenden Sockets, um eine Verbindung zwischen Client und Server herzustellen und anschließend Daten auszutauschen.
Sockeltypen
Den Benutzern stehen vier Arten von Sockets zur Verfügung. Die ersten beiden werden am häufigsten verwendet und die letzten beiden werden selten verwendet.
Es wird davon ausgegangen, dass Prozesse nur zwischen Sockets desselben Typs kommunizieren. Es gibt jedoch keine Einschränkung, die die Kommunikation zwischen Sockets unterschiedlichen Typs verhindert.
Stream Sockets- Die Lieferung in einer vernetzten Umgebung ist garantiert. Wenn Sie drei Elemente "A, B, C" über den Stream-Socket senden, kommen sie in derselben Reihenfolge an - "A, B, C". Diese Sockets verwenden TCP (Transmission Control Protocol) für die Datenübertragung. Ist eine Zustellung nicht möglich, erhält der Absender eine Fehleranzeige. Datensätze haben keine Grenzen.
Datagram Sockets- Die Lieferung in einer vernetzten Umgebung kann nicht garantiert werden. Sie sind verbindungslos, da Sie keine offene Verbindung wie in Stream Sockets benötigen. Sie erstellen ein Paket mit den Zielinformationen und senden es aus. Sie verwenden UDP (User Datagram Protocol).
Raw Sockets- Diese bieten Benutzern Zugriff auf die zugrunde liegenden Kommunikationsprotokolle, die Socket-Abstraktionen unterstützen. Diese Sockets sind normalerweise datagrammorientiert, obwohl ihre genauen Eigenschaften von der vom Protokoll bereitgestellten Schnittstelle abhängen. Raw-Sockets sind nicht für den allgemeinen Benutzer bestimmt. Sie wurden hauptsächlich für diejenigen bereitgestellt, die daran interessiert sind, neue Kommunikationsprotokolle zu entwickeln oder Zugang zu einigen der kryptischeren Einrichtungen eines vorhandenen Protokolls zu erhalten.
Sequenced Packet Sockets- Sie ähneln einem Stream-Socket, mit der Ausnahme, dass Datensatzgrenzen beibehalten werden. Diese Schnittstelle wird nur als Teil der Socket-Abstraktion von Network Systems (NS) bereitgestellt und ist in den meisten ernsthaften NS-Anwendungen sehr wichtig. Mit Sockets mit sequenzierten Paketen kann der Benutzer die SPP-Header (Sequence Packet Protocol) oder IDP-Header (Internet Datagram Protocol) für ein Paket oder eine Gruppe von Paketen bearbeiten, indem er entweder einen Prototyp-Header zusammen mit den zu sendenden Daten schreibt oder von Angabe eines Standard-Headers, der für alle ausgehenden Daten verwendet werden soll, und Ermöglichen, dass der Benutzer die Header für eingehende Pakete empfängt.
Was kommt als nächstes?
Die nächsten Kapitel sollen Ihre Grundlagen stärken und eine Grundlage vorbereiten, bevor Sie Server- und Client-Programme mit Socket schreiben können . Wenn Sie direkt springen möchten, um zu sehen, wie ein Client- und Serverprogramm geschrieben wird, können Sie dies tun, dies wird jedoch nicht empfohlen. Es wird dringend empfohlen, dass Sie Schritt für Schritt diese ersten Kapitel ausfüllen, um Ihre Basis zu erstellen, bevor Sie mit der Programmierung fortfahren.
Bevor wir mit den eigentlichen Dingen fortfahren, lassen Sie uns ein wenig über die Netzwerkadressen - die IP-Adresse - sprechen.
Die IP-Hostadresse oder üblicherweise nur die IP-Adresse wird verwendet, um mit dem Internet verbundene Hosts zu identifizieren. IP steht für Internet Protocol und bezieht sich auf die Internetschicht der gesamten Netzwerkarchitektur des Internets.
Eine IP-Adresse ist eine 32-Bit-Größe, die als vier 8-Bit-Zahlen oder Oktette interpretiert wird. Jede IP-Adresse identifiziert das teilnehmende Benutzernetzwerk, den Host im Netzwerk und die Klasse des Benutzernetzwerks eindeutig.
Eine IP-Adresse wird normalerweise in einer Punkt-Dezimal-Notation der Form N1.N2.N3.N4 geschrieben, wobei jedes Ni eine Dezimalzahl zwischen 0 und 255 Dezimalstellen (00 bis FF hexadezimal) ist.
Adressklassen
IP-Adressen werden von der Internet Assigned Numbers Authority (IANA) verwaltet und erstellt . Es gibt fünf verschiedene Adressklassen. Sie können bestimmen, in welcher Klasse sich eine IP-Adresse befindet, indem Sie die ersten vier Bits der IP-Adresse untersuchen.
Class A Adressen beginnen mit 0xxx, oder 1 to 126 Dezimal.
Class B Adressen beginnen mit 10xx, oder 128 to 191 Dezimal.
Class C Adressen beginnen mit 110x, oder 192 to 223 Dezimal.
Class D Adressen beginnen mit 1110, oder 224 to 239 Dezimal.
Class E Adressen beginnen mit 1111, oder 240 to 254 Dezimal.
Adressen beginnend mit 01111111, oder 127 dezimal, sind für Loopback und für interne Tests auf einem lokalen Computer reserviert [Sie können dies testen: Sie sollten immer in der Lage sein, einen Ping-Befehl zu senden 127.0.0.1, was auf dich zeigt]; Adressen der Klasse D sind für Multicasting reserviert. Adressen der Klasse E sind für die zukünftige Verwendung reserviert. Sie sollten nicht für Hostadressen verwendet werden.
Beispiel
Class | Leftmost bits | Start address | Finish address |
EIN | 0xxx | 0.0.0.0 | 127.255.255.255 |
B. | 10xx | 128.0.0.0 | 191.255.255.255 |
C. | 110x | 192.0.0.0 | 223.255.255.255 |
D. | 1110 | 224.0.0.0 | 239.255.255.255 |
E. | 1111 | 240.0.0.0 | 255.255.255.255 |
Subnetz
Subnetz oder Subnetz bedeutet im Grunde, ein Netzwerk abzweigen. Dies kann aus verschiedenen Gründen erfolgen, z. B. aufgrund des Netzwerks in einer Organisation, der Verwendung verschiedener physischer Medien (wie Ethernet, FDDI, WAN usw.), der Wahrung des Adressraums und der Sicherheit. Der häufigste Grund ist die Steuerung des Netzwerkverkehrs.
Die Grundidee beim Subnetz besteht darin, den Host-ID-Teil der IP-Adresse in zwei Teile zu unterteilen:
- Eine Subnetzadresse innerhalb der Netzwerkadresse selbst; und
- Eine Hostadresse im Subnetz.
Ein allgemeines Adressformat der Klasse B ist beispielsweise N1.N2.SH, wobei N1.N2 das Netzwerk der Klasse B identifiziert, das 8-Bit-S-Feld das Subnetz und das 8-Bit-H-Feld den Host im Subnetz identifiziert.
Hostnamen in Bezug auf Zahlen sind schwer zu merken und werden daher von gewöhnlichen Namen wie Takshila oder Nalanda bezeichnet. Wir schreiben Softwareanwendungen, um die gepunktete IP-Adresse herauszufinden, die einem bestimmten Namen entspricht.
Der Prozess zum Ermitteln der gepunkteten IP-Adresse basierend auf dem angegebenen alphanumerischen Hostnamen ist bekannt als hostname resolution.
Eine Hostnamenauflösung erfolgt durch spezielle Software, die sich auf Systemen mit hoher Kapazität befindet. Diese Systeme werden als Domain Name Systems (DNS) bezeichnet, die die Zuordnung von IP-Adressen und den entsprechenden gewöhnlichen Namen beibehalten.
Die Datei / etc / hosts
Die Entsprechung zwischen Hostnamen und IP-Adressen wird in einer Datei namens Hosts verwaltet . Auf den meisten Systemen befindet sich diese Datei in/etc Verzeichnis.
Einträge in dieser Datei sehen wie folgt aus:
# This represents a comments in /etc/hosts file.
127.0.0.1 localhost
192.217.44.207 nalanda metro
153.110.31.18 netserve
153.110.31.19 mainserver centeral
153.110.31.20 samsonite
64.202.167.10 ns3.secureserver.net
64.202.167.97 ns4.secureserver.net
66.249.89.104 www.google.com
68.178.157.132 services.amrood.com
Beachten Sie, dass einer bestimmten IP-Adresse möglicherweise mehr als ein Name zugeordnet ist. Diese Datei wird beim Konvertieren von IP-Adresse in Hostname und umgekehrt verwendet.
Sie hätten keinen Zugriff auf die Bearbeitung dieser Datei. Wenn Sie also einen Hostnamen zusammen mit der IP-Adresse eingeben möchten, benötigen Sie eine Root-Berechtigung.
Die meisten Netzanwendungen verwenden die Client-Server-Architektur, die sich auf zwei Prozesse oder zwei Anwendungen bezieht, die miteinander kommunizieren, um einige Informationen auszutauschen. Einer der beiden Prozesse fungiert als Client-Prozess und ein anderer Prozess als Server.
Client-Prozess
Dies ist der Prozess, bei dem normalerweise Informationen angefordert werden. Nach Erhalt der Antwort wird dieser Prozess möglicherweise beendet oder es wird eine andere Verarbeitung ausgeführt.
ExampleDer Internetbrowser fungiert als Clientanwendung, die eine Anfrage an den Webserver sendet, um eine HTML-Webseite abzurufen.
Serverprozess
Dies ist der Prozess, der eine Anfrage von den Clients entgegennimmt. Nachdem Sie eine Anfrage vom Client erhalten haben, führt dieser Prozess die erforderliche Verarbeitung durch, sammelt die angeforderten Informationen und sendet sie an den Client des Anforderers. Sobald dies erledigt ist, kann ein anderer Kunde bedient werden. Serverprozesse sind immer aufmerksam und bereit, eingehende Anforderungen zu bearbeiten.
Example - Der Webserver wartet ständig auf Anfragen von Internetbrowsern. Sobald er eine Anfrage von einem Browser erhält, nimmt er eine angeforderte HTML-Seite auf und sendet sie an diesen Browser zurück.
Beachten Sie, dass der Client die Adresse des Servers kennen muss, der Server jedoch weder die Adresse noch die Existenz des Clients kennen muss, bevor die Verbindung hergestellt wird. Sobald eine Verbindung hergestellt ist, können beide Seiten Informationen senden und empfangen.
2-Tier- und 3-Tier-Architekturen
Es gibt zwei Arten von Client-Server-Architekturen:
2-tier architecture- In dieser Architektur interagiert der Client direkt mit dem Server. Diese Art von Architektur kann einige Sicherheitslücken und Leistungsprobleme aufweisen. Internet Explorer und Webserver arbeiten mit einer zweistufigen Architektur. Hier werden Sicherheitsprobleme mit Secure Socket Layer (SSL) gelöst.
3-tier architectures- In dieser Architektur befindet sich eine weitere Software zwischen dem Client und dem Server. Diese mittlere Software heißt "Middleware". Middleware wird verwendet, um alle Sicherheitsüberprüfungen und den Lastausgleich bei hoher Last durchzuführen. Eine Middleware nimmt alle Anforderungen vom Client entgegen und leitet diese Anforderung nach Durchführung der erforderlichen Authentifizierung an den Server weiter. Anschließend führt der Server die erforderliche Verarbeitung durch und sendet die Antwort an die Middleware zurück. Schließlich gibt die Middleware diese Antwort an den Client zurück. Wenn Sie eine dreistufige Architektur implementieren möchten, können Sie jede Middleware wie Web Logic oder WebSphere-Software zwischen Ihrem Webserver und Ihrem Webbrowser aufbewahren.
Servertypen
Es gibt zwei Arten von Servern:
Iterative Server- Dies ist die einfachste Serverform, bei der ein Serverprozess einen Client bedient und nach Abschluss der ersten Anforderung eine Anforderung von einem anderen Client entgegennimmt. Währenddessen wartet ein anderer Kunde weiter.
Concurrent Servers- Dieser Servertyp führt mehrere gleichzeitige Prozesse aus, um viele Anforderungen gleichzeitig zu bearbeiten, da ein Prozess möglicherweise länger dauert und ein anderer Client nicht so lange warten kann. Der einfachste Weg , um einen gleichzeitigen Server unter Unix zu schreiben , ist zu gabeln ein Kind Prozess jeden Kunden separat zu behandeln.
Wie erstelle ich Client
Die Systemaufrufe zum Herstellen einer Verbindung sind für den Client und den Server etwas unterschiedlich, beide beinhalten jedoch das Grundkonstrukt eines Sockets. Beide Prozesse richten ihre eigenen Sockets ein.
Die Schritte zum Einrichten eines Sockets auf der Client-Seite sind wie folgt:
Erstellen Sie einen Socket mit dem socket() Systemaufruf.
Verbinden Sie den Socket mit der Adresse des Servers connect() Systemaufruf.
Daten senden und empfangen. Es gibt eine Reihe von Möglichkeiten, dies zu tun, aber die einfachste Möglichkeit ist die Verwendung vonread() und write() Systemaufrufe.
Wie erstelle ich einen Server?
Die Schritte zum Einrichten eines Sockets auf der Serverseite sind wie folgt:
Erstellen Sie einen Socket mit dem socket() Systemaufruf.
Binden Sie den Socket mit dem an eine Adresse bind()Systemaufruf. Bei einem Server-Socket im Internet besteht eine Adresse aus einer Portnummer auf dem Host-Computer.
Achten Sie auf Verbindungen mit dem listen() Systemaufruf.
Akzeptieren Sie eine Verbindung mit dem accept()Systemaufruf. Dieser Aufruf blockiert normalerweise die Verbindung, bis ein Client eine Verbindung zum Server herstellt.
Senden und Empfangen von Daten mit dem read() und write() Systemaufrufe.
Client- und Server-Interaktion
Das folgende Diagramm zeigt die vollständige Client- und Server-Interaktion.
In der Unix-Socket-Programmierung werden verschiedene Strukturen verwendet, um Informationen über die Adresse und den Port sowie andere Informationen zu speichern. Die meisten Socket-Funktionen erfordern einen Zeiger auf eine Socket-Adressstruktur als Argument. In diesem Kapitel definierte Strukturen beziehen sich auf die Internetprotokollfamilie.
sockaddr
Die erste Struktur ist sockaddr , die die Socket-Informationen enthält -
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
Dies ist eine generische Socket-Adressstruktur, die in den meisten Socket-Funktionsaufrufen übergeben wird. Die folgende Tabelle enthält eine Beschreibung der Mitgliedsfelder -
Attribut | Werte | Beschreibung |
---|---|---|
sa_family | AF_INET AF_UNIX AF_NS AF_IMPLINK |
Es repräsentiert eine Adressfamilie. In den meisten internetbasierten Anwendungen verwenden wir AF_INET. |
sa_data | Protokollspezifische Adresse | Der Inhalt der 14 Bytes der protokollspezifischen Adresse wird entsprechend der Art der Adresse interpretiert. Für die Internetfamilie verwenden wir die IP-Adresse der Portnummer, die durch die unten definierte Struktur sockaddr_in dargestellt wird. |
sockaddr in
Die zweite Struktur, mit der Sie auf die Elemente des Sockets verweisen können, lautet wie folgt:
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
Hier ist die Beschreibung der Mitgliedsfelder -
Attribut | Werte | Beschreibung |
---|---|---|
sa_family | AF_INET AF_UNIX AF_NS AF_IMPLINK |
Es repräsentiert eine Adressfamilie. In den meisten internetbasierten Anwendungen verwenden wir AF_INET. |
sin_port | Service-Port | Eine 16-Bit-Portnummer in Netzwerkbyte-Reihenfolge. |
sin_addr | IP Adresse | Eine 32-Bit-IP-Adresse in Netzwerkbyte-Reihenfolge. |
sin_zero | Nicht benutzt | Sie setzen diesen Wert einfach auf NULL, da dieser nicht verwendet wird. |
in addr
Diese Struktur wird nur in der obigen Struktur als Strukturfeld verwendet und enthält 32-Bit-Netz-ID / Host-ID.
struct in_addr {
unsigned long s_addr;
};
Hier ist die Beschreibung der Mitgliedsfelder -
Attribut | Werte | Beschreibung |
---|---|---|
s_addr | Service-Port | Eine 32-Bit-IP-Adresse in Netzwerkbyte-Reihenfolge. |
Hostent
Diese Struktur wird verwendet, um Informationen zum Host zu speichern.
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list
#define h_addr h_addr_list[0]
};
Hier ist die Beschreibung der Mitgliedsfelder -
Attribut | Werte | Beschreibung |
---|---|---|
h_name | ti.com etc. | Es ist der offizielle Name des Gastgebers. Zum Beispiel tutorialspoint.com, google.com usw. |
h_aliases | TI | Es enthält eine Liste von Hostnamen-Aliasnamen. |
h_addrtype | AF_INET | Es enthält die Adressfamilie und ist bei internetbasierten Anwendungen immer AF_INET. |
h_Länge | 4 | Es enthält die Länge der IP-Adresse, die für die Internetadresse 4 beträgt. |
h_addr_list | in_addr | Bei Internetadressen sind die Zeigerarrays h_addr_list [0], h_addr_list [1] usw. Punkte für die Strukturierung in_addr. |
NOTE - h_addr ist als h_addr_list [0] definiert, um die Abwärtskompatibilität zu gewährleisten.
Diener
Diese spezielle Struktur wird verwendet, um Informationen zu Diensten und zugehörigen Ports zu speichern.
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
Hier ist die Beschreibung der Mitgliedsfelder -
Attribut | Werte | Beschreibung |
---|---|---|
s_name | http | Dies ist der offizielle Name des Dienstes. Zum Beispiel SMTP, FTP POP3 usw. |
s_aliases | ALIAS | Es enthält die Liste der Service-Aliase. Meistens wird dies auf NULL gesetzt. |
Sport | 80 | Es wird eine Portnummer zugeordnet sein. Für HTTP sind dies beispielsweise 80. |
s_proto | TCP UDP |
Es wird auf das verwendete Protokoll eingestellt. Internetdienste werden entweder über TCP oder UDP bereitgestellt. |
Tipps zu Sockelstrukturen
Socket-Adressstrukturen sind ein wesentlicher Bestandteil jedes Netzwerkprogramms. Wir ordnen sie zu, füllen sie aus und übergeben ihnen Zeiger auf verschiedene Socket-Funktionen. Manchmal übergeben wir einen Zeiger auf eine dieser Strukturen an eine Socket-Funktion, die den Inhalt ausfüllt.
Wir übergeben diese Strukturen immer als Referenz (dh wir übergeben einen Zeiger auf die Struktur, nicht auf die Struktur selbst), und wir übergeben immer die Größe der Struktur als ein weiteres Argument.
Wenn eine Socket-Funktion eine Struktur ausfüllt, wird die Länge auch als Referenz übergeben, damit ihr Wert von der Funktion aktualisiert werden kann. Wir nennen diese Wertergebnisargumente.
Setzen Sie die Strukturvariablen immer auf NULL (dh '\ 0'), indem Sie memset () für bzero () -Funktionen verwenden, da sonst unerwartete Junk-Werte in Ihrer Struktur auftreten können.
Wenn ein Client-Prozess eine Verbindung zu einem Server herstellen möchte, muss der Client eine Möglichkeit haben, den Server zu identifizieren, zu dem er eine Verbindung herstellen möchte. Wenn der Client die 32-Bit-Internetadresse des Hosts kennt, auf dem sich der Server befindet, kann er diesen Host kontaktieren. Aber wie identifiziert der Client den bestimmten Serverprozess, der auf diesem Host ausgeführt wird?
Um das Problem der Identifizierung eines bestimmten Serverprozesses zu lösen, der auf einem Host ausgeführt wird, haben sowohl TCP als auch UDP eine Gruppe bekannter Ports definiert.
Für unseren Zweck wird ein Port als eine Ganzzahl zwischen 1024 und 65535 definiert. Dies liegt daran, dass alle Portnummern, die kleiner als 1024 sind, als bekannt gelten. Beispielsweise verwendet Telnet Port 23, http verwendet 80, ftp verwendet 21, und so weiter.
Die Portzuweisungen für Netzwerkdienste finden Sie in der Datei / etc / services. Wenn Sie Ihren eigenen Server schreiben, muss darauf geachtet werden, Ihrem Server einen Port zuzuweisen. Sie sollten sicherstellen, dass dieser Port keinem anderen Server zugewiesen wird.
Normalerweise ist es üblich, eine Portnummer von mehr als 5000 zuzuweisen. Es gibt jedoch viele Organisationen, die Server mit Portnummern von mehr als 5000 geschrieben haben. Beispielsweise läuft Yahoo Messenger auf 5050, SIP Server auf 5060 usw.
Beispiel für Ports und Dienste
Hier ist eine kleine Liste von Diensten und zugehörigen Ports. Die aktuellste Liste der Internet-Ports und des zugehörigen Dienstes finden Sie unter IANA - TCP / IP-Portzuweisungen .
Service | Port Number | Service Description |
Echo | 7 | UDP / TCP sendet zurück, was es empfängt. |
verwerfen | 9 | UDP / TCP wirft Eingaben weg. |
tagsüber | 13 | UDP / TCP gibt die ASCII-Zeit zurück. |
chargen | 19 | UDP / TCP gibt Zeichen zurück. |
ftp | 21 | TCP-Dateiübertragung. |
Telnet | 23 | TCP-Remote-Anmeldung. |
smtp | 25 | TCP-E-Mail. |
tagsüber | 37 | UDP / TCP gibt die Binärzeit zurück. |
tftp | 69 | UDP triviale Dateiübertragung. |
Finger | 79 | TCP-Informationen zu Benutzern. |
http | 80 | TCP World Wide Web. |
Anmeldung | 513 | TCP-Remote-Anmeldung. |
Wer | 513 | UDP verschiedene Informationen zu Benutzern. |
Xserver | 6000 | TCP X-Fenster (NB> 1023). |
Port- und Servicefunktionen
Unix bietet die folgenden Funktionen zum Abrufen des Dienstnamens aus der Datei / etc / services.
struct servent *getservbyname(char *name, char *proto) - Dieser Aufruf verwendet den Dienstnamen und den Protokollnamen und gibt die entsprechende Portnummer für diesen Dienst zurück.
struct servent *getservbyport(int port, char *proto) - Dieser Aufruf übernimmt die Portnummer und den Protokollnamen und gibt den entsprechenden Dienstnamen zurück.
Der Rückgabewert für jede Funktion ist ein Zeiger auf eine Struktur mit der folgenden Form:
struct servent {
char *s_name;
char **s_aliases;
int s_port;
char *s_proto;
};
Hier ist die Beschreibung der Mitgliedsfelder -
Attribut | Werte | Beschreibung |
---|---|---|
s_name | http | Es ist der offizielle Name des Dienstes. Zum Beispiel SMTP, FTP POP3 usw. |
s_aliases | ALIAS | Es enthält die Liste der Service-Aliase. Meistens wird es auf NULL gesetzt. |
Sport | 80 | Es wird die zugehörige Portnummer haben. Für HTTP sind es beispielsweise 80. |
s_proto | TCP UDP |
Es wird auf das verwendete Protokoll eingestellt. Internetdienste werden entweder über TCP oder UDP bereitgestellt. |
Leider speichern nicht alle Computer die Bytes, aus denen ein Multibyte-Wert besteht, in derselben Reihenfolge. Stellen Sie sich ein 16-Bit-Internet vor, das aus 2 Bytes besteht. Es gibt zwei Möglichkeiten, diesen Wert zu speichern.
Little Endian - In diesem Schema wird ein Byte niedriger Ordnung an der Startadresse (A) und ein Byte höherer Ordnung an der nächsten Adresse (A + 1) gespeichert.
Big Endian - In diesem Schema wird ein höherwertiges Byte an der Startadresse (A) und ein niederwertiges Byte an der nächsten Adresse (A + 1) gespeichert.
Damit Maschinen mit unterschiedlichen Konventionen für die Bytereihenfolge miteinander kommunizieren können, spezifizieren die Internetprotokolle eine Konvention für die kanonische Bytereihenfolge für Daten, die über das Netzwerk übertragen werden. Dies wird als Netzwerkbyte-Reihenfolge bezeichnet.
Beim Herstellen einer Internet-Socket-Verbindung müssen Sie sicherstellen, dass die Daten in den Mitgliedern sin_port und sin_addr der Struktur sockaddr_in in der Netzwerkbyte-Reihenfolge dargestellt werden.
Byte-Bestellfunktionen
Die Routinen zum Konvertieren von Daten zwischen der internen Darstellung eines Hosts und der Netzwerkbyte-Reihenfolge lauten wie folgt:
Funktion | Beschreibung |
---|---|
htons () | Host zu Network Short |
htonl () | Host zu Network Long |
ntohl () | Netzwerk, um lange zu hosten |
ntohs () | Netzwerk zu Host Short |
Nachfolgend finden Sie einige Details zu diesen Funktionen -
unsigned short htons(unsigned short hostshort) - Diese Funktion konvertiert 16-Bit-Mengen (2 Byte) von der Host-Byte-Reihenfolge in die Netzwerk-Byte-Reihenfolge.
unsigned long htonl(unsigned long hostlong) - Diese Funktion konvertiert 32-Bit-Mengen (4 Byte) von der Host-Byte-Reihenfolge in die Netzwerk-Byte-Reihenfolge.
unsigned short ntohs(unsigned short netshort) - Diese Funktion konvertiert 16-Bit-Mengen (2 Byte) von der Netzwerkbyte-Reihenfolge in die Host-Byte-Reihenfolge.
unsigned long ntohl(unsigned long netlong) - Diese Funktion konvertiert 32-Bit-Mengen von der Netzwerkbyte-Reihenfolge in die Host-Byte-Reihenfolge.
Diese Funktionen sind Makros und führen zum Einfügen von Konvertierungsquellcode in das aufrufende Programm. Auf Little-Endian-Computern ändert der Code die Werte in die Netzwerkbyte-Reihenfolge. Auf Big-Endian-Computern wird kein Code eingefügt, da keiner benötigt wird. Die Funktionen sind als null definiert.
Programm zum Bestimmen der Host-Bytereihenfolge
Behalten Sie den folgenden Code in einer Datei byteorder.c bei, kompilieren Sie ihn und führen Sie ihn auf Ihrem Computer aus.
In diesem Beispiel speichern wir den Zwei-Byte-Wert 0x0102 in der kurzen Ganzzahl und betrachten dann die beiden aufeinander folgenden Bytes c [0] (die Adresse A) und c [1] (die Adresse A + 1), um das Byte zu bestimmen Auftrag.
#include <stdio.h>
int main(int argc, char **argv) {
union {
short s;
char c[sizeof(short)];
}un;
un.s = 0x0102;
if (sizeof(short) == 2) {
if (un.c[0] == 1 && un.c[1] == 2)
printf("big-endian\n");
else if (un.c[0] == 2 && un.c[1] == 1)
printf("little-endian\n");
else
printf("unknown\n");
}
else {
printf("sizeof(short) = %d\n", sizeof(short));
}
exit(0);
}
Eine von diesem Programm auf einem Pentium-Computer erzeugte Ausgabe lautet wie folgt:
$> gcc byteorder.c $> ./a.out
little-endian
$>
Unix bietet verschiedene Funktionsaufrufe, mit denen Sie IP-Adressen bearbeiten können. Diese Funktionen konvertieren Internetadressen zwischen ASCII-Zeichenfolgen (was Menschen bevorzugen) und Binärwerten in Netzwerkbyte-Reihenfolge (Werte, die in Socket-Adressstrukturen gespeichert sind).
Die folgenden drei Funktionsaufrufe werden für die IPv4-Adressierung verwendet:
- int inet_aton (const char * strptr, struct in_addr * addrptr)
- in_addr_t inet_addr (const char * strptr)
- char * inet_ntoa (struct in_addr inaddr)
int inet_aton (const char * strptr, struct in_addr * addrptr)
Dieser Funktionsaufruf konvertiert die angegebene Zeichenfolge in der Internet-Standardpunktnotation in eine Netzwerkadresse und speichert die Adresse in der bereitgestellten Struktur. Die konvertierte Adresse befindet sich in der Reihenfolge der Netzwerkbytes (Bytes von links nach rechts). Es gibt 1 zurück, wenn die Zeichenfolge gültig war, und 0 bei einem Fehler.
Es folgt das Verwendungsbeispiel -
#include <arpa/inet.h>
(...)
int retval;
struct in_addr addrptr
memset(&addrptr, '\0', sizeof(addrptr));
retval = inet_aton("68.178.157.132", &addrptr);
(...)
in_addr_t inet_addr (const char * strptr)
Dieser Funktionsaufruf konvertiert die angegebene Zeichenfolge in der Internet-Standardpunktnotation in einen ganzzahligen Wert, der zur Verwendung als Internetadresse geeignet ist. Die konvertierte Adresse befindet sich in der Reihenfolge der Netzwerkbytes (Bytes von links nach rechts). Es gibt eine 32-Bit-IPv4-Adresse mit binärem Netzwerkbyte und INADDR_NONE bei einem Fehler zurück.
Es folgt das Verwendungsbeispiel -
#include <arpa/inet.h>
(...)
struct sockaddr_in dest;
memset(&dest, '\0', sizeof(dest));
dest.sin_addr.s_addr = inet_addr("68.178.157.132");
(...)
char * inet_ntoa (struct in_addr inaddr)
Dieser Funktionsaufruf konvertiert die angegebene Internet-Host-Adresse in eine Zeichenfolge in der Internet-Standard-Punktnotation.
Es folgt das Verwendungsbeispiel -
#include <arpa/inet.h>
(...)
char *ip;
ip = inet_ntoa(dest.sin_addr);
printf("IP Address is: %s\n",ip);
(...)
In diesem Kapitel werden die wichtigsten Socket-Funktionen beschrieben, die zum Schreiben eines vollständigen TCP-Clients und -Servers erforderlich sind.
Das folgende Diagramm zeigt die vollständige Client- und Server-Interaktion -
Die Socket-Funktion
Um eine Netzwerk-E / A durchzuführen, muss ein Prozess zunächst die Socket-Funktion aufrufen und den Typ des gewünschten Kommunikationsprotokolls und die Protokollfamilie usw. angeben.
#include <sys/types.h>
#include <sys/socket.h>
int socket (int family, int type, int protocol);
Dieser Aufruf gibt einen Socket-Deskriptor zurück, den Sie in späteren Systemaufrufen verwenden können, oder -1 bei einem Fehler.
Parameter
family - Es gibt die Protokollfamilie an und ist eine der unten gezeigten Konstanten. -
Familie | Beschreibung |
---|---|
AF_INET | IPv4-Protokolle |
AF_INET6 | IPv6-Protokolle |
AF_LOCAL | Unix-Domänenprotokolle |
AF_ROUTE | Routing-Sockets |
AF_KEY | Ket Steckdose |
Dieses Kapitel behandelt keine anderen Protokolle als IPv4.
type- Es gibt die Art der gewünschten Steckdose an. Es kann einen der folgenden Werte annehmen -
Art | Beschreibung |
---|---|
SOCK_STREAM | Stream-Socket |
SOCK_DGRAM | Datagramm-Socket |
SOCK_SEQPACKET | Sequenzierter Paketsocket |
SOCK_RAW | Roher Sockel |
protocol - Das Argument sollte auf den unten angegebenen Protokolltyp oder auf 0 gesetzt werden, um den Standard des Systems für die angegebene Kombination aus Familie und Typ auszuwählen. -
Protokoll | Beschreibung |
---|---|
IPPROTO_TCP | TCP-Transportprotokoll |
IPPROTO_UDP | UDP-Transportprotokoll |
IPPROTO_SCTP | SCTP-Transportprotokoll |
Die Connect - Funktion
Die Verbindungsfunktion wird von einem TCP-Client verwendet, um eine Verbindung mit einem TCP-Server herzustellen.
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
Dieser Aufruf gibt 0 zurück, wenn eine erfolgreiche Verbindung zum Server hergestellt wurde, andernfalls wird bei einem Fehler -1 zurückgegeben.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
serv_addr - Es ist ein Zeiger auf struct sockaddr, der die Ziel-IP-Adresse und den Port enthält.
addrlen - Stellen Sie es auf sizeof (struct sockaddr) ein.
Die bind - Funktion
Die Bindefunktion weist einem Socket eine lokale Protokolladresse zu. Bei den Internetprotokollen ist die Protokolladresse die Kombination aus einer 32-Bit-IPv4-Adresse oder einer 128-Bit-IPv6-Adresse sowie einer 16-Bit-TCP- oder UDP-Portnummer. Diese Funktion wird nur vom TCP-Server aufgerufen.
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr,int addrlen);
Dieser Aufruf gibt 0 zurück, wenn er erfolgreich an die Adresse gebunden wurde, andernfalls wird bei einem Fehler -1 zurückgegeben.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
my_addr - Es ist ein Zeiger auf struct sockaddr, der die lokale IP-Adresse und den Port enthält.
addrlen - Stellen Sie es auf sizeof (struct sockaddr) ein.
Sie können Ihre IP-Adresse und Ihren Port automatisch eingeben
Ein 0-Wert für die Portnummer bedeutet, dass das System einen zufälligen Port auswählt , und ein INADDR_ANY- Wert für die IP-Adresse bedeutet, dass die IP-Adresse des Servers automatisch zugewiesen wird.
server.sin_port = 0;
server.sin_addr.s_addr = INADDR_ANY;
NOTE- Alle Ports unter 1024 sind reserviert. Sie können einen Port über 1024 und unter 65535 festlegen, es sei denn, diese werden von anderen Programmen verwendet.
Die Listen- Funktion
Die Listen- Funktion wird nur von einem TCP-Server aufgerufen und führt zwei Aktionen aus:
Die Listen-Funktion konvertiert einen nicht verbundenen Socket in einen passiven Socket und gibt an, dass der Kernel eingehende Verbindungsanforderungen an diesen Socket akzeptieren soll.
Das zweite Argument für diese Funktion gibt die maximale Anzahl von Verbindungen an, die der Kernel für diesen Socket in die Warteschlange stellen soll.
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd,int backlog);
Dieser Aufruf gibt bei Erfolg 0 zurück, andernfalls gibt er bei Fehler -1 zurück.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
backlog - Dies ist die Anzahl der zulässigen Verbindungen.
Die akzeptieren Funktion
Die Accept- Funktion wird von einem TCP-Server aufgerufen, um die nächste abgeschlossene Verbindung von der Vorderseite der Warteschlange für abgeschlossene Verbindungen zurückzugeben. Die Signatur des Anrufs lautet wie folgt:
#include <sys/types.h>
#include <sys/socket.h>
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
Dieser Aufruf gibt bei Erfolg einen nicht negativen Deskriptor zurück, andernfalls wird bei einem Fehler -1 zurückgegeben. Es wird angenommen, dass der zurückgegebene Deskriptor ein Client-Socket-Deskriptor ist, und alle Lese- / Schreibvorgänge werden an diesem Deskriptor ausgeführt, um mit dem Client zu kommunizieren.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
cliaddr - Es ist ein Zeiger auf struct sockaddr, der die Client-IP-Adresse und den Port enthält.
addrlen - Stellen Sie es auf sizeof (struct sockaddr) ein.
Die Sendefunktion
Die Sendefunktion wird verwendet , um Daten über Stream - Sockets oder CONNECTED Datagramm - Sockets zu senden. Wenn Sie Daten über UNCONNECTED-Datagramm-Sockets senden möchten, müssen Sie die Funktion sendto () verwenden.
Sie können den Systemaufruf write () verwenden, um Daten zu senden. Die Unterschrift lautet wie folgt:
int send(int sockfd, const void *msg, int len, int flags);
Dieser Aufruf gibt die Anzahl der gesendeten Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
msg - Es ist ein Zeiger auf die Daten, die Sie senden möchten.
len - Dies ist die Länge der Daten, die Sie senden möchten (in Bytes).
flags - Es wird auf 0 gesetzt.
Die Recv- Funktion
Die Recv- Funktion wird verwendet, um Daten über Stream-Sockets oder CONNECTED-Datagramm-Sockets zu empfangen. Wenn Sie Daten über UNCONNECTED-Datagramm-Sockets empfangen möchten, müssen Sie recvfrom () verwenden.
Sie können den Systemaufruf read () verwenden, um die Daten zu lesen. Dieser Aufruf wird im Kapitel Hilfsfunktionen erläutert.
int recv(int sockfd, void *buf, int len, unsigned int flags);
Dieser Aufruf gibt die Anzahl der in den Puffer gelesenen Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
buf - Es ist der Puffer, in den die Informationen eingelesen werden.
len - Dies ist die maximale Länge des Puffers.
flags - Es wird auf 0 gesetzt.
Die sendto- Funktion
Die Sendto- Funktion wird verwendet, um Daten über UNCONNECTED-Datagramm-Sockets zu senden. Die Unterschrift lautet wie folgt:
int sendto(int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen);
Dieser Aufruf gibt die Anzahl der gesendeten Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
msg - Es ist ein Zeiger auf die Daten, die Sie senden möchten.
len - Dies ist die Länge der Daten, die Sie senden möchten (in Bytes).
flags - Es wird auf 0 gesetzt.
to - Es ist ein Zeiger auf struct sockaddr für den Host, an den Daten gesendet werden müssen.
tolen - Es wird auf sizeof (struct sockaddr) gesetzt.
Die Funktion recvfrom
Die Funktion recvfrom wird verwendet, um Daten von UNCONNECTED-Datagramm-Sockets zu empfangen.
int recvfrom(int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen);
Dieser Aufruf gibt die Anzahl der in den Puffer gelesenen Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
buf - Es ist der Puffer, in den die Informationen eingelesen werden.
len - Dies ist die maximale Länge des Puffers.
flags - Es wird auf 0 gesetzt.
from - Es ist ein Zeiger auf struct sockaddr für den Host, auf dem Daten gelesen werden müssen.
fromlen - Es wird auf sizeof (struct sockaddr) gesetzt.
Die enge Funktion
Die Schließfunktion wird verwendet, um die Kommunikation zwischen dem Client und dem Server zu schließen. Die Syntax lautet wie folgt:
int close( int sockfd );
Dieser Aufruf gibt bei Erfolg 0 zurück, andernfalls gibt er bei Fehler -1 zurück.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
Die Shutdown - Funktion
Die Shutdown- Funktion wird verwendet, um die Kommunikation zwischen dem Client und dem Server ordnungsgemäß zu schließen. Diese Funktion bietet mehr Kontrolle im Vergleich zur Schließfunktion . Unten ist die Syntax des Herunterfahrens angegeben -
int shutdown(int sockfd, int how);
Dieser Aufruf gibt bei Erfolg 0 zurück, andernfalls gibt er bei Fehler -1 zurück.
Parameter
sockfd - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
how - Geben Sie eine der Zahlen ein -
0 - zeigt an, dass das Empfangen nicht erlaubt ist,
1 - zeigt an, dass das Senden nicht erlaubt ist, und
2- zeigt an, dass sowohl Senden als auch Empfangen nicht zulässig sind. Wenn how auf 2 gesetzt ist, ist es dasselbe wie close ().
Die select - Funktion
Die Auswahlfunktion gibt an, welcher der angegebenen Dateideskriptoren zum Lesen oder Schreiben bereit ist oder eine Fehlerbedingung aussteht.
Wenn eine Anwendung recv oder recvfrom aufruft , wird sie blockiert, bis Daten für diesen Socket eintreffen. Eine Anwendung kann eine andere nützliche Verarbeitung ausführen, während der eingehende Datenstrom leer ist. Eine andere Situation ist, wenn eine Anwendung Daten von mehreren Sockets empfängt.
Das Aufrufen von recv oder recvfrom an einem Socket, dessen Eingabewarteschlange keine Daten enthält, verhindert den sofortigen Empfang von Daten von anderen Sockets. Der Funktionsaufruf select löst dieses Problem, indem das Programm alle Socket-Handles abfragen kann, um festzustellen, ob sie für nicht blockierende Lese- und Schreibvorgänge verfügbar sind.
Unten ist die Syntax von select - angegeben.
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);
Dieser Aufruf gibt bei Erfolg 0 zurück, andernfalls gibt er bei Fehler -1 zurück.
Parameter
nfds- Gibt den Bereich der zu testenden Dateideskriptoren an. Die Funktion select () testet Dateideskriptoren im Bereich von 0 bis nfds-1
readfds- Es zeigt auf ein Objekt vom Typ fd_set , das bei der Eingabe die zu lesenden Dateideskriptoren angibt und bei der Ausgabe angibt, welche Dateideskriptoren zum Lesen bereit sind. Es kann NULL sein, um eine leere Menge anzuzeigen.
writefds- Es zeigt auf ein Objekt vom Typ fd_set , das bei der Eingabe die zu beschreibenden Dateideskriptoren angibt und bei der Ausgabe angibt, welche Dateideskriptoren schreibbereit sind. Es kann NULL sein, um eine leere Menge anzuzeigen.
exceptfds- Es zeigt auf ein Objekt vom Typ fd_set , das bei der Eingabe die Dateideskriptoren angibt, die auf anstehende Fehlerbedingungen überprüft werden sollen, und bei der Ausgabe angibt, bei welchen Dateideskriptoren Fehlerbedingungen anstehen. Es kann NULL sein, um eine leere Menge anzuzeigen.
timeout- Es zeigt auf eine Zeitstruktur, die angibt, wie lange der Auswahlaufruf die Deskriptoren für eine verfügbare E / A-Operation abfragen soll. Wenn der Timeout-Wert 0 ist, wird select sofort zurückgegeben. Wenn das Timeout-Argument NULL ist, wird select blockiert, bis mindestens ein Datei- / Socket-Handle für eine verfügbare E / A-Operation bereit ist. Andernfalls wird select nach Ablauf der Zeitspanne im Timeout zurückgegeben ODER, wenn mindestens ein Datei- / Socket-Deskriptor für eine E / A-Operation bereit ist.
Der Rückgabewert von select ist die Anzahl der Handles, die in den Dateideskriptorsätzen angegeben sind, die für die E / A bereit sind. Wenn das im Timeout-Feld angegebene Zeitlimit erreicht ist, wählen Sie return 0 aus. Die folgenden Makros zum Bearbeiten eines Dateideskriptorsatzes sind vorhanden:
FD_CLR(fd, &fdset)- Löscht das Bit für den Dateideskriptor fd im Dateideskriptorsatz fdset.
FD_ISSET(fd, &fdset)- Gibt einen Wert ungleich Null zurück, wenn das Bit für den Dateideskriptor fd in dem Dateideskriptorsatz gesetzt ist, auf den fdset zeigt , andernfalls 0.
FD_SET(fd, &fdset) - Setzt das Bit für den Dateideskriptor fd im Dateideskriptorsatz fdset.
FD_ZERO(&fdset) - Initialisiert den Dateideskriptorsatz fdset so, dass er für alle Dateideskriptoren null Bit enthält.
Das Verhalten dieser Makros ist undefiniert, wenn das Argument fd kleiner als 0 oder größer oder gleich FD_SETSIZE ist.
Beispiel
fd_set fds;
struct timeval tv;
/* do socket initialization etc.
tv.tv_sec = 1;
tv.tv_usec = 500000;
/* tv now represents 1.5 seconds */
FD_ZERO(&fds);
/* adds sock to the file descriptor set */
FD_SET(sock, &fds);
/* wait 1.5 seconds for any data to be read from any single socket */
select(sock+1, &fds, NULL, NULL, &tv);
if (FD_ISSET(sock, &fds)) {
recvfrom(s, buffer, buffer_len, 0, &sa, &sa_len);
/* do something */
}
else {
/* do something else */
}
In diesem Kapitel werden alle Hilfsfunktionen beschrieben, die bei der Socket-Programmierung verwendet werden. Weitere Hilfsfunktionen sind in den Kapiteln beschrieben -Ports and Servicesund Netzwerk Byte Orders.
Die Schreibfunktion
Die Schreibfunktion versucht, n Byte-Bytes aus dem Puffer zu schreiben, auf den buf in die Datei zeigt , die dem geöffneten Dateideskriptor fildes zugeordnet ist .
Sie können auch die Funktion send () verwenden, um Daten an einen anderen Prozess zu senden.
#include <unistd.h>
int write(int fildes, const void *buf, int nbyte);
Nach erfolgreichem Abschluss gibt write () die Anzahl der tatsächlich in die mit fildes verknüpften Dateien geschriebenen Bytes zurück. Diese Zahl ist niemals größer als nbyte. Andernfalls wird -1 zurückgegeben.
Parameter
fildes - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
buf - Es ist ein Zeiger auf die Daten, die Sie senden möchten.
nbyte- Dies ist die Anzahl der zu schreibenden Bytes. Wenn nbyte 0 ist, gibt write () 0 zurück und hat keine anderen Ergebnisse, wenn die Datei eine reguläre Datei ist. Andernfalls sind die Ergebnisse nicht spezifiziert.
Die Lesefunktion
Die Lesefunktion zu lesen versucht nbyte Bytes aus der Datei mit dem Puffer verbunden ist , fildes, in den Puffer buf von spitz.
Sie können auch die Funktion recv () verwenden, um Daten in einen anderen Prozess zu lesen.
#include <unistd.h>
int read(int fildes, const void *buf, int nbyte);
Nach erfolgreichem Abschluss gibt write () die Anzahl der tatsächlich in die mit fildes verknüpften Dateien geschriebenen Bytes zurück. Diese Zahl ist niemals größer als nbyte. Andernfalls wird -1 zurückgegeben.
Parameter
fildes - Es ist ein Socket-Deskriptor, der von der Socket-Funktion zurückgegeben wird.
buf - Es ist der Puffer, in den die Informationen eingelesen werden.
nbyte - Dies ist die Anzahl der zu lesenden Bytes.
Die Gabel - Funktion
Die Gabelfunktion erstellt einen neuen Prozess. Der neue Prozess, der als untergeordneter Prozess bezeichnet wird, ist eine exakte Kopie des aufrufenden Prozesses (übergeordneter Prozess). Der untergeordnete Prozess erbt viele Attribute vom übergeordneten Prozess.
#include <sys/types.h>
#include <unistd.h>
int fork(void);
Nach erfolgreichem Abschluss gibt fork () 0 an den untergeordneten Prozess und die Prozess-ID des untergeordneten Prozesses an den übergeordneten Prozess zurück. Andernfalls wird -1 an den übergeordneten Prozess zurückgegeben, es wird kein untergeordneter Prozess erstellt und errno wird festgelegt, um den Fehler anzuzeigen.
Parameter
void - Es bedeutet, dass kein Parameter erforderlich ist.
Die Bzero- Funktion
Die Funktion bzero platziert nbyte null Bytes in der Zeichenfolge s . Mit dieser Funktion werden alle Socket-Strukturen auf Null gesetzt.
void bzero(void *s, int nbyte);
Diese Funktion gibt nichts zurück.
Parameter
s- Es gibt die Zeichenfolge an, die mit Null-Bytes gefüllt werden muss. Dies ist eine Punkt-zu-Socket-Strukturvariable.
nbyte- Gibt die Anzahl der Bytes an, die mit Nullwerten gefüllt werden sollen. Dies ist die Größe der Socket-Struktur.
Die bcmp- Funktion
Die Funktion bcmp vergleicht die Bytezeichenfolge s1 mit der Bytezeichenfolge s2. Es wird angenommen, dass beide Zeichenfolgen n Byte lang sind.
int bcmp(const void *s1, const void *s2, int nbyte);
Diese Funktion gibt 0 zurück, wenn beide Zeichenfolgen identisch sind, andernfalls 1. Die Funktion bcmp () gibt immer 0 zurück, wenn nbyte 0 ist.
Parameter
s1 - Gibt die erste zu vergleichende Zeichenfolge an.
s2 - Gibt die zweite zu vergleichende Zeichenfolge an.
nbyte - Gibt die Anzahl der zu vergleichenden Bytes an.
Die bcopy- Funktion
Die bcopy- Funktion kopiert nbyte Bytes von der Zeichenfolge s1 in die Zeichenfolge s2. Überlappende Zeichenfolgen werden korrekt behandelt.
void bcopy(const void *s1, void *s2, int nbyte);
Diese Funktion gibt nichts zurück.
Parameter
s1 - Es gibt die Quellzeichenfolge an.
s2v - Es gibt die Zielzeichenfolge an.
nbyte - Gibt die Anzahl der zu kopierenden Bytes an.
Die Memset- Funktion
Die Memset- Funktion wird auch verwendet, um Strukturvariablen auf die gleiche Weise wie zu setzenbzero. Schauen Sie sich die unten angegebene Syntax an.
void *memset(void *s, int c, int nbyte);
Diese Funktion gibt einen Zeiger auf void zurück. in der Tat ein Zeiger auf den eingestellten Speicher und Sie müssen ihn entsprechend kasten.
Parameter
s - Es gibt die einzustellende Quelle an.
c - Es gibt das Zeichen an, das an nbyte Stellen gesetzt werden soll.
nbyte - Gibt die Anzahl der einzustellenden Bytes an.
Um einen Prozess zu einem TCP-Server zu machen, müssen Sie die folgenden Schritte ausführen:
Erstellen Sie einen Socket mit dem Systemaufruf socket () .
Binden Sie den Socket mithilfe des Systemaufrufs bind () an eine Adresse . Bei einem Server-Socket im Internet besteht eine Adresse aus einer Portnummer auf dem Host-Computer.
Achten Sie auf Verbindungen mit dem Systemaufruf listen () .
Akzeptieren Sie eine Verbindung mit dem Systemaufruf accept () . Dieser Aufruf wird normalerweise blockiert, bis ein Client eine Verbindung zum Server herstellt.
Senden und Empfangen von Daten mithilfe der Systemaufrufe read () und write () .
Lassen Sie uns diese Schritte nun in Form von Quellcode ausführen. Fügen Sie diesen Code in die Datei server.c ein und kompilieren Sie ihn mit dem gcc- Compiler.
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main( int argc, char *argv[] ) {
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n;
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 5001;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here process will
* go in sleep mode and will wait for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
/* Accept actual connection from the client */
newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
/* If connection is established then start communicating */
bzero(buffer,256);
n = read( newsockfd,buffer,255 );
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
printf("Here is the message: %s\n",buffer);
/* Write a response to the client */
n = write(newsockfd,"I got your message",18);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
return 0;
}
Mehrere Verbindungen verarbeiten
Damit der Server mehrere Verbindungen gleichzeitig verarbeiten kann, nehmen wir im obigen Code die folgenden Änderungen vor:
Setzen Sie die accept- Anweisung und den folgenden Code in eine Endlosschleife.
Rufen Sie nach dem Herstellen einer Verbindung fork () auf, um einen neuen Prozess zu erstellen.
Der untergeordnete Prozess schließt sockfd und ruft die Doprocessing- Funktion auf, wobei der neue Socket-Dateideskriptor als Argument übergeben wird. Wenn die beiden Prozesse ihre Konversation abgeschlossen haben, wie durch die Rückgabe von doprocessing () angezeigt , wird dieser Prozess einfach beendet.
Der übergeordnete Prozess schließt newsockfd . Da sich der gesamte Code in einer Endlosschleife befindet, kehrt er zur accept-Anweisung zurück, um auf die nächste Verbindung zu warten.
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
void doprocessing (int sock);
int main( int argc, char *argv[] ) {
int sockfd, newsockfd, portno, clilen;
char buffer[256];
struct sockaddr_in serv_addr, cli_addr;
int n, pid;
/* First call to socket() function */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
/* Initialize socket structure */
bzero((char *) &serv_addr, sizeof(serv_addr));
portno = 5001;
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = INADDR_ANY;
serv_addr.sin_port = htons(portno);
/* Now bind the host address using bind() call.*/
if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR on binding");
exit(1);
}
/* Now start listening for the clients, here
* process will go in sleep mode and will wait
* for the incoming connection
*/
listen(sockfd,5);
clilen = sizeof(cli_addr);
while (1) {
newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
if (newsockfd < 0) {
perror("ERROR on accept");
exit(1);
}
/* Create child process */
pid = fork();
if (pid < 0) {
perror("ERROR on fork");
exit(1);
}
if (pid == 0) {
/* This is the client process */
close(sockfd);
doprocessing(newsockfd);
exit(0);
}
else {
close(newsockfd);
}
} /* end of while */
}
Die folgende Codesequenz zeigt eine einfache Implementierung der Doprocessing- Funktion.
void doprocessing (int sock) {
int n;
char buffer[256];
bzero(buffer,256);
n = read(sock,buffer,255);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
printf("Here is the message: %s\n",buffer);
n = write(sock,"I got your message",18);
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
}
Um einen Prozess zu einem TCP-Client zu machen, müssen Sie die folgenden Schritte ausführen: & minus;
Erstellen Sie einen Socket mit dem Systemaufruf socket () .
Verbinden Sie den Socket mit dem Systemaufruf connect () mit der Adresse des Servers .
Daten senden und empfangen. Es gibt verschiedene Möglichkeiten, dies zu tun. Am einfachsten ist es jedoch, die Systemaufrufe read () und write () zu verwenden.
Lassen Sie uns diese Schritte nun in Form von Quellcode ausführen. Fügen Sie diesen Code in die Datei einclient.c und kompiliere es mit gcc Compiler.
Führen Sie dieses Programm aus und übergeben Sie den Hostnamen und die Portnummer des Servers, um eine Verbindung zum Server herzustellen, den Sie bereits in einem anderen Unix-Fenster ausgeführt haben müssen.
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <netinet/in.h>
#include <string.h>
int main(int argc, char *argv[]) {
int sockfd, portno, n;
struct sockaddr_in serv_addr;
struct hostent *server;
char buffer[256];
if (argc < 3) {
fprintf(stderr,"usage %s hostname port\n", argv[0]);
exit(0);
}
portno = atoi(argv[2]);
/* Create a socket point */
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
perror("ERROR opening socket");
exit(1);
}
server = gethostbyname(argv[1]);
if (server == NULL) {
fprintf(stderr,"ERROR, no such host\n");
exit(0);
}
bzero((char *) &serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
bcopy((char *)server->h_addr, (char *)&serv_addr.sin_addr.s_addr, server->h_length);
serv_addr.sin_port = htons(portno);
/* Now connect to the server */
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
perror("ERROR connecting");
exit(1);
}
/* Now ask for a message from the user, this message
* will be read by server
*/
printf("Please enter the message: ");
bzero(buffer,256);
fgets(buffer,255,stdin);
/* Send message to the server */
n = write(sockfd, buffer, strlen(buffer));
if (n < 0) {
perror("ERROR writing to socket");
exit(1);
}
/* Now read server response */
bzero(buffer,256);
n = read(sockfd, buffer, 255);
if (n < 0) {
perror("ERROR reading from socket");
exit(1);
}
printf("%s\n",buffer);
return 0;
}
Hier finden Sie eine Liste aller Funktionen zur Socket-Programmierung.
Port- und Servicefunktionen
Unix bietet die folgenden Funktionen zum Abrufen des Dienstnamens aus der Datei / etc / services.
struct servent *getservbyname(char *name, char *proto) - Dieser Aufruf verwendet einen Dienstnamen und einen Protokollnamen und gibt die entsprechende Portnummer für diesen Dienst zurück.
struct servent *getservbyport(int port, char *proto) - Dieser Aufruf verwendet eine Portnummer und einen Protokollnamen und gibt den entsprechenden Dienstnamen zurück.
Byte-Bestellfunktionen
unsigned short htons (unsigned short hostshort) - Diese Funktion konvertiert 16-Bit-Mengen (2 Byte) von der Host-Byte-Reihenfolge in die Netzwerk-Byte-Reihenfolge.
unsigned long htonl (unsigned long hostlong) - Diese Funktion konvertiert 32-Bit-Mengen (4 Byte) von der Host-Byte-Reihenfolge in die Netzwerk-Byte-Reihenfolge.
unsigned short ntohs (unsigned short netshort) - Diese Funktion konvertiert 16-Bit-Mengen (2 Byte) von der Netzwerkbyte-Reihenfolge in die Host-Byte-Reihenfolge.
unsigned long ntohl (unsigned long netlong) - Diese Funktion konvertiert 32-Bit-Mengen von der Netzwerkbyte-Reihenfolge in die Host-Byte-Reihenfolge.
IP-Adressfunktionen
int inet_aton (const char *strptr, struct in_addr *addrptr)- Dieser Funktionsaufruf konvertiert die angegebene Zeichenfolge in der Internet-Standardpunktnotation in eine Netzwerkadresse und speichert die Adresse in der bereitgestellten Struktur. Die konvertierte Adresse befindet sich in der Reihenfolge der Netzwerkbytes (Bytes von links nach rechts). Es gibt 1 zurück, wenn die Zeichenfolge gültig ist, und 0 bei einem Fehler.
in_addr_t inet_addr (const char *strptr)- Dieser Funktionsaufruf konvertiert die angegebene Zeichenfolge in der Internet-Standardpunktnotation in einen ganzzahligen Wert, der zur Verwendung als Internetadresse geeignet ist. Die konvertierte Adresse befindet sich in der Reihenfolge der Netzwerkbytes (Bytes von links nach rechts). Es gibt eine 32-Bit-IPv4-Adresse mit binärem Netzwerkbyte und INADDR_NONE bei einem Fehler zurück.
char *inet_ntoa (struct in_addr inaddr) - Dieser Funktionsaufruf konvertiert die angegebene Internet-Host-Adresse in eine Zeichenfolge in der Internet-Standard-Punktnotation.
Socket Core-Funktionen
int socket (int family, int type, int protocol) - Dieser Aufruf gibt einen Socket-Deskriptor zurück, den Sie in späteren Systemaufrufen verwenden können, oder gibt bei einem Fehler -1 aus.
int connect (int sockfd, struct sockaddr *serv_addr, int addrlen)- Die Verbindungsfunktion wird von einem TCP-Client verwendet, um eine Verbindung mit einem TCP-Server herzustellen. Dieser Aufruf gibt 0 zurück, wenn eine erfolgreiche Verbindung zum Server hergestellt wurde, andernfalls wird -1 zurückgegeben.
int bind(int sockfd, struct sockaddr *my_addr,int addrlen)- Die Bindefunktion weist einem Socket eine lokale Protokolladresse zu. Dieser Aufruf gibt 0 zurück, wenn er erfolgreich an die Adresse gebunden wurde, andernfalls -1.
int listen(int sockfd, int backlog)- Die Listen-Funktion wird nur von einem TCP-Server aufgerufen, um auf die Client-Anfrage zu warten. Dieser Aufruf gibt bei Erfolg 0 zurück, andernfalls -1.
int accept (int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen)- Die Accept-Funktion wird von einem TCP-Server aufgerufen, um Client-Anfragen zu akzeptieren und eine tatsächliche Verbindung herzustellen. Dieser Aufruf gibt bei Erfolg einen nicht negativen Deskriptor zurück, andernfalls wird -1 zurückgegeben.
int send(int sockfd, const void *msg, int len, int flags)- Mit der Sendefunktion werden Daten über Stream-Sockets oder CONNECTED-Datagramm-Sockets gesendet. Dieser Aufruf gibt die Anzahl der gesendeten Bytes zurück, andernfalls -1.
int recv (int sockfd, void *buf, int len, unsigned int flags)- Die Recv-Funktion wird verwendet, um Daten über Stream-Sockets oder CONNECTED-Datagramm-Sockets zu empfangen. Dieser Aufruf gibt die Anzahl der in den Puffer gelesenen Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
int sendto (int sockfd, const void *msg, int len, unsigned int flags, const struct sockaddr *to, int tolen)- Mit der Sendto-Funktion werden Daten über UNCONNECTED-Datagramm-Sockets gesendet. Dieser Aufruf gibt die Anzahl der gesendeten Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
int recvfrom (int sockfd, void *buf, int len, unsigned int flags struct sockaddr *from, int *fromlen)- Die Funktion recvfrom wird zum Empfangen von Daten von UNCONNECTED-Datagramm-Sockets verwendet. Dieser Aufruf gibt die Anzahl der in den Puffer gelesenen Bytes zurück, andernfalls wird bei einem Fehler -1 zurückgegeben.
int close (int sockfd)- Mit der Schließfunktion wird eine Kommunikation zwischen Client und Server geschlossen. Dieser Aufruf gibt bei Erfolg 0 zurück, andernfalls -1.
int shutdown (int sockfd, int how)- Mit der Funktion zum Herunterfahren wird eine Kommunikation zwischen Client und Server ordnungsgemäß geschlossen. Diese Funktion bietet mehr Kontrolle im Vergleich zur Schließfunktion. Bei Erfolg wird 0 zurückgegeben, andernfalls -1.
int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout) - Mit dieser Funktion können Sie mehrere Sockets lesen oder schreiben.
Socket Helper-Funktionen
int write (int fildes, const void *buf, int nbyte)- Die Schreibfunktion versucht, nbyte Bytes aus dem Puffer, auf den buf zeigt, in die Datei zu schreiben, die dem offenen Dateideskriptor fildes zugeordnet ist. Nach erfolgreichem Abschluss gibt write () die Anzahl der tatsächlich in die mit fildes verknüpften Dateien geschriebenen Bytes zurück. Diese Zahl ist niemals größer als nbyte. Andernfalls wird -1 zurückgegeben.
int read (int fildes, const void *buf, int nbyte)- Die Lesefunktion versucht, n Byte-Bytes aus der Datei, die dem offenen Dateideskriptor fildes zugeordnet ist, in den Puffer zu lesen, auf den buf zeigt. Nach erfolgreichem Abschluss gibt write () die Anzahl der tatsächlich in die mit fildes verknüpften Dateien geschriebenen Bytes zurück. Diese Zahl ist niemals größer als nbyte. Andernfalls wird -1 zurückgegeben.
int fork (void)- Die Gabelfunktion erstellt einen neuen Prozess. Der neue Prozess, der als untergeordneter Prozess bezeichnet wird, ist eine exakte Kopie des aufrufenden Prozesses (übergeordneter Prozess).
void bzero (void *s, int nbyte)- Die Funktion bzero platziert nbyte null Bytes in der Zeichenfolge s. Diese Funktion wird verwendet, um alle Socket-Strukturen mit Nullwerten festzulegen.
int bcmp (const void *s1, const void *s2, int nbyte)- Die Funktion bcmp vergleicht die Byte-Zeichenfolge s1 mit der Byte-Zeichenfolge s2. Es wird angenommen, dass beide Zeichenfolgen n Byte lang sind.
void bcopy (const void *s1, void *s2, int nbyte)- Die bcopy-Funktion kopiert nbyte Bytes von der Zeichenfolge s1 in die Zeichenfolge s2. Überlappende Zeichenfolgen werden korrekt behandelt.
void *memset(void *s, int c, int nbyte) - Die Memset-Funktion wird auch verwendet, um Strukturvariablen auf die gleiche Weise wie bzero festzulegen.