tcp三次握手-并发

本文深入解析TCP协议的三次握手和四次挥手过程,详述滑动窗口机制在流量控制中的作用,以及多进程和多线程并发服务器的实现方式。

1. tcp三次握手

1.2 三次握手的过程

  1. tcp协议是面向连接的、安全的流式传输协议;
  2. 在数据传输前,必须保证通信双方建立起了连接;
  3. 握手是协议的行为,在connect()时进行,无需编程实现;在这里插入图片描述
  4. 标志位含义
    (1)SYN: 请求建立连接
    (2)ACK: 同意
    (3)FIN: 断开连接的请求

1.2 tcp协议中的序号

在这里插入图片描述

  1. 第一次握手:
    (1)客户端将SYN置1,发出连接请求;
    (2)客户端随机生成一个32bit的序号seq/Seq;
    (3)这个序号后可以携带数据。
  2. 第二次握手:
    (1)服务器同意请求ACK置1;
    (2)服务器回发一个确认序号: ack/Ack=客户端随机序号+数据长度+SYN标志位;
    (3)服务器发出连接请求:SYN置1;
    (4)服务器会生成一个随机序号: seq=k;
  3. 第三次握手:
    (1)客户端同意请求ACK置1;
    (2)客户端回发一个确认序号: ack/Ack=服务器随机序号+数据长度+SYN标志位;在这里插入图片描述

2. 滑动窗口

  1. 滑动窗口是TCP中用于实现如 ACK 确认、流量控制、拥塞控制的承载结构。可以理解为一块缓存。
  2. 通信的双方都有滑动窗口;
    (1)服务器:发送数据的窗口,接收数据的窗口;
    (2)客户端:发送数据的窗口,接收数据的窗口;
  3. 滑动窗口的内存是在变化的:其空间大小固定,但可存储的数据量在变化;其取决于通信对方可以接收的数据量。在这里插入图片描述
  4. 使用流程在这里插入图片描述
    (1) mss: 最大的数据段大小 Maximum Segment Size -> 一条数据的最大数据量;
    (2)win:滑动窗口。
1. 客户端向服务器发起连接, 客户端的滑动窗口大小为4096, 一次发送的最大数据量1460
2. 服务器接收连接请求(第二次握手), 告诉客户端服务器的滑动窗口大小为6144, 一次发送的最大数据量1024
3. 第三次握手
4. 4-9 客户端连续给服务器发送了 6k数据, 每次发送1k
5. 第10次, 服务器告诉客户端: 发送的6k数据已经收到存储到滑动窗口缓存中, 缓存数据已经处理了2k
6. 第11次, 服务器告诉客户端: 发送的6k数据已经收到存储到滑动窗口缓存中, 缓存数据已经处理了4k
7. 第12次, 客户端给服务器发送1k数据

3. 四次挥手

在这里插入图片描述

  1. 四次挥手发生在断开连接时,即调用close()函数时;
  2. 客户端和服务器端谁都可以发起挥手的动作, 谁先调用close()函数就是谁发起的;
  3. 因为tcp在连接的时候, 建立的是双向连接, 因此在断开的时候需要双向断开;
1. 第13次, 主动请求和服务器断开连接, 并且给服务器发送了1k数据
2. 第14次, 服务器回复ACK8194, a: 同意断开连接的请求 b: 告诉客户端我已经收到刚才2k数据 c: 滑动窗口2k
3. 第15, 16次, 通知客户端滑动窗口中的数据处理完了
4. 第17次, 服务器给客户端发送FIN, 断开和客户端的连接
5. 第18次, 客户端同意了服务器的断开请求

4. 并发服务器

  1. 多进程并发服务器

#include <sys/socket.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>       
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

//信号处理函数: 回收子进程资源
void handler(int sig)
{
	while(1)
	{
		pid_t pid = waitpid(-1, NULL, WNOHANG);
		if(pid == -1)
		{
			printf("所有子进程都已被回收\n");
			break;
		}
		else if(pid == 0)
		{
			printf("还有子进程在运行\n");
			break;
		}
		else
		{
			printf("子进程 %d 被回收\n", pid);
		}
	}
}


int main()
{
	//1.套接字
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd == -1)
	{
		perror("socket");
		exit(0);
	}
	
	//2.绑定
	struct sockaddr_in addr =
	{
		.sin_family = AF_INET, 
		.sin_port =  htons(9000), 
    };
	inet_pton(AF_INET, "127.0.0.1", (void *)&addr.sin_addr.s_addr);
	int ret = bind(lfd, (const struct sockaddr *)&addr, sizeof(addr));
	
	//3.设置监听
	ret = listen(lfd, 120);
	if(ret == -1)
	{
		perror("listen");
		exit(0);
	}
	
	//设置信号捕捉
	struct sigaction sig = 
	{
		.sa_handler = handler,
		sigemptyset(&sig.sa_mask),
		.sa_flags = 0,
	};
	sigaction(SIGCHLD, &sig, NULL);
	
	//4.连接
	printf("等待客户端连接...\n");
	while(1)
	{
		//4.1等待连接
		struct sockaddr_in caddr;
		int addrlen = sizeof(caddr);
		int cfd = accept(lfd, (struct sockaddr *)&caddr, &addrlen);
		if(cfd == -1)
		{
			if(errno == EINTR)
			{
				continue;
			}
			else
			{
				perror("accept");
				exit(0);
			}
		}
		
		//4.2打印客户端信息
		char ip[32] = {0};
		inet_ntop(AF_INET, (void *)&caddr.sin_addr.s_addr, ip, sizeof(ip));
		printf("IP:%s\tport: %d 连接成功!\n", ip, ntohs(caddr.sin_port));

		
	//5.通信
		//5.1创建子进程
		pid_t pid = fork();
		if(pid == 0)
		{
		//5.2通信
			
			while(1)
			{
				//接收数据
				char buf[1024] = {0};
				ret = read(cfd, buf, sizeof(buf));
				if(ret == 0)
				{
					printf("IP:%s\tport: %d 断开连接!\n", ip, ntohs(caddr.sin_port));
					break;
				}
				else if(ret == -1)
				{
					printf("读取失败\n");
					break;
				}
				else
				{
					//打印接收的数据
					printf("%s: %s\n",ip, buf);
					write(cfd, buf, strlen(buf)+1);
				}
			}
		//5.3回收结束子进程
			close(cfd);
			exit(0);
		}
	}
	
	//6.断开通信
	close(lfd);

	return 0;
}

  1. 多线程并发服务器
#include <sys/types.h>        
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>

//参数结构体
struct args
{
	struct sockaddr_in addr;
	int connfd;
};
args arg[1024];
//子线程通信
void* working(void * arg)
{
	char buf[1024] = {0};
	struct args *params = (struct args *)arg;
	while(1)
	{
		//读操作
		int ret = read = (params->connfd, buf, sizeof(buf));
		if(ret == -1)
		{
			printf("读取失败\n");
			return NULL;
		}
		else if(ret == 0)
		{
			printf("客户端断开连接\n");
			return NULL;
		}
		else
		{
			printf("%s\n", buf);
		}
		//写操作
		write(params->connfd, buf, strlen(buf));
	}
	return NULL;
}
	
int main()
{
	//1.套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("sockfd");
		exit(0);
	}
	
	//2.绑定
		//2.1建立sockaddr结构体
	struct sockaddr_in addr = 
	{
		.sin_family = AF_INET,
		.sin_port = htons(9000),
	};
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
		//2.2将sockaddr与sockfd绑定
	int ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
	if(ret == -1)
	{
		perror("bind");
		exit(0);
	}
	
	//3.监听
	ret = listen(sockfd, 100);
	if(ret == -1)
	{
		perror("listen");
		exit(0);
	}
	
	//4.等待
	int i = 0;
	while(1)
	{
		//4.1接收连接请求
		struct sockaddr_in acceptAddr;
		int len = sizeof(acceptAddr);
		printf("等待客户端连接...\n");
		
		int connfd = accept(sockfd, (struct sockaddr *)&acceptAddr, &len);
		if(connfd == -1)
		{
			perror("accept");
			exit(0);
		}
		//4.2将子线程参数打包
		arg[i].connfd = connfd;
		memcpy(&arg[i].addr, &acceptAddr, len)//4.3创建子线程
		pthread_t tid;
		pthread_create(&tid, NULL, working, &arg[i]);
		//4.4分离子线程
		pthread_detach(arg[i].tid);	
		i++
	}

	//6.关闭
	close(sockfd);
	close(connfd);
	
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值