2017-08-18 4 views
0

Ich bin neu in der C-Socket-Programmierung. Ich habe gelernt, dass nach dem Senden von Daten über einen Socket, sollte entweder close oder shutdown der Socket-Deskriptor, der ein EOF an das andere Ende gesendet wird ausgelöst. Ohne EOF blockiert read/recv weiterhin.In C, wie EOF nach dem Schreiben von Daten ohne Close/Shutdown eine Steckdose angeben?

Jetzt frage ich mich, ob es überhaupt möglich ist, den Sockel für weitere Datenübertragung offen zu halten. Was ich gelesen habe, scheint das zu sein, was Leute tun, wenn sie eine Keep-Alive-HTTP-Verbindung aufbauen. Aber ich konnte nicht herausfinden, wie das erreicht wird.

Der folgende Code zeigt das Szenario, das nach dem write des Clients festhängt.

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 
#include <unistd.h> 
#include <errno.h> 
#include <sys/types.h> 
#include <sys/socket.h> 
#include <netinet/in.h> 
#include <netdb.h> 

#define thread_printf(...) printf("[PID %d] ", getpid()); printf(__VA_ARGS__); 

int create_socket() 
{ 
    int socket_fd; 
    socket_fd = socket(AF_INET, SOCK_STREAM, 0); 
    if (socket_fd < 0) 
    perror("Error opening socket"); 
    return socket_fd; 
} 

int listen_port(int portno) 
{ 
    int socket_fd; 
    struct sockaddr_in server_addr; 

    socket_fd = create_socket(); 

    memset((void *) &server_addr, 0, sizeof(server_addr)); 
    server_addr.sin_family = AF_INET; 
    server_addr.sin_port = htons(portno); 
    server_addr.sin_addr.s_addr = INADDR_ANY; 

    if (bind(socket_fd, (struct sockaddr *) &server_addr, sizeof(server_addr)) < 0) 
    perror("Error on binding"); 

    listen(socket_fd, 5); 
    return socket_fd; 
} 

int connect_port(const char *host, int portno) 
{ 
    int socket_fd; 
    struct sockaddr_in proxy_addr; 
    struct hostent *proxy; 

    socket_fd = create_socket(); 

    proxy = gethostbyname(host); 
    if (proxy == NULL) 
    perror("Error no such host"); 

    proxy_addr.sin_family = AF_INET; 
    proxy_addr.sin_port = htons(portno); 
    memcpy((void *) &proxy_addr.sin_addr.s_addr, (void *) proxy->h_addr, proxy->h_length); 

    if (connect(socket_fd, (struct sockaddr *) &proxy_addr, sizeof(proxy_addr)) < 0) 
    perror("Error connecting"); 

    return socket_fd; 
} 

int read_socket(int fd, char *buf, int bufsize) 
{ 
    thread_printf("read_socket\n"); 
    int m = 0;     /* total number of bytes received */ 
    int n = 0;     /* number of bytes received in a single read */ 

    while (m < bufsize - 1) 
    { 
    n = read(fd, buf + m, bufsize - m - 1); 
    if (n == -1)    /* socket read error */ 
    { 
     perror("Error reading socket"); 
     break; 
    } 
    if (n == 0)     /* socket is closed */ 
     break; 
    m += n; 
    } 

    if (m >= bufsize) 
    perror("Error buffer overflow"); 

    buf[m] = '\0'; 
    return m; 
} 

int write_socket(int fd, char *buf, int len) 
{ 
    thread_printf("write_socket\n"); 
    int m = 0; 
    int n = 0; 

    while (m < len) 
    { 
    n = write(fd, buf + m, len - m); 
    if (n == -1) { 
     perror("Error socket send"); 
     break; 
    } 
    if (n == 0)     /* socket is closed */ 
     break; 
    m += n; 
    } 
    thread_printf("m = %d, len = %d\n", m, len); 
    return m; 
} 

int main() 
{ 
    int socket_fd = listen_port(3129); 
    if (fork() == 0)    /* client */ 
    { 
    close(socket_fd); 
    int client_socket_fd = connect_port("127.0.0.1", 3129); 
    int n; 
    char *msg = "This is a client request\n"; 
    char buf[1024]; 
    memset((void *) buf, 0, 1024); 
    n = write_socket(client_socket_fd, msg, strlen(msg)); 
    thread_printf("client sent: %s\n", msg); 
    n = read_socket(client_socket_fd, buf, 1024); 
    thread_printf("client received: %s\n", buf); 
    close(client_socket_fd); 
    } 
    else       /* server */ 
    { 
    struct sockaddr_in client_addr; 
    int client_len; 
    client_len = sizeof(client_addr); 
    int client_socket_fd = accept(socket_fd, (struct sockaddr *) &client_addr, &client_len); 
    int n; 
    char *msg = "This is a server response\n"; 
    char buf[1024]; 
    n = read_socket(client_socket_fd, buf, 1024); 
    thread_printf("server received: %s\n", buf); 
    n = write_socket(client_socket_fd, msg, strlen(msg)); 
    thread_printf("server sent: %s\n", msg); 
    close(client_socket_fd); 
    } 

    return 0; 
} 
+5

HTTP Keep-Alive erfordert 'Inhalt -Länge 'Header oder Chunks, damit Sie wissen, wo die nächste Antwort beginnt. – Ryan

+0

Danke für das Aufzeigen @Ryan. Es macht für mich mehr Sinn nach dem Lesen, was 'Content-Length' und' Transfer-Encoding' bedeuten und wie sie verwendet werden. – Yan

+1

OT: Der letzte Parameter, der an 'accept()' übergeben wird, muss die Adresse eines 'socklen_t' sein, nicht eines' int'. Auch 'read()' und 'write()' geben 'ssize_t' not' int' zurück. – alk

Antwort

4

Im Server-Code wartet 1024 Bytes zu lesen, aber in Client Sie nur 26 Bytes als strlen(msg) senden, so wird Ihr Server nie wieder von read_socket

Wie bei jeder Socket-Client-Server-Anwendung, Sie Sie müssen Ihr eigenes Protokoll für Client und Server definieren, um zu entscheiden, wann alle Daten gelesen wurden, entweder durch Festlegen von Paketen mit fester Länge oder durch Einbeziehen der Länge der Daten als Teil der Daten selbst.

z Sie können Ihre Daten gehören, ersten 2 Bytes als Länge der Daten, gefolgt von Ist-Daten

+0

Dank @ Pras. Jetzt merke ich, dass ich missverstanden habe, was das letzte Argument von "lesen" bedeutet. Wenn also nicht ein EOF signalisiert wird, wird 'read' blockiert, bis die angegebene Anzahl von Bytes abgerufen wird. Ist das richtig? – Yan

+0

Es kann für weniger Daten auch zurückkehren, aber Sie machen einen anderen recv Anruf, um mehr Daten zu lesen, und Client sendet keine, das macht es im nächsten recv blockieren – Pras

3

Jetzt entscheide ich frage mich, ob es überhaupt möglich ist, die Buchse offen für die weitere Datenübertragung zu halten.

Ja, absolut - in der Regel können Sie die Steckdose so lange offen halten, wie Sie wollen, und Sie können (und/oder empfangen) wie so viele Daten wie Sie senden.

Der Empfänger wird in den meisten Fällen eine Möglichkeit brauchen, um die eingehenden Daten eindeutig zu parsen - insbesondere muss er wissen, wann eine semantische "Nachricht" (*) endet und die nächste beginnt. Wenn Sie nur eine "Nachricht" senden und dann die TCP-Verbindung schließen, wird dieses Problem vermieden, da der EOF selbst als Indikator für die Beendigung der Nachricht dient. Wenn Sie die Verbindung jedoch für mehr als eine Nachricht offen halten möchten wird einen Weg brauchen, auf dem der Empfänger feststellen kann, wo die Nachrichtengrenze liegt.

Diese Information muss in den Daten selbst codiert werden, da das TCP-Protokoll keine Versprechungen darüber abgibt, wie viele Bytes es während jedes Aufrufs von recv() an den Empfangsprozess liefert; es garantiert nur, dass die Bytes in der gleichen Reihenfolge empfangen werden, in der sie gesendet wurden.

Gängige Methoden für die Nachrichten-Grenzinformation kodiert sind:

  1. die Nachricht mit einer kurzen fester Größe Kopfvoranstellen, wie viele Bytes gibt an, in dem Nachrichtentext
  2. benenne ein speziellen „zu erwarten Ende- of-message "-Zeichen wie ein NUL-Byte oder ein Carriage-Return (beachten Sie, dass Sie in diesem Fall sicherstellen müssen, dass Ihre Nachrichten dieses Zeichen niemals als Teil der Nutzdaten enthalten oder wenn dies der Fall ist irgendwie entkommen, so dass es nicht fälschlicherweise als Nachrichtengrenze interpretiert wird)
  3. SLIP encoding (oder ähnlich)
  4. alle Nachrichten Erfordern eine bekannte, feste Größe zu sein (dies ist nur für bestimmte Anwendungsfälle geeignet ist)

I Methode persönlich bevorzugen (1), wie ich es finden, die am einfachsten zu korrekt und effizient implementieren, aber alle oben genannten können funktionieren.

(*) „Nachricht“ in diesem Zusammenhang als „die Anzahl von Bytes, die der Empfänger in lesen muss, bevor er ihre Bedeutung in einiger nützlichen Art und Weise reagieren kann“ definiert ist

+0

# 4 ist wohl noch einfacher, richtig zu implementieren, aber hat eine große Nachteil: Es ist nicht möglich, die Abwärtskompatibilität bei der Erweiterung des Protokolls aufrechtzuerhalten. – spectras

+0

richtig, ja; effizient, vielleicht nicht, es sei denn, Ihre Nachricht ist immer bei oder in der Nähe Ihrer gewählten Nachrichtengröße (oder etwas kleiner) –

Verwandte Themen