• 【UNIX网络编程(三)】TCP客户/server程序演示样例


    上一节给出了TCP网络编程的函数。这一节使用那些基本函数编写一个完毕的TCP客户/server程序演示样例。

    样例运行的过程例如以下

    1、客户从标准输入读入一行文本,并写给server。

    2、server从网络输入读入这行文本,并回射给客户。

    3、客户从网络输入读入这行回射文本,并显示在标准输出上。

    用图描写叙述例如以下:


    编写TCP回射server程序例如以下:

    #include <stdio.h>
    #include <errno.h>
    #include <stdlib.h>
    #include <strings.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    
    #define SERV_PORT	9877
    #define MAXLINE		2048
    
    void
    str_echo(int sockfd)
    {
    	ssize_t	n;
    	char	buf[MAXLINE];
    
    again:
    	while((n = read(sockfd, buf, MAXLINE)) > 0)
    		write(sockfd, buf, n);
    
    	if(n < 0 && errno == EINTR)
    		goto again;
    	else if(n < 0)
    		printf("str_echo : read error");
    }
    
    int
    main(int argc, char **argv)
    {
    	int	listenfd, connfd;
    	pid_t	childpid;
    	socklen_t	clilen;
    	struct	sockaddr_in	cliaddr, servaddr;
    
    	listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if(listenfd == -1){
    		printf("socket fail.
    ");	
    		return -1;
    	}
    
    	bzero(&servaddr, sizeof(struct sockaddr_in));
    	servaddr.sin_family 	 = AF_INET;
    	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    	servaddr.sin_port 		 = htons(SERV_PORT);
    
    	if(bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr))){
    		printf("bind fail.
    ");	
    		return -1;
    	}
    	if(listen(listenfd, 5)){
    		printf("listen fail.
    ");	
    		return -1;
    	}
    
    	printf("listen stat.
    ");
    
    	for(;;){
    		clilen = sizeof(cliaddr);				
    		connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
    		if(connfd == -1){
    			printf("accept fail.
    ");	
    			return -1;
    		}
    		printf("accept stat.
    ");
    		if((childpid = fork()) == 0){
    			close(listenfd);	
    			printf("servers.
    ");
    			str_echo(connfd);
    			exit(0);
    		}
    		close(connfd);
    	}
    
    	return 0;
    }
    
    
    TCP回射客户程序例如以下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    
    
    #define MAXLINE 2048
    #define SERV_PORT 9877
    
    
    void str_cli(FILE *, int);
    
    
    int
    main(int argc, char **argv)
    {
    	int 	sockfd;
    	struct  sockaddr_in cliaddr;
    
    
    	if(argc != 2){
    		printf("usage:tcpcli <IPaddress>");	
    		return -1;
    	}
    
    
    	sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    	if(sockfd == -1){
    		printf("socket fail.
    ");	
    		return -1;
    	}
    
    
    	bzero(&cliaddr, sizeof(cliaddr));
    	cliaddr.sin_family = AF_INET;
    	cliaddr.sin_port   = htons(SERV_PORT);
    	if(!(inet_pton(AF_INET, argv[1], &cliaddr.sin_addr))){
    		printf("inet pton fail.
    ");	
    		return -1;
    	}
    
    
    	if(connect(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr))){
    		printf("connect fail.
    ");	
    		return -1;
    	}
    
    
    	str_cli(stdin, sockfd);
    
    
    	exit(0);
    
    
    }
    
    
    void
    str_cli(FILE *fp, int sockfd)
    {
    	char 	sendline[MAXLINE], recvline[MAXLINE];
    
    
    	while(fgets(sendline, MAXLINE, fp) != NULL){
    		write(sockfd, sendline, strlen(sendline));
    		
    		if(read(sockfd, recvline, MAXLINE) == 0)	
    			printf("str_cli:server terminated prematurely");
    
    
    		fputs(recvline, stdout);
    	}
    }
    编译两个程序:

    gcc tcpserv.c -o tcpserv

    gcc tcpcli.c -o tcpcli

    測试,正常启动server./tcpserv &,之后启动client./tcpcli 127.0.0.1,之后就能够在终端输入一行文本,接着就会显示出该文本。

    分析整个建立过程

    1、首先在后台启动server,server启动后,它调用socket、bind、listen和accept。并堵塞于accept调用。在启动客户程序之前。用netstat程序检查server监听套接字的状态。

    netstat -a | grep 9877。显演示样例如以下内容:

    tcp        0      0 *:9877                  *:*                     LISTEN

    该行表明。有一个套接字处于LISTEN状态,它有通配的本地IP地址。本地端口为9877。

    2、接着在同一主机上启动客户。并指定server主机的IP地址为127.0.0.1(环回地址)。./tcpcli 127.0.0.1

    客户调用socket和connect,connect引起TCP的三路握手过程。

    当三路握手完毕之后。客户中的connect和server中的accept均返回,连接于是建立。

    3、客户调用str_cli函数,该函数堵塞与fgets调用。

    4、当server中的accept返回时,server调用fork。再又子进程调用str_echo。该函数调用read。而read在等待客户送入一行文本期间堵塞。

    5、还有一方面。server父进程再次调用accept并堵塞,等待下一个客户连接。

    至此。有3个都在睡眠的进程:客户进程、server父进程和server子进程。


    分析终止过程:

    连接建立后,在客户的标准输入中键入什么。都会回射到它的标准输出中,在客户正常终止时。客户和server的过程例如以下:

    1、当我们键入EOF字符时,fgets返回一个空指针,于是str_cli函数返回。

    2、当str_cli返回到客户的main函数时,main通过调用exit终止。

    3、进程终止处理的部分工作是关闭全部打开的描写叙述符,因此客户打开的套接字由内核关闭。这导致客户TCP发送一个FIN给server,serverTCP则以ACK响应,这就是TCP链接终止序列的前半部分。

    至此。server套接字处于CLOSE_WAIT状态,客户套接字则处于FIN_WAIT_2状态。

    4、当serverTCP接收FIN时,server子进程堵塞于read调用,于是read返回0。这导致str_echo函数返回server子进程的main函数。

    5、server子进程通过调用exit来终止。

    6、server子进程中打开的全部描写叙述符随之关闭。

    7、进程终止处理的还有一部分是:在server子进程终止时,给父进程发送一个SIGCHLD信号。


    思考:

    1、子进程给父进程发送了一个SIGCHLD信号,但父进程没有捕获它,而是採用了默认行为。这样会导致子进程进入僵死状态。

    2、上面的都是正常启动正常终止的情况,若server进程在客户之前终止。则客户会发生什么?若server主机崩溃又会如何?等等

  • 相关阅读:
    MySQL 数据库常用命令
    HTML常用标签介绍
    浏览器 返回状态码汇总
    Mysql常用的三种数据库引擎比较
    系统常用端口大全
    nginx入门与实战
    Linux系统基础优化及常用命令
    python开发之virtualenv与virtualenvwrapper讲解
    常用服务安装部署
    远程连接Linux
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10760335.html
  • Copyright © 2020-2023  润新知