[c] sockets met select() en fork-en

Pagina: 1
Acties:

  • MadMan81
  • Registratie: April 2000
  • Laatst online: 18-04 15:42
Ik ben een server applicatie aan het bouwen die sockets afhandelt. In eerste instantie had ik een “klassieke” opzet met accept() en dan een fork. De bleek echter niet helemaal te werken. Daarom maak ik nu gebruik van select(). Als basis hiervoor heb ik gebruik gemaakt van het chatserver voorbeeld van beej:

http://www.ecst.csuchico....de/net/html/advanced.html

Het nadeel van deze aanpak is echter dat hij maar met maximaal 1 socket tegelijk bezig kan zijn. De server applicatie die ik ga bouwen kan echter vrij lang bezig zijn met de inkomende data, daarom wil ik hem ook laten fork-en nadat hij de data heeft gelezen van de socket.

Bij de opzet met accept worden na het forken gelijk een aantal sockets afgesloten: de parent sluit de nieuwe socket, de child de listener socket. Ook heb je nog een sigint_handler die beide sockets afsluit. Maar hoe zit dat nu bij select() ? Welke sockets moet ik daar afsluiten in de parent (volgens mij geen een)? En welke moet ik afsluiten in de child (volgens mij allemaal, de child exit() namelijk als hij klaar is met het verwerk van de data)?

Nu is mijn vraag: Moet ik idd sockets gaan afsluiten? Zo ja welke en wanneer?
En: Als ik sockets moet afsluiten, waar haal ik de fd’s vandaan? Ze staan in fd_set, maar ik ken maar 4 functies daar voor (FD_ZERO, FD_SET, FD_ISSET en FD_CLR).

Ik heb zowel hier als op google al gezocht, maar kon niets vinden vinden over fork-en in select() implementaties.

Cupra Born


  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Misschien overbodig, maar is het niet juist de bedoeling met een select aanpak op zoveel mogelijk single-threaded te werken? Waar gebruik je de fork dan nog voor?

[ Voor 14% gewijzigd door Infinitive op 10-01-2005 12:28 ]

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


  • MadMan81
  • Registratie: April 2000
  • Laatst online: 18-04 15:42
Omdat ik wil voorkomen dat andere verbindingen lang moeten wachten.. Normaal zou je dat idd doet met accept en dan een fork. Maar ik heb verbindingen die de socket openhouden en verbindingen die de socket gelijk afsluiten. Appart werkte het prima, maar samen blijf hij hangen op de accpet van die nieuwe verbindingen... Heel wazig, maar deze aanpak werkt iig wel goed met beide

Cupra Born


Verwijderd

even ervan uitgaande dat je dit op een GNU/Linux (of vergelijkbaar) OS doet: heb je al eens "info socket" gedaan? Hier staan ook een aantal goede voorbeelden in. Verder kun je overwegen om hiervoor 'inetd' te gebruiken:
Writing a server program to be run by `inetd' is very simple. Each time
someone requests a connection to the appropriate port, a new server
process starts. The connection already exists at this time; the socket
is available as the standard input descriptor and as the standard
output descriptor (descriptors 0 and 1) in the server process. Thus
the server program can begin reading and writing data right away.
Often the program needs only the ordinary I/O facilities; in fact, a
general-purpose filter program that knows nothing about sockets can
work as a byte stream server run by `inetd'.
(uit de info pages)

Verwijderd

Omdat ik het wel leuk vond om hier weer eens naar te kijken, heb ik ook eventjes de info pages erbij gepakt. Zie het volgende voorbeeld. Dit werkt prima met meerdere connecties.

C:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>

#define PORT    5555
#define MAXMSG  512

int
make_socket (uint16_t port)
{
  int sock;
  struct sockaddr_in name;

  /* Create the socket. */
  sock = socket (PF_INET, SOCK_STREAM, 0);
  if (sock < 0)
    {
      perror ("socket");
      exit (EXIT_FAILURE);
    }

  /* Give the socket a name. */
  name.sin_family = AF_INET;
  name.sin_port = htons (port);
  name.sin_addr.s_addr = htonl (INADDR_ANY);
  if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0)
    {
      perror ("bind");
      exit (EXIT_FAILURE);
    }

  return sock;
}

int
read_from_client (int filedes)
{
  char buffer[MAXMSG];
  int nbytes;

  nbytes = read (filedes, buffer, MAXMSG);
  if (nbytes < 0)
    {
      /* Read error. */
      perror ("read");
      exit (EXIT_FAILURE);
    }
  else if (nbytes == 0)
    /* End-of-file. */
    return -1;
  else
    {
      /* Data read. */
      fprintf (stderr, "Server: got message: `%s'\n", buffer);
      return 0;
    }
}

int
main (void)
{
  extern int make_socket (uint16_t port);
  int sock;
  fd_set active_fd_set, read_fd_set;
  int i;
  struct sockaddr_in clientname;
  size_t size;

  /* Create the socket and set it up to accept connections. */
  sock = make_socket (PORT);
  if (listen (sock, 1) < 0)
    {
      perror ("listen");
      exit (EXIT_FAILURE);
    }

  /* Initialize the set of active sockets. */
  FD_ZERO (&active_fd_set);
  FD_SET (sock, &active_fd_set);

  while (1)
    {
      /* Block until input arrives on one or more active sockets. */
      read_fd_set = active_fd_set;
      if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0)
        {
          perror ("select");
          exit (EXIT_FAILURE);
        }

      /* Service all the sockets with input pending. */
      for (i = 0; i < FD_SETSIZE; ++i)
        if (FD_ISSET (i, &read_fd_set))
          {
            if (i == sock)
              {
                /* Connection request on original socket. */
                int new;
                size = sizeof (clientname);
                new = accept (sock,
                              (struct sockaddr *) &clientname,
                              &size);
                if (new < 0)
                  {
                    perror ("accept");
                    exit (EXIT_FAILURE);
                  }
                fprintf (stderr,
                         "Server: connect from host %s, port %hd.\n",
                         inet_ntoa (clientname.sin_addr),
                         ntohs (clientname.sin_port));
                FD_SET (new, &active_fd_set);
              }
            else
              {
                /* Data arriving on an already-connected socket. */
                if (read_from_client (i) < 0)
                  {
                    close (i);
                    FD_CLR (i, &active_fd_set);
                  }
              }
          }
    }
}

  • MadMan81
  • Registratie: April 2000
  • Laatst online: 18-04 15:42
Zo iets heb ik nu ook, zie ook het voorbeeld in de first post..

Alleen wat ik graag wil is in de afhandeling van de data die binnen gekomen is een fork.. En de vraag is moet ik in de child alle openstaande verbindingen afsluiten, zo ja hoe haal ik de fd's uit de fd_set

De huidige methode, die dus ongeveer gelijk is aan wat jij heb gepost, werkt maar 1 verbinding tegelijk af, hierdoor moeten alle andere verbindingen wachten. Dat is voor mijn geen oplossing omdat er dan time-out's onstaan: de afhandeling kan namelijk best lang duren...

Cupra Born


  • Creepy
  • Registratie: Juni 2001
  • Laatst online: 08:31

Creepy

Tactical Espionage Splatterer

Met een fork() krijg je alle openstaande fd's van het forkende process mee. Dus lijkt het me het beste dat je alle niet gebruikte sockets afsluit.
Als je de close-on-exec vlag van een FD zet in het hoofdproces dan worden deze FD's automatisch gesloten in het child process.

[ Voor 3% gewijzigd door Creepy op 10-01-2005 15:39 ]

"I had a problem, I solved it with regular expressions. Now I have two problems". That's shows a lack of appreciation for regular expressions: "I know have _star_ problems" --Kevlin Henney


  • MadMan81
  • Registratie: April 2000
  • Laatst online: 18-04 15:42
Das opzich wel een goed idee ja, had ik zelf nog niet aan gedacht..

edit:
Hmm.. hoewel met op internet zegt dat je dat met coe() zou kunnen, kan ik die functie op mijn systeem (fedora core 3) niet vinden..

[ Voor 62% gewijzigd door MadMan81 op 10-01-2005 16:23 ]

Cupra Born


  • Infinitive
  • Registratie: Maart 2001
  • Laatst online: 25-09-2023
Het is mij nog niet duidelijk welke rol de select in je verhaal speelt. Volgens mij zet je je sockets op non-blocking en gebruik je de select om te detecteren dat er een nieuwe connectie in de queue zit en deze te accepteren. Daarna fork je en sluit je in het child proces af wat je niet nodig hebt.

Aan de select heb je dan eigenlijk niet zoveel. Je kan net zogoed je sockets op blocking laten en vervolgens blijven accepten. Accept zal eindigen zodra er een connectie binnenkomt.

Tenzij je select gebruikt om wat meer controle op je sockets uit te oefenen... je zou bijvoorbeeld "requests" die kort duren op dezelfde thread kunnen uitvoeren (alle socket en file operaties via non-blocking IO) en een pool van threads gebruiken voor langdurige operaties.

putStr $ map (x -> chr $ round $ 21/2 * x^3 - 92 * x^2 + 503/2 * x - 105) [1..4]


  • Onno
  • Registratie: Juni 1999
  • Niet online
Een thread/procespool moet je zeker gebruiken ja, voor elke request forken is niet bepaald efficient.
Creepy schreef op maandag 10 januari 2005 @ 15:38:
Als je de close-on-exec vlag van een FD zet in het hoofdproces dan worden deze FD's automatisch gesloten in het child process.
Als je execve gebruikt ja. Niet bij alleen een fork().

[ Voor 16% gewijzigd door Onno op 10-01-2005 21:12 ]

Pagina: 1