• 【UNIX网络编程(四)】TCP套接字编程具体分析


    引言:

    套接字编程事实上跟进程间通信有一定的相似性,可能也正由于此。stevens这位大神才会将套接字编程与进程间的通信都归为“网络编程”,并分别写成了两本书《UNP1》《UNP2》。

    TCP套接字编程是套接字编程中很重要的一种,细致分析,事实上它的原理并不复杂。

    如今就以一个样例来具体分析TCP套接字编程。


    一、演示样例要求:

    本节中试着编写一个完毕的TCP客户/server程序演示样例。并对它进行深入的探讨。该演示样例会用到绝大多数的基本函数。未用到但比較重要的函数会在后面的补充上。

    演示样例要完毕的内容是实现一个客户/server程序:

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

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

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

    用图表示给该客户/server演示样例就是:


    大致分析该客户/server程序:首先。客户程序通过fgets函数从标准输入得到文本,然后客户程序调用write函数发给server。server通过read函数读取客户程序发过来的文本,然后server调用write函数将“从client读取的文本”又一次发给client。client调用read函数读取server又一次发给client的文本,之后客户程序调用fputs函数将读取的文本又一次发送到标准输出。

    从上面的分析能够看出,TCP客户程序与TCPserver程序是两个独立的程序,它们能够组合使用从而形成一个“TCP回射客户/server程序”。

    以下我们首先分别写好client程序、server端程序,并用Socket调试工具,进行调试。调试结束后。组合这两个程序,形成一个客户/server程序。


    二、演示样例分析:

    在写客户/server程序之前,我们先用图来展示一下各个阶段是怎样工作的。以及客户、server是怎样建立连接并传递数据的。


    从上到下依照顺序分析client应用程序以及server段应用程序例如以下:

    1、server端调用socket()函数创建一个套接字,由socket()函数创建的套接字都是主动套接字,也就是说,即使我们的初衷是在server端创建的套接字。但此时内核并不知道我们的意图,此时的套接字不过一个要调用connect()函数发起连接的客户套接字。那么该怎样使它变成一个被动套接字呢?也就是说怎样告诉内核应该接受指向该套接字的连接请求呢?必需要经过后面的bind()函数和listen()函数。

    2、调用完socket()函数后。TCP此时的状态是CLOSED状态,接下来我们调用bind()函数。该函数完毕对ip地址和port号的绑定。注意bind函数通常是由server调用的。client一般不会调用该函数绑定地址,由于对于port号,内核会自己主动的帮助client确定它的port号为一个暂时port号。对于IP地址。内核将依据所用外出网络接口来选择源IP地址。

    3、server端此时调用listen()函数,它的任务就是将上面步骤1中socket()函数创建的主动套接字转换为一个被动套接字,指示内核应该接受指定该套接字的连接请求,同一时候规定了内核应该为对应套接字排队的最大连接数。listen()函数调用完毕之后,TCP状态由CLOSED状态转换到LISTEN状态。

    4、server端调用accept()函数。它用于从已完毕连接队列对头返回下一个已完毕连接。假设已完毕连接队列为空,那么进程休眠。注意。accept()函数的第二三个參数分别用来返回已连接的对端进程(客户)的协议地址和套接字地址结构的长度。

    假设成功。它的返回值是由内核自己主动生成的一个全兴描写叙述符,代表与所返回客户的TCP连接。

    accept()函数的第一个參数就是socket函数创建的套接字,我们称之为监听套接字返回值为已连接套接字描写叙述符

    5、由于accept()函数会使server进程堵塞。等待client的连接。

    client用socket()函数创建一个套接字后。接着客户进程调用connect()函数建立与TCPserver的连接(注意。client没用bind、listen函数)。对于TCP套接字。connect函数会激发TCP的三路握手过程。即,client发SYN给server。server接到后回发SYN和ACK给client。client接受到server的SYN后。回发ACK给server端。三路握手完毕后。连接建立。此时的client与server端都处于ESTABLISHED状态。连接建立后,我们就能够通过read和write函数在客户和server端进行文本的传送了。

    6、当文本发送完成后,客户给server端发送一个FIN结束标志。server端接到FIN后给client回发一个FIN结束标志,然后客户接到server的FIN,在给server发送一个确认标志ACK。

    之后。连接断开。两端的TCP状态都处于CLOSED状态。


    三、演示样例代码及解说

    首先给出client的程序并用TCP/UDP Socket调试工具进行调试,并给出结果:

    代码:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    
    #define MAXLINE 2048
    
    int
    main(int argc, char **argv)
    {
    	int sockfd;
    	char sendline[MAXLINE], recvline[MAXLINE];
    	struct sockaddr_in	cliaddr;
    
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);/*创建套接字*/
    	if(sockfd < 0){
    		printf("socket function fail.
    ");	
    		return -1;
    	}
    
    	bzero(&cliaddr, sizeof(cliaddr));
    	cliaddr.sin_family = AF_INET;
    	cliaddr.sin_port = htons(9877);
    	inet_pton(AF_INET, argv[1], &cliaddr.sin_addr);/*对套接字地址结构进行操作*/
    
    	connect(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));/*建立连接*/
    
    	while(fgets(sendline, MAXLINE, stdin) != NULL){
    		write(sockfd, sendline, strlen(sendline));	/*利用套接字进行数据发送*/
    	}
    }
    编译该程序:gcc client.c -o client

    用调试工具进行调试,用该工具建立TCPserver。port号设为9877,然后运行上面生成的可运行程序./client 59.73.166.231(该IP地址。是我电脑的IP地址。Linux的IP地址是59.73.166.32。它会出如今调试工具的client地址部分)。

    并在client发送数据。abc。然后就会在调试工具里显示该文本abc,接着发送def,相同会在调试工具里出现def文本。效果例如以下图所看到的:


    由上面能够看出client的port号是53633。它是由内核自己主动分配的。

    以下给出server端的程序例如以下:

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    
    char c[2048];
    
    int
    main(int argc, char **argv)
    {
    	int 	n;
    	int		sockfd, coundfd;
    	struct sockaddr_in serv, client;
    	socklen_t socklen;
    
    	/*creat sockfd using the socket function*/
    	sockfd = socket(AF_INET, SOCK_STREAM, 0);
    	if(sockfd == -1){
    		printf("socket function fail.
    ");	
    		return -1;
    	}
    
    	/*bind the ip addr and port using the bind function*/
    	bzero(&serv, sizeof(serv));
    	serv.sin_family 	  = AF_INET;
    	serv.sin_addr.s_addr  = htonl(INADDR_ANY);
    	serv.sin_port		  = 9877;
    	if(bind(sockfd, (struct sockaddr *)&serv, sizeof(struct sockaddr_in))){
    		printf("bind function fail.
    ");	
    		return -1;
    	}
    
    	/*listen function*/
    	if(listen(sockfd, 5)){
    		printf("listen function fail.
    ");	
    		return -1;
    	}
    
    	/*accept function*/
    	socklen = sizeof(struct sockaddr_in);
    	printf("accept wait 
    ");
    	coundfd = accept(sockfd, (struct sockaddr *)&client, &socklen);
    	printf("accept wait 
    ");
    
    	while(1){
    		if((n = read(coundfd, c, 2048) > 0)){
    			write(coundfd, c, n);	
    		}
    	}
    
    }
    跟上面的客户端程序一样。也能够进行相同的server端程序的測试,此处略去了。


    四、完毕的TCP回射客户/server程序

    因为上面的程序是进行的測试。client并没有回射部分,以下的代码给出回射部分。

    客户程序:

    #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);
    	}
    }
    服务器程序:

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




  • 相关阅读:
    P1113 杂务 题解
    P3916 图的遍历 题解
    P5318 【深基18.例3】查找文献 题解
    P2814 家谱 题解
    P3879 [TJOI2010]阅读理解 题解
    P4305 不重复的数字题解
    P1955 [NOI2015] 程序自动分析题解
    P1892 [BOI2003]团伙
    P1525 [NOIP2010 提高组] 关押罪犯
    【610】keras 相关问题说明
  • 原文地址:https://www.cnblogs.com/llguanli/p/6775841.html
Copyright © 2020-2023  润新知