socket编程——tcp客户端实现框架对比分析

   在宝书《Unix网络编程》中,作者针对客户端提出了大概5种编程框架,分别如下:

(1)停-等方式。

(2)select加阻塞式IO版本。

(3)非阻塞IO版本。

(4)多进程fork版本。

(5)多线程版本。

在16.2.2小节中,对比总结了这几种方式,作者推荐的方式是:推荐使用 多进程fork版本。我们来整理,并简单分析一下这几种版本的编程框架。

一、停-等(迭代)方式示例代码;

#include "unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	char sendline[MAXLINE], recvline[MAXLINE];
	while(Fgets(sendline, MAXLINE, fp) != NULL){
		Write(sockfd, sendline, strlen(sendline));
		if(Readline(sockfd, recvline, MAXLINE) == 0)
			err_quit("str_cli:server terminated prematurely");
		Fputs(recvline, stdout);
	}
}


int 
main(int argc, char **argv)
{
	int sockfd;
	struct sockaddr_in servaddr;
	if(argc != 2)
		err_quit("usage:tcpcli <IPaddress>");
	sockfd = Socket(AF_INET, SOCK_STREAM, 0);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(SERV_PORT);
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
	Connect(sockfd, (SA *)&servaddr, sizeof(servaddr));
	str_cli(stdin, sockfd);
	exit(0);
}

 这种方式,很明显,效率极低,实时性最差。

二、select加阻塞式IO操作,程序框架如下所示;

/* 客户端socket初始化*/
/* 串口初始化,打开*/
connect(socketFd,....)
/* fd_set 初始化*/
 while( 1 ){
                FD_SET(usart1Fd, &all_set);
                FD_SET(socketFd, &all_set);
                read_set = all_set;
                if( select(fdmax + 1, &read_set, NULL, NULL, NULL) == -1){
                        perror("usart1 select error.\n");
                }
 
                if(FD_ISSET(usart1Fd, &read_set)){
                        usart_rx_len = read(usart1Fd, buf, 256);
                        if(usart_rx_len){
                                write(socketFd, buf, usart_rx_len);
                        }
                
                        else printf("usart rx error!\n");
                }
                if(FD_ISSET(socketFd, &read_set)){
                        socket_rx_len = read(socketFd, socket_buf, 256);
                        if(socket_rx_len){
                                write(usart1Fd, socket_buf, socket_rx_len);
                        }
                        else printf("socket rx error!\n");;
                }
        }

 这种方式下,主要是发挥了select的特性,也就是可以同时监控多个文件描述符,任意一个描述符可读,就 返回,实时性大大增加,并且是单进程单线程,是一种比较常用框架。

三、非阻塞IO方式,程序代码如下:

void
str_cli(FILE *fp, int sockfd)
{
	int			maxfdp1, val, stdineof;
	ssize_t		n, nwritten;
	fd_set		rset, wset;
	char		to[MAXLINE], fr[MAXLINE];
	char		*toiptr, *tooptr, *friptr, *froptr;

	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);

	toiptr = tooptr = to;	/* initialize buffer pointers */
	friptr = froptr = fr;
	stdineof = 0;

	maxfdp1 = max(max(STDIN_FILENO, STDOUT_FILENO), sockfd) + 1;
	for ( ; ; ) {
		FD_ZERO(&rset);
		FD_ZERO(&wset);
		if (stdineof == 0 && toiptr < &to[MAXLINE])
			FD_SET(STDIN_FILENO, &rset);	/* read from stdin */
		if (friptr < &fr[MAXLINE])
			FD_SET(sockfd, &rset);			/* read from socket */
		if (tooptr != toiptr)
			FD_SET(sockfd, &wset);			/* data to write to socket */
		if (froptr != friptr)
			FD_SET(STDOUT_FILENO, &wset);	/* data to write to stdout */

		Select(maxfdp1, &rset, &wset, NULL, NULL);
/* end nonb1 */
/* include nonb2 */
		if (FD_ISSET(STDIN_FILENO, &rset)) {
			if ( (n = read(STDIN_FILENO, toiptr, &to[MAXLINE] - toiptr)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("read error on stdin");

			} else if (n == 0) {
#ifdef	VOL2
				fprintf(stderr, "%s: EOF on stdin\n", gf_time());
#endif
				stdineof = 1;			/* all done with stdin */
				if (tooptr == toiptr)
					Shutdown(sockfd, SHUT_WR);/* send FIN */

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: read %d bytes from stdin\n", gf_time(), n);
#endif
				toiptr += n;			/* # just read */
				FD_SET(sockfd, &wset);	/* try and write to socket below */
			}
		}

		if (FD_ISSET(sockfd, &rset)) {
			if ( (n = read(sockfd, friptr, &fr[MAXLINE] - friptr)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("read error on socket");

			} else if (n == 0) {
#ifdef	VOL2
				fprintf(stderr, "%s: EOF on socket\n", gf_time());
#endif
				if (stdineof)
					return;		/* normal termination */
				else
					err_quit("str_cli: server terminated prematurely");

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: read %d bytes from socket\n",
								gf_time(), n);
#endif
				friptr += n;		/* # just read */
				FD_SET(STDOUT_FILENO, &wset);	/* try and write below */
			}
		}
/* end nonb2 */
/* include nonb3 */
		if (FD_ISSET(STDOUT_FILENO, &wset) && ( (n = friptr - froptr) > 0)) {
			if ( (nwritten = write(STDOUT_FILENO, froptr, n)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("write error to stdout");

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: wrote %d bytes to stdout\n",
								gf_time(), nwritten);
#endif
				froptr += nwritten;		/* # just written */
				if (froptr == friptr)
					froptr = friptr = fr;	/* back to beginning of buffer */
			}
		}

		if (FD_ISSET(sockfd, &wset) && ( (n = toiptr - tooptr) > 0)) {
			if ( (nwritten = write(sockfd, tooptr, n)) < 0) {
				if (errno != EWOULDBLOCK)
					err_sys("write error to socket");

			} else {
#ifdef	VOL2
				fprintf(stderr, "%s: wrote %d bytes to socket\n",
								gf_time(), nwritten);
#endif
				tooptr += nwritten;	/* # just written */
				if (tooptr == toiptr) {
					toiptr = tooptr = to;	/* back to beginning of buffer */
					if (stdineof)
						Shutdown(sockfd, SHUT_WR);	/* send FIN */
				}
			}
		}
	}
}
/* end nonb3 */

 这种方式,按照作者的计算,速度是最快的,实时性也 最好,但是从代码量上就可以看出,该种框架过于复杂,反而不推荐使用。

四、多进程fork版本,代码如下:

void
str_cli(FILE *fp, int sockfd)
{
	pid_t pid;
	char sendline[MAXLINE], recvline[MAXLINE];

	if( (pid = Fork()) == 0){   /* child: server -> stdout */
		while(Readline(sockfd, recvline, MAXLINE) > 0)
			Fputs(recvline, stdout);

		kill(getppid(), SIGTERM);
		close(usartFd);   
		exit(0);
	}
	/* parent: stdin -> server */
	while(Fgets(sendline, MAXLINE, fp) != NULL)
		Writen(sockfd, sendline, strlen(sendline));

	Shutdown(sockfd, SHUT_WR);		/* EOF on stdin, send FIN */
	pause();
	return ;
}

 这种框架,采用了 子进程“继承”的优势,子进程会继承父进程中所有的描述符,而且由于是两个进程,所以实时性也比较好,最大的优点是,实现起来非常的方便。

五、多线程框架,代码如下:

void *copyto(void *);

/* global for both threads to access */
static int sockfd;
static FILE *fp;

void 
str_cli(FILE *fp_arg, int sockfd_arg)
{
	char recvline[MAXLINE];
	pthread_t tid;
	sockfd = sockfd_arg;
	fp = fp_arg;

	Pthread_create(&tid, NULL, copyto, NULL);
	while(Readline(sockfd, recvline, MAXLINE) > 0)
		Fputs(recvline, stdout);
}

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;
}

   从上面的代码可知, 主线程用于与socket服务器进行通信,子线程则是与串口进行通信,这种方式类似于多进程,速度也比多进程稍微快一些。

小结:非阻塞版本的执行速度是最快的,select方式是单进程单线程,程序整体开销小(所以一般的库实现都是采用select版本),多进程版本是最简单的,速度也还可以,而多线程的速度比多进程稍微快些,综合考虑,推荐使用多进程fork版本,其次是多线程,再次使用select方式,不推荐 使用非阻塞版本和 迭代方式。

发布了256 篇原创文章 · 获赞 278 · 访问量 67万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 精致技术 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览