UNIXネットワークプログラミングボリューム1回射クライアントプログラムTCPクライアントプログラム設計パターン
10637 ワード
本文はsenlieオリジナルで、転載はこの住所を残してください:http://blog.csdn.net/zhengsenlie
次に、同じTCPプロトコルを使用するクライアントプログラムのいくつかの異なるバージョンを紹介します.それぞれ、停止バージョン、selectブロッキングI/Oバージョン、非ブロッキングI/Oバージョン、forkバージョン、スレッド化バージョンです.これらは、同じmain関数によって呼び出され、同じ機能、すなわち、リターンプログラムクライアントを実現します.
標準入力から1行のテキストを読み込み、サーバに書き込み、サーバのローへの戻りを読み込み、戻り行を標準出力に書き込みます.
ここで、非ブロックI/Oバージョンは、すべてのバージョンで最も高速に実行されますが、コードは複雑です.
新しいスレッドを作成するのはforkを使用して新しいプロセスを派生するよりもずっと速いです.
次のコードのスレッド化バージョンの実行速度はforkバージョンよりやや速く、非ブロックI/Oよりやや遅い.一般的なプログラミングでは、スレッドバージョンが推奨されます.
以下は、「Unixネットワークプログラミング:ボリューム1」の各バージョンのパフォーマンステスト結果です.
354.0秒、停止バージョン
12.3秒、selectプラスブロックI/Oバージョン
6.9秒、 非ブロックI/Oバージョン
8.7秒、
forkバージョン
8.5秒、 スレッド化バージョン
次はmain関数のコードです
最初のコード(停止バージョン):
質問1:ソケットにイベントが発生した場合、お客様がfgets呼び出しをブロックする可能性があります.
改善1:selectを使用してstr_を書き換えるcli関数、select呼び出しにブロックされるか、標準入力が読めるのを待つか、
あるいはソケットが読めるのを待つ.これにより、サーバが終了すると、お客様はすぐに通知を受けることができます.
改善1の問題:selectで作成したリターンクライアントプログラムを一括して実行し、ユーザー入力の末尾に遭遇しても、
データがサーバに運ばれたパイプまたはサーバからのパイプにある可能性があります.
改善2(selectブロッキングI/Oバージョン):shutdown関数を使用してTCPのハーフクローズ特性を利用
次のコードはselectとshutdownを使用しています.前者はサーバが一端の接続を閉じるだけでお客様に通知します.
後者は、お客様がバッチ入力を正しく処理できるようにします.このバージョンでは、テキストの動作を中心としたコードも廃棄され、バッファに対する操作が公開されます.
改善2問題:ブロックI/O
たとえば、標準入力に1行のテキストが読める場合は、readを呼び出して読み込み、呼び出します. writenはそれをサーバに送信します.
しかし、ソケット送信バッファが満たされている場合、writen呼び出しはブロックされます.プロセスがwriten呼び出しにブロックされている間、ソケット受信バッファからのデータが読み出される可能性があります.
同様に、ソケットから1行の入力テキストが読み取り可能である場合、標準出力がネットワークよりも遅い場合、プロセスは後続のwrite呼び出しにブロックされる可能性があります.
改善3(非ブロックI/Oバージョン):非ブロックI/Oの使用
改善3:非ブロックバージョンが複雑
改善4:非ブロックI/Oが必要な場合、より簡単な方法はアプリケーションタスクを複数のプロセス(forkを使用)または複数のスレッドに分割することです
forkバージョン:
スレッド化バージョン:
次に、同じTCPプロトコルを使用するクライアントプログラムのいくつかの異なるバージョンを紹介します.それぞれ、停止バージョン、selectブロッキングI/Oバージョン、非ブロッキングI/Oバージョン、forkバージョン、スレッド化バージョンです.これらは、同じmain関数によって呼び出され、同じ機能、すなわち、リターンプログラムクライアントを実現します.
標準入力から1行のテキストを読み込み、サーバに書き込み、サーバのローへの戻りを読み込み、戻り行を標準出力に書き込みます.
ここで、非ブロックI/Oバージョンは、すべてのバージョンで最も高速に実行されますが、コードは複雑です.
新しいスレッドを作成するのはforkを使用して新しいプロセスを派生するよりもずっと速いです.
次のコードのスレッド化バージョンの実行速度はforkバージョンよりやや速く、非ブロックI/Oよりやや遅い.一般的なプログラミングでは、スレッドバージョンが推奨されます.
以下は、「Unixネットワークプログラミング:ボリューム1」の各バージョンのパフォーマンステスト結果です.
354.0秒、停止バージョン
12.3秒、selectプラスブロックI/Oバージョン
6.9秒、 非ブロックI/Oバージョン
8.7秒、
forkバージョン
8.5秒、 スレッド化バージョン
次はmain関数のコードです
#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if (argc != 2)
err_quit("usage: tcpcli <IPaddress>");
//1. TCP
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
//2. IP
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
//3.
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
//4.str_cli
str_cli(stdin, sockfd);
exit(0);
}
最初のコード(停止バージョン):
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
char sendline[MAXLINE], recvline[MAXLINE];
//1. fp , sendline
while (Fgets(sendline, MAXLINE, fp) != NULL) {
//2. sendline sockfd
Writen(sockfd, sendline, strlen(sendline));
//3. , recvline
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
//4. recvline
Fputs(recvline, stdout);
}
}
質問1:ソケットにイベントが発生した場合、お客様がfgets呼び出しをブロックする可能性があります.
改善1:selectを使用してstr_を書き換えるcli関数、select呼び出しにブロックされるか、標準入力が読めるのを待つか、
あるいはソケットが読めるのを待つ.これにより、サーバが終了すると、お客様はすぐに通知を受けることができます.
/**
* TCP select
**/
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1; // maxfdpl
fd_set rset; //
char sendline[MAXLINE], recvline[MAXLINE];
//1. select
FD_ZERO(&rset); // rset
for ( ; ; ) {
FD_SET(fileno(fp), &rset); // I/O fp
FD_SET(sockfd, &rset); // sockfd
maxfdp1 = max(fileno(fp), sockfd) + 1;
// select
Select(maxfdp1, &rset, NULL, NULL, NULL);
//2.
if (FD_ISSET(sockfd, &rset)) {
// readline , fputs stdout
if (Readline(sockfd, recvline, MAXLINE) == 0)
err_quit("str_cli: server terminated prematurely");
Fputs(recvline, stdout);
}
//3.
if (FD_ISSET(fileno(fp), &rset)) {
// fgets , writen
if (Fgets(sendline, MAXLINE, fp) == NULL)
return; /* all done */
Writen(sockfd, sendline, strlen(sendline));
}
}
}
改善1の問題:selectで作成したリターンクライアントプログラムを一括して実行し、ユーザー入力の末尾に遭遇しても、
データがサーバに運ばれたパイプまたはサーバからのパイプにある可能性があります.
改善2(selectブロッキングI/Oバージョン):shutdown関数を使用してTCPのハーフクローズ特性を利用
次のコードはselectとshutdownを使用しています.前者はサーバが一端の接続を閉じるだけでお客様に通知します.
後者は、お客様がバッチ入力を正しく処理できるようにします.このバージョンでは、テキストの動作を中心としたコードも廃棄され、バッファに対する操作が公開されます.
/**
* TCP select
**/
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
//stdineof
int maxfdp1, stdineof;
fd_set rset;
char buf[MAXLINE];
int n;
//1. select
stdineof = 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, NULL);
//2.
if (FD_ISSET(sockfd, &rset)) { /* socket is readable */
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
if (stdineof == 1) // EOF, EOF,
return;
else // ,
err_quit("str_cli: server terminated prematurely");
}
Write(fileno(stdout), buf, n);
}
//3.
if (FD_ISSET(fileno(fp), &rset)) {
// EOF , stdineof 1
// shutdown FIN,
if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
stdineof = 1;
Shutdown(sockfd, SHUT_WR);
FD_CLR(fileno(fp), &rset);
continue;
}
Writen(sockfd, buf, n);
}
}
}
改善2問題:ブロックI/O
たとえば、標準入力に1行のテキストが読める場合は、readを呼び出して読み込み、呼び出します. writenはそれをサーバに送信します.
しかし、ソケット送信バッファが満たされている場合、writen呼び出しはブロックされます.プロセスがwriten呼び出しにブロックされている間、ソケット受信バッファからのデータが読み出される可能性があります.
同様に、ソケットから1行の入力テキストが読み取り可能である場合、標準出力がネットワークよりも遅い場合、プロセスは後続のwrite呼び出しにブロックされる可能性があります.
改善3(非ブロックI/Oバージョン):非ブロックI/Oの使用
/* include nonb1 */
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
int maxfdp1, val, stdineof;
ssize_t n, nwritten;
fd_set rset, wset;
char to[MAXLINE], fr[MAXLINE];
//to
//from
char *toiptr, *tooptr, *friptr, *froptr;
//1.
val = Fcntl(sockfd, F_GETFL, 0);
Fcntl(sockfd, F_SETFL, val | O_NONBLOCK); //
val = Fcntl(STDIN_FILENO, F_GETFL, 0);
Fcntl(STDIN_FILENO, F_SETFL, val | O_NONBLOCK); //
val = Fcntl(STDOUT_FILENO, F_GETFL, 0);
Fcntl(STDOUT_FILENO, F_SETFL, val | O_NONBLOCK); //
//2.
toiptr = tooptr = to;
friptr = froptr = fr;
stdineof = 0;
maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
//3. : select
// select
for ( ; ; ) {
//
FD_ZERO(&rset);
FD_ZERO(&wset);
if (stdineof == 0 && toiptr < &to[MAXLINE])
FD_SET(STDIN_FILENO, &rset); /* */
if (friptr < &fr[MAXLINE])
FD_SET(sockfd, &rset); /* */
if (tooptr != toiptr)
FD_SET(sockfd, &wset); /* */
if (froptr != friptr)
FD_SET(STDOUT_FILENO, &wset); /* */
// select
Select(maxfdp1, &rset, &wset, NULL, NULL);
/* end nonb1 */
/* include nonb2 */
//4.
// read
if (FD_ISSET(STDIN_FILENO, &rset)) {
//read 。 EWOULDBLOCK , ,
if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("read error on stdin");
}
//read EOF,
else if (n == 0) {
#ifdef VOL2
fprintf(stderr, "%s: EOF on stdin
", gf_time());
#endif
stdineof = 1; /* */
if (tooptr == toiptr) // to
Shutdown(sockfd, SHUT_WR);/* */
}
//read
else {
#ifdef VOL2
fprintf(stderr, "%s: read %d bytes from stdin
", gf_time(), n);
#endif
toiptr += n; // toiptr
FD_SET(sockfd, &wset); //
}
}
// read
if (FD_ISSET(sockfd, &rset)) {
//read 。 EWOULDBLOCK , ,
if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("read error on socket");
}
//read EOF,
else if (n == 0) {
#ifdef VOL2
fprintf(stderr, "%s: EOF on socket
", gf_time());
#endif
if (stdineof) // EOF , EOF , ;
return; /* normal termination */
else
err_quit("str_cli: server terminated prematurely");
}
//read
else {
#ifdef VOL2
fprintf(stderr, "%s: read %d bytes from socket
",
gf_time(), n);
#endif
friptr += n; // friptr
FD_SET(STDOUT_FILENO, &wset); //
}
}
/* end nonb2 */
/* include nonb3 */
//write
if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0)) {
//write 。
if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("write error to stdout");
}
//write
else {
#ifdef VOL2
fprintf(stderr, "%s: wrote %d bytes to stdout
",
gf_time(), nwritten);
#endif
froptr += nwritten; //
if (froptr == friptr)
froptr = friptr = fr; // ,
}
}
//write
if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) {
//write
if ( (nwritten = write(sockfd, tooptr, n)) < 0) {
if (errno != EWOULDBLOCK)
err_sys("write error to socket");
}
//write
else {
#ifdef VOL2
fprintf(stderr, "%s: wrote %d bytes to socket
",
gf_time(), nwritten);
#endif
tooptr += nwritten;
if (tooptr == toiptr) { //
toiptr = tooptr = to;
if (stdineof) // , ,
Shutdown(sockfd, SHUT_WR); /* send FIN */
}
}
}
}
}
/* end nonb3 */
改善3:非ブロックバージョンが複雑
改善4:非ブロックI/Oが必要な場合、より簡単な方法はアプリケーションタスクを複数のプロセス(forkを使用)または複数のスレッドに分割することです
forkバージョン:
/**
* TCP (fork)
**/
#include "unp.h"
void
str_cli(FILE *fp, int sockfd)
{
pid_t pid;
char sendline[MAXLINE], recvline[MAXLINE];
// : stdout
if ( (pid = Fork()) == 0) {
while (Readline(sockfd, recvline, MAXLINE) > 0)
Fputs(recvline, stdout);
kill(getppid(), SIGTERM); /* SIGTERM , */
exit(0);
}
// : stdin
while (Fgets(sendline, MAXLINE, fp) != NULL)
Writen(sockfd, sendline, strlen(sendline));
// stdin EOF, ,
Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */
// pause
pause();
return;
}
スレッド化バージョン:
/**
* TCP
**/
#include "unpthread.h"
void *copyto(void *);
static int sockfd; /* global for both threads to access */
static FILE *fp;
void
str_cli(FILE *fp_arg, int sockfd_arg)
{
char recvline[MAXLINE];
pthread_t tid;
//1.
sockfd = sockfd_arg; /* copy arguments to externals */
fp = fp_arg;
//2.
Pthread_create(&tid, NULL, copyto, NULL);
//3. :
while (Readline(sockfd, recvline, MAXLINE) > 0)
Fputs(recvline, stdout);
}
//4.copyto :
void *
copyto(void *arg)
{
char sendline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL)
Writen(sockfd, sendline, strlen(sendline));
Shutdown(sockfd, SHUT_WR); /* EOF on stdin, send FIN */
return(NULL);
/* 4return (i.e., thread terminates) when EOF on stdin */
}