Forum: PC-Programmierung Ausgabe nach stdout geht nicht, aber ausgabe nach STDOUT_FILENO funktionniert


von Daniel A. (daniel-a)


Lesenswert?

Hallo

Ich stehe bei einem meiner Nebenprojekte vor einem kleinen Problem. In 
meinem Programm erstelle ich zunächst einige worker Prozesse, mit denen 
ich per anonymem unix domain socket kommuniziere. Wenn diese Worker 
Prozesse starten, schliesse ich bei diesen STDIN_FILENO, STDOUT_FILENO, 
und STDERR_FILENO. In einem anderen Prozess warte ich auf eingehende TCP 
Verbindungen auf port 8080. Wenn ich eine neue Verbindung habe, warte 
ich mit poll auf Daten. Sobald Daten ankommen, sende ich den Socket File 
Descriptor der Verbindung über den UNIX Domain Socket zu einem Worker 
Prozess. Dort öffne ich mit dup2 STDIN_FILENO und STDOUT_FILENO als 
alias des Socket file descriptors, und schliesse diesen danach, sofern 
dieser nicht bereits STDIN_FILENO oder STDOUT_FILENO war. Danach rufe 
ich die tcp_onrecive funktion auf, welche mit fgetc zeichen einliest, 
und danach auch gleich wieder ausgibt.

Das ist, wo mein Problem liegt. Das einlesen mittels stdin z.B. über 
fgetc funktioniert, und ich kann die Ausgabe von fputc nach stderr bei 
der Console mit in dem der Server läuft sehen. Auch ausgaben nach 
STDOUT_FILENO mittels write funktionieren, und werden über den Socket 
gesendet. Aber ausgaben nach stdout funktionieren nicht, egal ob ich 
putchar, fputc, oder sonstige Funktionen nutze. Ich habe nie fclose auf 
stdout aufgerufen. Wie kann es sein, dass ausgaben nach stdout nicht 
nach STDOUT_FILENO geschrieben werden, und kann ich das irgendwie 
korrigieren?

https://github.com/Daniel-Abrecht/server/blob/master/src/stream/tcpserver.c#L114
1
void tcp_onrecive( void ){
2
  int c, x;
3
  while( (c=fgetc(stdin)) != EOF ){
4
    putchar( c ); // geht nicht
5
    write( STDOUT_FILENO, (char[]){c}, 1 ); // funktionniert
6
    fputc( c, stderr );
7
  }
8
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Du vermischt Dateihandles und Dateinummern.

Verwende fwrite anstelle von write, dann kannst Du auch mit 
Dateihandles arbeiten.

stdin, stdout etc. sind Dateihandles vom Typ FILE*.

write aber verwendet Dateinummern, mit fileno kann man sich aus einem 
Handle so eine Nummer erzeugen lassen.

von Daniel A. (daniel-a)


Lesenswert?

Ich kenne den Unterschied zwischen Datei streams und Datei descriptoren. 
Ich will erreichen, dass ich sowohl über stdout, als auch über 
STDOUT_FILENO ausgaben machen kann, welche dann über den Socket gesendet 
werden. Die Idee ist, dass funktionen wie printf(...) und 
fwrite(,,,stdout) intern auf den write system call zurückgreifen müssen, 
und stdout Anfangs ein handle auf STDOUT_FILENO (oder 1) ist, solange 
ich stdout nicht mit fclose oder freopen schliesse. Wenn ich also 
STDOUT_FILENO mit close schliesse, und später mit dup2 eine Kopie des 
Socket file descriptors mit der Nummer von STDOUT_FILENO mache, ist es 
als ob ich STDOUT_FILENO durch den socket file descriptor ersetzt hätte. 
Die libc kann davon eigentlich nichts mitbekommen, so das Ausgaben mit 
printf(...) und fwrite(,,,stdout) weiterhin in einem Syscall von write 
zum filedescriptor STDOUT_FILENO enden sollte, welcher dann aber der 
Socket file descriptor ist.

Ich habe einige Debug ausgaben eingebaut, und dass write durch ein 
fwrite ersetzt, und die Anzahl worker prozesse auf einen reduziert:
1
void tcp_onrecive( void ){
2
  int c;
3
  fprintf( stderr, "a %d %d\n", fileno(stdout), STDOUT_FILENO );
4
  while( (c=fgetc(stdin)) != EOF ){
5
    int d = fwrite( (char[]){c}, 1, 1, stdout );
6
    fprintf( stderr, "b %d %d\n", d, ferror(stdout) );
7
    fputc( c, stderr );
8
  }
9
}

Ausgabe Server:
1
abd@basalt ~/projects/server $ ./bin/tcpserver 
2
a 1 1
3
b 1 0
4
1b 1 0
5
2b 1 0
6
3b 1 0
7
b 1 0
8
9
Worker 1 done

Man sieht hier, dass fileno von stdout effektiv STDOUT_FILENO ist, 
fwrite angibt, 1 byte geschrieben zu haben, und ferror angibt, dass es 
keine Fehler gab. Ausserdem wurden die Eingaben von Telnet auf stderr 
ausgegeben, soweit also alles wie erwartet.

Telnet ein/ausgabe:
1
abd@basalt / $ telnet 127.0.0.1 8081
2
Trying 127.0.0.1...
3
Connected to 127.0.0.1.
4
Escape character is '^]'.
5
123
6
^]
7
8
telnet> Connection closed.

Hier sieht man, dass durch das fwrite nichts zurückgesendet wurde. 
Verwende ich statdessen write, bekomme ich die Daten zurück gesendet.

Hier noch der strace des Worker prozesses:
1
set_robust_list(0x7f3c74e879e0, 24)     = 0
2
close(0)                                = 0
3
close(1)                                = 0
4
rt_sigaction(SIGUSR2, {0x401d80, [], SA_RESTORER|SA_SIGINFO, 0x7f3c74a8ce20}, NULL, 8) = 0
5
close(6)                                = 0
6
pause()                                 = ? ERESTARTNOHAND (To be restarted if no handler)
7
--- SIGUSR2 {si_signo=SIGUSR2, si_code=SI_QUEUE, si_pid=27500, si_uid=1016, si_value={int=1, ptr=0x1}} ---
8
recvmsg(4, {msg_name(0)=NULL, msg_iov(1)=[{"\1\0\0\0\0\0\0\0\0B`\0\0\0\0\0\2\0\0\0\1\0\0\0", 24}], msg_controllen=24, {cmsg_len=20, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, {0}}, msg_flags=0}, 0) = 24
9
dup2(0, 1)                              = 1
10
rt_sigreturn()                          = -1 EINTR (Interrupted system call)
11
write(2, "a 1 1\n", 6)                  = 6
12
fstat(0, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
13
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c74eb6000
14
read(0, "123\r\n", 4096)                = 5
15
fstat(1, {st_mode=S_IFSOCK|0777, st_size=0, ...}) = 0
16
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3c74eb5000
17
write(2, "b 1 0\n", 6)                  = 6
18
write(2, "1", 1)                        = 1
19
write(2, "b 1 0\n", 6)                  = 6
20
write(2, "2", 1)                        = 1
21
write(2, "b 1 0\n", 6)                  = 6
22
write(2, "3", 1)                        = 1
23
write(2, "b 1 0\n", 6)                  = 6
24
write(2, "\r", 1)                       = 1
25
write(2, "b 1 0\n", 6)                  = 6
26
write(2, "\n", 1)                       = 1
27
read(0, "", 4096)                       = 0
28
close(0)                                = 0
29
close(1)                                = 0
30
sendmsg(5, {msg_name(0)=NULL, msg_iov(1)=[{"\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\1\0\0\0\1\0\0\0", 24}], msg_controllen=16, {cmsg_len=16, cmsg_level=SOL_SOCKET, cmsg_type=SCM_RIGHTS, ...}, msg_flags=0}, 0) = 24
31
getuid()                                = 1016
32
rt_sigqueueinfo(27500, SIGUSR2, {si_signo=SIGUSR2, si_code=SI_QUEUE, si_pid=27499, si_uid=1016, si_value={int=1, ptr=0x1}}) = 0
33
pause()                                 = ? ERESTARTNOHAND (To be restarted if no handler)
34
--- SIGINT {si_signo=SIGINT, si_code=SI_KERNEL, si_value={int=1, ptr=0x1}} ---
35
+++ killed by SIGINT +++

Wie man sieht hat fwrite nach stdout kein write syscall nach 
STDOUT_FILENO abgesetzt, spätenstens nach dem '\n' hätte es das tun 
müssen. Ich komme einfach nicht dahinter, wieso funktioniert das 
einlesen mit stdin, aber nicht die Ausgabe mit stdout, wo ich doch mit 
beiden das selbe gemacht habe.

von Daniel A. (daniel-a)


Lesenswert?

OK, ich hab's rausgefunden. stdio wird bei der ersten verwendung 
initialisiert, und wärend der buffer bei Konsolen nach jedem newline 
geflusht wird, passiert es bei sockets erst wenn der buffer voll ist. 
Die Lösung ist entweder ein manuelles fflush oder das ändern des Buffers 
mit setvbuf.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.