Forum: PC-Programmierung Ausgabe nach stdout geht nicht, aber ausgabe nach STDOUT_FILENO funktionniert
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 | }
|
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.
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.
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.
|