TCP 协议的理解及套接口编程

TCP 协议的理解及套接口编程

注:部分截图没给出,转载需说明出处,仅供学习。

1.编写服务端与客户端程序
(1)基本通信编程

客户端要求:
指定客户端的IP地址和端口号
与服务端建立TCP连接
请求读取文件A(全部小写字母,多行),并将A文件发送给服务器端
显示本地和异地协议地址信息

服务端要求:
指定服务器端端口号,使用通配IP地址,监听TCP端口
处理客户端的TCP连接请求
接受客户端转换请求(将发送的A文件接受后转换全部大写字母),并返回给客户端。
显示本地和异地协议地址信息
总结网络编程中的注意事项

主要思路:客户端读取保存在客户端的文件(这里是test0.txt),并将里面的内容发送给服务器端,服务器端接受后转换成大写并返回给客户端。
头文件:(下面的所有代码头文件不再列出)

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<stdlib.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<error.h>
#include<assert.h>
#include<string.h>
#include<sys/select.h>                        
#include<sys/time.h>
#include <ctype.h>  /*for toupper*/
#include <fcntl.h> 

主要代码:
客户端(shiyan2.1c.c):

/* client端:./client <上传文件名>*/
#define    MAXLINE        1024 
void usage(char *command)//没有输入文件名时报错
{  
    printf("usage :%s filename\n", command);  
    exit(0);  
}  
int main(int argc, char *argv[])
{
	int sock_id;
	int len;
	struct sockaddr_in serv_addr; //服务器端网络地址结构体
	struct sockaddr_in my_addr;//本地地址 
	char buf[BUFSIZ];  //数据传送的缓冲区
	FILE                   *fp; 
	
	if (argc != 2) {    /*输入参数*/
        usage(argv[0]);  
    }  
    if ((fp = fopen(argv[1],"r")) == NULL) {    /*输入的 上传文件名*/
        perror("Open file failed\n");  
        exit(0);  
    }	
	/*创建客户端套接字*/
	if((sock_id=socket(PF_INET,SOCK_STREAM,0))<0)    {
		perror("socket");                                   /*返回-1 出错*/
		return 1;
	}	
	//初始化	
	memset(&serv_addr,0,sizeof(serv_addr)); 
	serv_addr.sin_family=AF_INET; 
	serv_addr.sin_port= htons(8000);
	serv_addr.sin_addr.s_addr=inet_addr("127.0.0.1");
    /*连接*/
	if(connect(sock_id,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))<0){
		perror("connect");
		return 1;
	}
	printf("connected to server\n");
	
	if((len=recv(sock_id,buf,BUFSIZ,0))>0){
        buf[len]='\0';
	    printf("%s",buf); }
socklen_t l;
	l=sizeof(my_addr);
	if(getsockname(sock_id,(struct sockaddr*)&my_addr,&len)){
	    printf("getsockname error\n");
		return(-1);
	}
	printf("client addr: IP:%s port:%d \n",inet_ntoa(my_addr.sin_addr),ntohs(my_addr.sin_port));
	l=sizeof(serv_addr);
	if(getpeername(sock_id,(struct sockaddr*)&serv_addr,&l))
    {	
        printf("getpeername error\n");
        return(-1);
	}
	printf("server addr: IP:%s port:%d \n",inet_ntoa(serv_addr.sin_addr),ntohs(serv_addr.sin_port));
	
	/*读取文件信息并发送给服务器端*/
	bzero(buf, MAXLINE);  
	while ((len = fread(buf, sizeof(char), MAXLINE, fp)) >0 ) {  
        if (  (send(sock_id, buf, len, 0))< 0 ) {  
            perror("Send file failed\n");  
            exit(0);  
        }  
        bzero(buf, MAXLINE);  
    }  
    fclose(fp);
	bzero(buf, MAXLINE);
    len=recv(sock_id,buf,BUFSIZ,0);  //接受服务器端的数据
	buf[len]='\0';
	printf("received:%s\n",buf);
	close(sock_id);//关闭套接字
    return 0;
}

服务端(shiyan2.1s.c):

int main(int argc, char *argv[]){
	int server_sockfd;//服务器端套接字
	int client_sockfd;//客户端套接字
	int len,i;
	struct sockaddr_in my_addr;   //服务器网络地址结构体
	struct sockaddr_in remote_addr; //客户端网络地址结构体
	int sin_size;
	char buf[BUFSIZ];  
	memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
	my_addr.sin_family=AF_INET;
	my_addr.sin_addr.s_addr=INADDR_ANY;
	my_addr.sin_port=htons(8000); 
	/*创建服务器端套接字*/
printf("creating socket...\n");
	if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0){  
		perror("socket");
		return 1;
	}
	int on = 1; 
    if((setsockopt(server_sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)  {  
	perror("setsockopt failed");
       exit(1);  
    }   
        /*绑定*/
	if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){
		perror("bind");
		return 1;
	}
	
	/*监听连接请求--监听队列长度为5*/
	listen(server_sockfd,5);
	sin_size=sizeof(struct sockaddr_in);
	
	/*等待客户端连接请求到达*/
	if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0){
		perror("accept");
		return 1;
	}
	printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
	
struct sockaddr_in sockn,peern; 
	socklen_t le;
	le=sizeof(sockn);
	if(getsockname(server_sockfd,(struct sockaddr*)&sockn,&le)){
		printf("getsockname error\n");
		return(-1);
	}
	printf("server addr: IP:%s port:%d \n",inet_ntoa(sockn.sin_addr),ntohs(sockn.sin_port));
	socklen_t l;
	l=sizeof(peern);
	if(getpeername(client_sockfd,(struct sockaddr*)&peern,&l)){
		 printf("getpeername error\n");
		 return(-1);
	 }
	printf("client addr: IP:%s port:%d \n",inet_ntoa(peern.sin_addr),ntohs(peern.sin_port));	

	len=send(client_sockfd,"message from server : Welcome to my server\n",43,0);//发送欢迎信息
	/*接收客户端的数据并将其发送给客户端*/
	while((len=recv(client_sockfd,buf,BUFSIZ,0))>0){
		buf[len]='\0';
		printf("%s\n",buf);
		for (i = 0; i < len; i++)
			buf[i] = toupper(buf[i]);//用来将字符c转换为大写英文字母
		printf("toupper: %s\n",buf);
		if(send(client_sockfd,buf,len,0)<0){
			perror("write");
			return 1;
		}
	}
	close(client_sockfd);
	close(server_sockfd);
        return 0;
}

test0.txt文件内容:
test0
运行结果:(分别为客户端和服务器端)
在这里插入图片描述
在这里插入图片描述
在网络编程中的注意事项:
1)在编程前,要熟悉各个函数的用法以及参数,才能在编程时更灵活的使用他们。
2)使用文件指针fp时,对文件读完/写完要及时关闭,否则会导致数据丢失。
3)在服务器端可以使用setsockopt函数,可以重用socket,方便了编程过程中多次编译。
4)在使用ssh连接一个linux系统时(我使用的是kali),登录时不能登录系统的root账户,其他账户才可以。
5)服务器端的IP地址初始化一定要是INADDR_ANY,才能够接收到任何IP地址的客户端连接。
6)编译时,要将服务器先开启,后开客户端。

(2)多连接编程
客户端:获取两个随机数,分别发送给两个服务器;并扩展到多服务器;多个套接口向多个服务器请求服务。
服务端:两个服务器,1个将随机数相加,一个将随机数相乘,分别返回给客户端;扩展到多个服务器。

主要思路:客户端使用数组创建多个socket,手动输入两个随机数发送给两个服务器端,服务端计算后并返回。
主要代码:
客户端:(dljcli.c)

#define NUMBER 2
int main(int argc,char *argv[]){
	int sockfd[NUMBER];  /*NUMBER为需要建立的套接字数量*/
	struct sockaddr_in servaddr[NUMBER];//多个服务器端网络地址结构体
	char buf[1024];
	int i,len;
	/*创建客户端套接字--IPv4协议,面向连接通信,TCP协议*/
	for(i=0;i<NUMBER;i++){
	    sockfd[i]=socket(AF_INET,SOCK_STREAM,0);
		if(sockfd[i]<0){
			perror("socket");
			return 1;
		}
	}
	int port=8000;
	/*填充NUMBER个地址结构*/
	for(i=0;i<NUMBER;i++,port++){
		memset(&servaddr[i],0,sizeof(servaddr[i])); //数据初始化--清零
	    servaddr[i].sin_family=AF_INET; //设置为IP通信
	    servaddr[i].sin_addr.s_addr=inet_addr("127.0.0.1");
	    servaddr[i].sin_port=htons(port); //与不同的服务器建立连接需要不同的端口号
	}
	/*建立NUMBER个连接*/
	for(i=0;i<NUMBER;i++){
		printf("connect to server[%d]\n",i);
		if(connect(sockfd[i],(struct sockaddr *)&servaddr[i],sizeof(struct sockaddr))<0){
			perror("connect");
			return 1;
		}	
	}
	i=0;
	int n=NUMBER;
	bzero(buf, 1024); 
	//itoa(num,buf,10);  /*将整型值转换为字符串。linux下没有这个函数*/
	while(n!=0){
		printf("Enter string[%d] to send: ",i);
	    scanf("%s",buf);
		len=send(sockfd[i],buf,strlen(buf),0);
		len=recv(sockfd[i],buf,BUFSIZ,0);
		if(i==0) /*这条语句只是为了两个服务端时,区别出一个做×,一个做+*/
		printf("received from server(*): %s\n",buf);
	    else if(i==1)
		printf("received from server(+): %s\n",buf);	
		bzero(buf, 1024); 
		i++;
		n--;
	}
	for(i=0;i<NUMBER;i++){close(sockfd[i]);}
}

服务端(*):(shiyan2.21.c)

int main(int argc, char *argv[])
{
	int server_sockfd;//服务器端套接字
	int client_sockfd;//客户端套接字
	int len,i;
	struct sockaddr_in my_addr;   //服务器网络地址结构体
	struct sockaddr_in remote_addr; //客户端网络地址结构体
	int sin_size;
	char buf[BUFSIZ];  //数据传送的缓冲区
	memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
	my_addr.sin_family=AF_INET; //设置为IP通信
	my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
	my_addr.sin_port=htons(8000); //服务器端口号
	/*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
	if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0){  
		perror("socket");
		return 1;
	}
        /*将套接字绑定到服务器的网络地址上*/
	if (bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){
		perror("bind");
		return 1;
	}
	/*监听连接请求--监听队列长度为5*/
	listen(server_sockfd,10);
	sin_size=sizeof(struct sockaddr_in);
	/*等待客户端连接请求到达*/
	if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0){
		perror("accept");
		return 1;
	}
	printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
	int num;
	/*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
	while((len=recv(client_sockfd,buf,BUFSIZ,0))>0){
		buf[len]='\0';
		num=atoi(buf);   /*将字符串转换为整型值。*/
		printf("translation: %d * %d = ",num,num);
		num=num*num;
		printf("%d\n",num);		
		sprintf(buf,"%d",num);
		//itoa(num,buf,10);  /*将整型值转换为字符串。linux下没有这个函数*/
		len=strlen(buf);		
		buf[len]='\0';
		printf("send answer: %s\n",buf);
		if(send(client_sockfd,buf,len,0)<0){
			perror("write");
			return 1;
		}
	}
	close(client_sockfd);
	close(server_sockfd);
        return 0;
}

服务端(+):(shiyan2.22.c)

int main(int argc, char *argv[]){
	int server_sockfd;//服务器端套接字
	int client_sockfd;//客户端套接字
	int len,i;
	struct sockaddr_in my_addr;   //服务器网络地址结构体
	struct sockaddr_in remote_addr; //客户端网络地址结构体
	int sin_size;
	char buf[BUFSIZ];  //数据传送的缓冲区
	memset(&my_addr,0,sizeof(my_addr)); //数据初始化--清零
	my_addr.sin_family=AF_INET; //设置为IP通信
	my_addr.sin_addr.s_addr=INADDR_ANY;//服务器IP地址--允许连接到所有本地地址上
	my_addr.sin_port=htons(8001); //服务器端口号
	/*创建服务器端套接字--IPv4协议,面向连接通信,TCP协议*/
	if((server_sockfd=socket(PF_INET,SOCK_STREAM,0))<0){  
		perror("socket");
		return 1;
	}
        /*将套接字绑定到服务器的网络地址上*/
	if(bind(server_sockfd,(struct sockaddr *)&my_addr,sizeof(struct sockaddr))<0){
		perror("bind");
		return 1;
	}
	/*监听连接请求--监听队列长度为5*/
	listen(server_sockfd,10);
	sin_size=sizeof(struct sockaddr_in);
	/*等待客户端连接请求到达*/
	if((client_sockfd=accept(server_sockfd,(struct sockaddr *)&remote_addr,&sin_size))<0){
		perror("accept");
		return 1;
	}
	printf("accept client %s\n",inet_ntoa(remote_addr.sin_addr));
	int num;
	/*接收客户端的数据并将其发送给客户端--recv返回接收到的字节数,send返回发送的字节数*/
	while((len=recv(client_sockfd,buf,BUFSIZ,0))>0){
		buf[len]='\0';
		num=atoi(buf);   /*将字符串转换为整型值。*/
		printf("translation: %d + %d = %d\n",num,num,num+num);
		num=num+num;
		sprintf(buf,"%d",num);
		//itoa(num,buf,10);  /*将整型值转换为字符串。linux下没有这个函数*/
		printf("send answer: %s\n",buf);
		if(send(client_sockfd,buf,len,0)<0){
			perror("write");
			return 1;
		}
	}
	close(client_sockfd);
	close(server_sockfd);
        return 0;
}

运行结果:
客户端:
在这里插入图片描述
服务端(*):
在这里插入图片描述
服务端(+):
在这里插入图片描述
上面所示的是一个客户端两个服务端的情况,如果要拓展为n个服务端的话,则只需把客户端程序中这条命令“#define NUMBER 2”中的2改成n就可以。

(3)多路复用
将上述(1)(2)功能实现,改用多路复用I/O来实现。客户端(标准输入/读文件,与网络数据到达进行多路复用)每读取一行便发送消息,服务器端(监听套接口与已连接套接口的复用)每次转换一行信息。

主要思路:一个客户端,两个服务端,两个服务端都使用多路复用I/O,一个对这个客户端送来的随机数相加,另一个相乘并返回。客户端每输入一行,服务器端就处理一行。(多路复用思想:当前进程可以处理多个响应事件,记录多个描述符,然后控制轮询时间态,当有响应产生的时候就去保存当前响应文件描述符,对他进行连接处理/数据传输。)
主要代码
客户端:(shiyan2.3c.c)
客户端的代码与上面(2)多连接编程的客户端代码类似,主要不同的地方是:

i=0;
while(n!=0){
		while(1)//这一步是为了让“客户端输入一行,服务器端转换一行信息”效果更明显
	    {
		    printf("Enter string to send:");
		    scanf("%s",buf);
	    	if(!strcmp(buf,"quit"))
			   break;
		    write(sockfd[i],buf,strlen(buf));
		if((read(sockfd[i],buf,1024))>0)
		        printf("received from server %d : %s\n",i,buf);
			
	    }
		i++;
		bzero(buf, 1024); 
		n--;
	}
(更改定义的NUMBER值也可以实现多个套接口向多个服务器请求服务)

服务端:(shiyan2.31.c)

#define _BACKLOG_ 5
#define rep(i,a,b) for(i=a;i<b;i++)
int fds[64]; //文件描述符集合
int i;
//命令行参数出错
static void usage(const char *proc){
	printf("usage : %s [ip][port]\n",proc);
}
int IsInt(char* str)
    {
        int len;
        len = strlen(str);
        int i=0;
        for(i;i<len;i++)
        {
            if(!(isdigit(str[i])))
                return 0;
        }
        return 1;
    }			
int main(int argc,char *argv[]){
	int port = atoi(argv[2]);
	char *ip = argv[1];
	int new_sock = -1;
	struct sockaddr_in client;
	socklen_t len = sizeof(client);
	int max_fd; //selet函数的第一个记录参数/
	//读写事件文件描述符
	fd_set reads;
	fd_set writes;
	int num,length,j;
	int fds_num = sizeof(fds)/sizeof(fds[0]);
	//判断命令参数行.
	if(argc != 3){
		usage(argv[0]);
		exit(1);
	}
	
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	if(sockfd < 0){
		perror("socket");
		exit(1);
	}

	struct sockaddr_in local;
	local.sin_family = AF_INET;
	local.sin_port = htons(port);  //8000
	local.sin_addr.s_addr = inet_addr(ip);

	int set = -1;
	if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &set, sizeof(set)) < 0){   
	   	perror("setsoickopt");
		exit(4);
	}
    /*绑定*/
	printf("begin blinding...\n");
	if(bind(sockfd,(struct sockaddr*)&local,sizeof(local)) < 0){
		perror("bind");
		exit(2);
	}
    /*监听*/
	printf("begin listing...\n");
	if(listen(sockfd,_BACKLOG_) < 0){
		perror("listen");
		exit(3);
	}
	
	
	//对文件描述符集合进行初始化
	rep(i,0,fds_num){
		fds[i] = -1;
	}
	fds[0] = sockfd;//当前存在listen文件描述符。
	max_fd = fds[0];	//初次调用select函数设置max_fd的值;
	while(1){
		FD_ZERO(&reads);//初始化
		FD_ZERO(&writes);
		//将sockfd设置为reads,因为时在等待请求相应,相当与当前的读取操作。
		FD_SET(sockfd,&reads);
		struct timeval timeout = {10,0};//设置多路复用中的轮寻时间表。
		
		//每次读取添加事件到reads中。;
		rep(i,1,fds_num){
			if(fds[i] >0){
				FD_SET(fds[i],&reads);
				if(fds[i] > max_fd){  //获取最大的文件描述符值
					max_fd = fds[i];
				}
			}
		}

		switch(select(max_fd+1, &reads,&writes,NULL,&timeout)){
			case 0 ://timeout
				{
					printf("select timeout\n");
					break;}
			case -1:
				{	//error	
					perror("select");
					break;}
			default://返回改变了的文件描述.
				{	
					char buf[1024];
					//遍历所有的文件描述符集合。
					rep(i,0,fds_num){
						//确认是否时监听时间,是的话就绪要accept;
						if(fds[i] == sockfd && \
								FD_ISSET(fds[i],&reads)){
							printf("begin accepting...\n");
							new_sock = accept(sockfd,(struct sockaddr*)&client,&len);
							if(new_sock <0){
								perror("accept");
								continue;
							}
							printf("get a new connet...%d\n",new_sock);
							rep(i,0,fds_num){
								if(fds[i] == -1){
									fds[i] = new_sock;
									break;
								}
							}
							if(i == fds_num){
								close(new_sock);
							}
						}
						else if(fds[i] > 0 &&\
								FD_ISSET(fds[i],&reads)){	//正常事件,但是是非监听时间,也就代表时新建立的new_sock。
									ssize_t s = read(fds[i],buf,sizeof(buf) -1);
									if(s > 0){
										buf[s] = '\0';
										printf("receive data from client : %s\n",buf);
										FD_SET(fds[i],&writes);
									}
									else if(s == 0){
										printf("client quit...\n");
										sleep(5);
										close(fds[i]);
										fds[i] = -1;
									}
									else{}
								}
						else{}
						if(fds[i] > 0&&\
								FD_ISSET(fds[i],&writes)){
							if(IsInt(buf)){
									num=atoi(buf);   /*将字符串转换为整型值。*/
									printf("translation: %d * %d = ",num,num);
									num=num*num;
									printf("%d\n",num);	
									sprintf(buf,"%d",num);
									printf("send answer: %s\n",buf);
							}
							else if(!IsInt(buf)){
									int len=strlen(buf);
								    for (j = 0; j < len; j++)
			                          buf[j] = toupper(buf[j]);
								    printf("send answer(upper): %s\n",buf);
								}
		                        write(fds[i],buf,sizeof(buf));
						}
					}
				}
		break;
		}
	}
	return 0;
}

运行结果:
客户端:
在这里插入图片描述
分析:客户端可以向一个服务器端连续发送数据,每发送一行,就收到一行(服务器端处理后发回的),输入quit即可退出,然后就可以向另一个服务器端发送数据(这里是同一个服务器,客户端通过不同的套接口与同一个服务器通信)。

服务端:
在这里插入图片描述
分析:服务器端接受一行客户端发来的数据就处理一行并返回。这里的服务器提供两个服务,客户端发来的如果是数字,则计算这个数字自身相乘的结果并返回,如果是字符串则将其转为大写并返回。

2.采用netstat命令,以及sleep系统调用,验证TCP连接建立的三次握手各种状态和终止的几种状态。
监听状态,已连接状态,半关闭状态,完全关闭状态等。
(此处观察的是(3)多路复用中的TCP连接建立过程,下面只列出其中一个服务器(端口号:8000))
(1)LISTEN(监听状态):服务器端先开启,客户端还没开启的时候,服务器端是监听状态。
在这里插入图片描述
(2)ESTABLISHED(已连接状态):当客户端与服务器端完成三次握手,成功建立连接,客户端和服务器端都是已连接状态,双方就可以发送数据进行通信了。
客户端:
在这里插入图片描述
服务器端:
在这里插入图片描述
在这里插入图片描述
分析:客户端调用connect函数请求TCP连接时,系统会自动为它选择一个未用端口号,并用本地的IP地址设置套接字地址的相应项,如上图可知,系统给这个客户端选择了50488端口。(grep 指定端口8000)
netstat扫描的结果:可以看到服务器端由监听状态变为已连接状态。
(3)FIN_WAIT2:主动方(主动关闭的一方)接收到对方的ACK,将状态转入FIN_WAIT2。
在这里插入图片描述
分析:上图是服务器端先发起断开连接请求。主动方(服务器端)发起断开连接请求等待对方确认,将自身的状态转入FIN-WAIT1。被动方(客户端)收到FIN报文,回复ACK,将状态转为CLOSE_WAIT。主动方(服务器端)接收到对方的ACK,将状态转入FIN_WAIT2。
(4)CLOSE_WAIT:被动方收到FIN报文,回复ACK,将状态转为CLOSE_WAIT。如上图,客户端收到服务器端的断开连接请求。
(5)TIME_WAIT:(客户端先发起断开连接的情况)客户端收到了服务器端的FIN,将tcp连接状态转为TIME_WAIT。
在这里插入图片描述
因为感觉看到的状态不是很多,于是我用tcpdump再观察了一下:
客户端与两个服务器端的TCP连接过程:
在这里插入图片描述
客户端先发起断开连接:
3.捕获各个TCP连接传输数据过程中的各种信息
采用winpcap和ethereal软件进行捕获,并记录和解释捕获得到的信息
由于我在ethereal里没找到本机虚拟机地址,所以我是采用wireshark软件对(1)基本通信编程中的客户端和服务器端之间的通信进行捕获,如下:
在这里插入图片描述
我们可以从中很清楚的看到客户端与服务器端之间的通信。客户端端口号为58952,服务器端端口号为8000。
(1)建立连接(三次握手)
1)58952->8000 [SYN](报文编号No.3):客户端向服务器端发送同步请求SYN,附序列号Seq=0,并进入SYN_SENT状态,等待服务器确认。
2)8000->58952 [SYN,ACK](No.4):服务器收到SYN包,必须确认客户的SYN(ACK=1),同时自己也发送一个SYN包(Seq=0),询问客户端是否准备好进行数据通信,即SYN+ACK包,此时服务器进入SYN_RECV状态;
3)58952->8000 [ACK](No.5):客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
完成三次握手,客户端与服务器开始传送数据。(根据代码我们也可以知道如下:)
4)8000->58952 [PSH,ACK](No.6):服务器端像客户端发送欢迎消息“welcome to my server”
我们也可以从捕获的报文中看到:
在这里插入图片描述
5)58952->8000 [PSH,ACK](No.8):客户端向服务器端发送test0.txt文件里的内容(小写字符串
6)8000->58952 [PSH,ACK](No.10):服务器端向客户端发送转换后的字符串
(2)连接终止
1)58952->8000 [FIN,ACK](No.11):客户端调用close,主动关闭,发送FIN,表示数据发送完毕,并且进入FIN_WAIT状态。(此处有个ACK是对上条服务器发来的数据的应答)
2)8000->58952 [ACK](No.12):服务器收到FIN,被动关闭,返回ACK,进入CLOSE_WAIT状态。
3)58952->8000 [FIN](No.13):服务器调用close关闭它的套接口,并发送一个FIN。
4)8000->58952 [ACK](No.14):客户端收到FIN并确认,发送ACK。

这个实验做得不是很好,因为是第一次做,所以在思考问题方面还有所欠缺。再后来也有发现更好的做法,但没去修改代码,如果读者有发现什么问题,欢迎提出。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值