This article is senlie original, reproduced Please retain this address: http://blog.csdn.net/zhengsenlie
Next, I will introduce several different versions of the same tcp client program, including the stop and other versions, the Select and blocking I/O versions,
Non-blocking I/O version, fork version, and thread version. They are all called by the same main function to implement the same function, that is, the echo program client.
It reads a line of text from the standard input, writes it to the server, reads the server's response to the line, and writes the response line to the standard output.
Among them, the non-blocking I/O version is the fastest to execute in all versions, but its code is complicated.
Creating a New thread is usually much faster than using fork to derive a new process,
In the following code, the thread version execution speed is slightly faster than that of fork, and slightly slower than that of non-blocking I/O. Thread version is recommended for general programming.
The following is the performance test results of each version in "UNIX Network Programming: Volume 1:
354.0 seconds, stop or other versions
12.3 seconds, select and block I/O versions
6.9 seconds, non-blocking I/O version
8.7 seconds, fork version
8.5 seconds, threaded version
The code for the main function is as follows:
# Include "unp. H" intmain (INT argc, char ** argv) {intsockfd; struct sockaddr_inservaddr; If (argc! = 2) err_quit ("Usage: tcpcli <IPaddress>"); // 1. create TCP socket sockfd = socket (af_inet, sock_stream, 0); // 2. specify the Server IP address and port bzero (& servaddr, sizeof (servaddr); servaddr. sin_family = af_inet; servaddr. sin_port = htons (serv_port); inet_ton (af_inet, argv [1], & servaddr. sin_addr); // 3. connect (sockfd, (Sa *) & servaddr, sizeof (servaddr) with the server; // 4. the str_cli function completes the remaining customer processing work. str_cli (stdin, sockfd); exit (0 );}
Initial code (stopped or other versions ):
# Include "unp. H "voidstr_cli (File * FP, int sockfd) {charsendline [maxline], recvline [maxline]; // 1. read a row from FP and store it to sendlinewhile (fgets (sendline, maxline, FP )! = NULL) {// 2. write the sendline content to the server writen (sockfd, sendline, strlen (sendline) connected to sockfd); // 3. read the echo row from the server and store it to recvlineif (Readline (sockfd, recvline, maxline) = 0) err_quit ("str_cli: Server terminated prematurely"); // 4. write the recvline content to the standard output fputs (recvline, stdout );}}
Problem 1: when some events occur on the socket, the customer may block the fgets call.
Improvement 1: Use select to override str_cli functions, block select calls, or wait for standard input to be readable,
Or wait for the socket to be readable. In this way, the customer will be notified immediately after the server is terminated.
/*** Select for TCP **/# include "unp. H "voidstr_cli (File * FP, int sockfd) {intmaxfdp1; // The maxfdpl parameter specifies the number of descriptors to be measured fd_setrset; // charsendline [maxline], recvline [maxline]; // 1. call selectfd_zero (& rset); // initialize rsetfor (;) {fd_set (fileno (FP), & rset ); // open the path fd_set (sockfd, & rset) corresponding to the FP of the standard I/O file; // open the socket FD's position maxfdp1 = max (fileno (FP), sockfd) + 1; // call select to block select (maxfdp1, & rset, null) when a descriptor is ready; // 2. process readable socket If (fd_isset (sockfd, & rset) {// use Readline to read the echo text, and then use fputs to output to stdoutif (Readline (sockfd, recvline, maxline) = 0) err_quit ("str_cli: Server terminated prematurely"); fputs (recvline, stdout);} // 3. process readable input if (fd_isset (fileno (FP), & rset) {// use fgets to read a line of text, and then write it to the socket If (fgets (sendline, maxline, FP) = NULL) return;/* All done */writen (sockfd, sendline, strlen (sendline ));}}}
Problem 1: running the echo client program compiled by the SELECT statement in batch, even if the user input has ended,
Data may still be in the pipeline to or from the server.
Improvement 2 (select and blocking I/O version): Use the shutdown function to take advantage of the half-off feature of TCP
The following code uses select and shutdown. The former notifies the customer as long as the server closes the connection at that end,
The latter allows the customer to correctly process batch input. This version also discards the code centered on text behavior and releases buffer operations.
/*** Use select for TCP and manipulate the buffer **/# include "unp. H "voidstr_cli (File * FP, int sockfd) {// stdineof indicates whether the standard input ends intmaxfdp1, stdineof; fd_setrset; charbuf [maxline]; INTN; // 1. call selectstdineof = 0; fd_zero (& rset); For (;) {If (stdineof = 0) fd_set (fileno (FP), & rset); fd_set (sockfd, & rset); maxfdp1 = max (fileno (FP), sockfd) + 1; select (maxfdp1, & rset, null, null); // 2. process readable socket If (fd_isset (sockfd, & rset) {/* socket is readable */If (n = read (sockfd, Buf, maxline) = 0) {If (stdineof = 1) // If EOF is read on the socket and EOF has been encountered in the standard input, return is terminated normally; else // otherwise, the server process prematurely terminated err_quit ("str_cli: Server terminated prematurely");} write (fileno (stdout), Buf, n);} // 3. processing readable input if (fd_isset (fileno (FP), & rset) {// If EOF is encountered in standard input, set stdineof to 1 // and call the shutdown function to send fin to the server. Disable the write operation if (n = read (fileno (FP), Buf, maxline) from the client to the server )) = 0) {stdineof = 1; Shutdown (sockfd, shut_wr); fd_clr (fileno (FP), & rset); continue;} writen (sockfd, Buf, n );}}}
Improvement 2 problem: Blocking I/O
For example, if a line of text is readable in the standard input, we call read to read it, and then call writen to send it to the server.
However, if the socket sending buffer is full, the writen call will be blocked. During the Process blocking during the writen call, data from the socket receiving buffer may be available for reading.
Similarly, if there is a line of input text readable from the socket, once the standard output is slower than the network, the process may still block subsequent write calls.
IMPROVEMENT 3 (non-blocking I/O version): use non-blocking I/O
/* Include nonb1 */# include "unp. H "voidstr_cli (File * FP, int sockfd) {intmaxfdp1, Val, stdineof; ssize_tn, nwritten; fd_setrset, Wset; charto [maxline], FR [maxline]; // to accommodate data from standard input to server // from the server to standard output: char * toiptr, * tooptr, * friptr, * froptr; // 1. set the descriptor to non-blocking val = fcntl (sockfd, f_getfl, 0); fcntl (sockfd, f_setfl, Val | o_nonblock); // socket descriptor val = fcntl (stdin_fileno, f_getfl, 0); fcntl (stdin_fileno, f_setfl, Val | o_nonblock); // standard input val = fcntl (stdout_fileno, f_getfl, 0); fcntl (stdout_fileno, f_setfl, Val | o_nonblock); // standard output // 2. initialize the buffer pointer toiptr = tooptr = to; friptr = froptr = fr; stdineof = 0; maxfdp1 = max (stdin_fileno, stdout_fileno), sockfd) + 1; // 3. main Loop: After a select // select call is called, the following conditions are tested separately for (;) {// specify the descriptor fd_zero (& rset) of interest ); fd_zero (& Wset); If (stdineof = 0 & toiptr <& to [maxline]) fd_set (S Tdin_fileno, & rset);/* Open the bit corresponding to the standard input in the read descriptor Set */If (friptr <& FR [maxline]) fd_set (sockfd, & rset ); /* Open the socket bit corresponding to the read descriptor Set */If (tooptr! = Toiptr) fd_set (sockfd, & Wset);/* Open the socket bit in the write descriptor Set */If (froptr! = Friptr) fd_set (stdout_fileno, & Wset);/* Open the bit corresponding to the standard output in the write descriptor Set * // call selectselect (maxfdp1, & rset, & Wset, null, null);/* end nonb1 * // * include nonb2 * // 4. perform a separate test on each of the following conditions // read from the standard input readif (fd_isset (stdin_fileno, & rset) {// read returns an error. Ewouldblock is a normal error, indicating that this descriptor is still in the blocking state. You should ignore this error if (n = read (stdin_fileno, toiptr, & to [maxline]-toiptr )) <0) {If (errno! = Ewouldblock) err_sys ("read error on stdin");} // read returns EOF, standard input processing ends else if (n = 0) {# ifdefvol2fprintf (stderr, "% s: EOF on stdin \ n", gf_time (); # endifstdineof = 1;/* mark standard input end */If (tooptr = toiptr) // No data in the to buffer must be sent to shutdown (sockfd, shut_wr ); /* disable write operations from the client to the server * // read the returned data else {# ifdefvol2fprintf (stderr, "% s: Read % d bytes from stdin \ n ", gf_time (), n); # endiftoiptr + = N; // Add toiptrfd_set (sockfd, & Wset); // enable the socket bit in the write descriptor set} // read from the socket (fd_isset (sockfd, & rset) {// read returns an error. Ewouldblock is a normal error, indicating that this descriptor is still in the blocking state. You should ignore this error if (n = read (sockfd, friptr, & FR [maxline]-friptr )) <0) {If (errno! = Ewouldblock) err_sys ("read error on socket");} // read returns EOF and else if (n = 0) {# ifdefvol2fprintf (stderr, "% s: EOF on socket \ n", gf_time (); # endifif (stdineof) // if you encounter an EOF from the server and have encountered an EOF in the standard input, otherwise, return is terminated too early./* normal termination */elseerr_quit ("str_cli: Server terminated prematurely ");} // read returned data else {# ifdefvol2fprintf (stderr, "% s: Read % d bytes from socket \ n", gf_time (), n ); # Endiffriptr + = N; // added friptrfd_set (stdout_fileno, & Wset ); // enable the bit corresponding to the standard output in the write descriptor set}/* end nonb2 * // * include nonb3 * // write to the standard output if (fd_isset (stdout_fileno, & Wset) & (n = friptr-froptr)> 0) {// write returns an error. If (nwritten = write (stdout_fileno, froptr, n) <0) {If (errno! = Ewouldblock) err_sys ("write error to stdout");} // write else {# ifdefvol2fprintf (stderr, "% s: wrote % d bytes to stdout \ n ", gf_time (), nwritten); # endiffroptr + = nwritten; // increase the number of written bytes if (froptr = friptr) froptr = friptr = fr; // If the output pointer catches up with the input pointer, the two pointers are not restored to point to the starting position of the buffer} // write to the socket If (fd_isset (sockfd, & Wset) & (n = toiptr-tooptr)> 0) {// write failure if (nwritten = write (sockfd, tooptr, n) <0) {If (errno! = Ewouldblock) err_sys ("write error to socket");} // write else {# ifdefvol2fprintf (stderr, "% s: wrote % d bytes to socket \ n ", gf_time (), nwritten); # endiftooptr + = nwritten; If (tooptr = toiptr) {// toiptr = tooptr = to; If (stdineof) // if the standard input has been read and all data in the buffer zone has been sent to the server, close the write socket Shutdown (sockfd, shut_wr ); /* Send fin */}/* end nonb3 */
IMPROVEMENT 3 problem: non-blocking version is complicated
IMPROVEMENT 4: when non-blocking I/O is required, the simpler method is to divide application tasks into multiple processes (using fork) or multiple threads.
Fork version:
/*** TCP uses two processes (fork) **/# include "unp. H "voidstr_cli (File * FP, int sockfd) {pid_tpid; charsendline [maxline], recvline [maxline]; // sub-process: read data from the server and output it to stdoutif (pid = fork () = 0) {While (Readline (sockfd, recvline, maxline)> 0) fputs (recvline, stdout); kill (getppid (), sigterm);/* The child process sends a sigterm signal to the parent process to terminate the parent process */exit (0);} // parent process: read data from stdin and send it to the server while (fgets (sendline, maxline, FP )! = NULL) writen (sockfd, sendline, strlen (sendline); // read EOF in stdin, input ends, close write socket Shutdown (sockfd, shut_wr ); /* EOF on stdin, send fin * // The parent process completes the data prepare. Call pause to bring yourself into sleep state pause (); return ;}
Threaded version:
/*** TCP uses two threads **/# include "unpthread. H "Void * copyto (void *); static intsockfd;/* Global for both threads to access */static file * FP; voidstr_cli (File * fp_arg, int sockfd_arg) {charrecvline [maxline]; pthread_ttid; // 1. save the parameter in the external variable sockfd = sockfd_arg;/* Copy arguments to externals */FP = fp_arg; // 2. create a new thread pthread_create (& tid, null, copyto, null); // 3. main thread loop: copy from socket to standard output while (Readline (sockfd, recvline, maxline) > 0) fputs (recvline, stdout);} // 4. copyto thread: Copy void * copyto (void * Arg) {charsendline [maxline]; while (fgets (sendline, maxline, FP) from standard input to socket )! = NULL) writen (sockfd, sendline, strlen (sendline); Shutdown (sockfd, shut_wr);/* EOF on stdin, send fin */Return (null ); /* 4 return (I. E ., thread terminates) When EOF on stdin */}
UNIX Network Programming volume 1 loose client program tcp client Programming Paradigm