【TCP】TCP服务器

1. 并发服务器

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

2. IO多路转换

多线/进程并发服务器分析

  1. 单线/进程服务器无法连接多客户端
    (1)当服务器与某一客户端通信时,若有其他客户端发送连接请求,服务器会阻塞在read()函数上而无法执行accept()函数,也就无法建立新的连接;
    (2)当服务器等待服务器连接时,就会阻塞在accept()函数,从而无法执行read()函数与其他已连接的客户端进行通信;
  2. 多线/进程并发服务器
    (1)当有客户端连接服务器时,服务器的主线/进程继续监测客户端的连接,并创建子线/进程来与客户端通信;
    (2)此时,主线/进程只负责检测客户端一直循环阻塞在accept()函数,而子线/进程只负责通信一直阻塞在read()函数,相互之间不影响;
  3. 客户端的检测与通信
    (1)实际上是通过实时的读取特定的内核缓存区是否有数据,来判断是否有请求和通信数据。:监测与通信的缓存区不同,因此socked()accept()生成的fd不一样
    (2)因此可以设置所有的阻塞状态委托内核判断, 得到内核回复之后进行后续处理。
  4. 单线/进程服务器
    在这里插入图片描述
    (1)将要监测的内核缓存区设置到自定义的文件描述符表中,并通过API调用给内核;
    (2)内核通过遍历文件描述符表,得到要监测的内核缓冲区,并将监测的结果写到自定义的文件描述符表;
    (3)自定义的表中,共1024bit即128Byte,读:1监测,0不监测;写:1未满可写,1已满不可泄;异常:1异常,0无异常;
// 相关函数
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
// sizeof(fd_set) = 128
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:
- nfds: 委托内核检测的最大文件描述符的值 + 1
- readfds: 读集合, 委托内核检测哪些文件描述符的读属性,这是一个传入传出的参数
- writefds: 写集合, 委托内核检测哪些文件描述符的写属性,这是一个传入传出的参数
- exceptfds: 异常集合, 委托内核检测哪些文件描述符出现了异常
- timeout: 阻塞对应的时间长度
struct timeval {
	long   tv_sec;     /* seconds */
	long   tv_usec;     /* microseconds */
};
- NULL: 永久阻塞, 直到检测到了文件描述符有变化
- tv_sec = 0, tv_usec = 0, 不阻塞
- tv_sec > 0 || tv_usec > 0, 阻塞对应的时间长度
返回值:
-1: 失败
>0(n): 检测的集合中有n个文件描述符发送的变化
将参数文件描述符fd对应的标志位, 设置为0
void FD_CLR(int fd, fd_set *set);
判断fd对应的标志位到底是0还是1, 返回值: fd对应的标志位的值, 0, 返回0, 1->返回1
int  FD_ISSET(int fd, fd_set *set);
将参数文件描述符fd对应的标志位, 设置为1
void FD_SET(int fd, fd_set *set);
fd_set 共有1024bit, 全部初始化为0
void FD_ZERO(fd_set *set);

示例

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


int main()
{
	//1.套接字
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd == -1)
	{
		perror("lfd");
		exit(0);
	}
	
	//2.绑定
		//2.1建立sockaddr结构体
	struct sockaddr_in addr = 
	{
		.sin_family = AF_INET,
		.sin_port = htons(10000),
	};
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
		//2.2将sockaddr与lfd绑定
	int ret = bind(lfd, (struct sockaddr *)&addr, sizeof(addr));
	if(ret == -1)
	{
		perror("bind");
		exit(0);
	}
	
	//3.设置监听
	ret = listen(lfd, 100);
	if(ret == -1)
	{
		perror("listen");
		exit(0);
	}
	
	//4.内核监听
		//4.1自定义文件描述符集
	fd_set rdset, tmp;							//定义	
	FD_ZERO(&rdset);							//初始化
	FD_SET(lfd, &rdset);						//监听套接字
	int maxfd = lfd + 1;
	while(1)
	{
		//4.2传输给内核
		tmp = rdset;
		select(maxfd + 1, &tmp, NULL, NULL, NULL);
	//5.监测套接字
		//5.1若有客户端连接
		if(FD_ISSET(lfd, &tmp))
		{
			struct sockaddr_in acceptAddr;		//创建通信套接字
			int len = sizeof(acceptAddr);
			int cfd = accept(lfd, (struct sockaddr *)&acceptAddr, &len);
			if(cfd == -1)
			{
				perror("accept");
				exit(0);
			}
		//5.2监测通信
			maxfd = maxfd > cfd? maxfd : cfd;
			FD_SET(cfd, &rdset);
		}
	//6.传输
		for(int i = lfd + 1; i <= maxfd; i++)
		{
			//6.1若文件描述符集中有1,通信
			if(FD_ISSET(i, &tmp))
			{
				//读数据
				char buf[1024] = {0};
				int ret = read(i, buf, sizeof(buf));
				if(ret == -1)
				{
					perror("read");
					exit(0);
				}
				else if(ret == 0)
				{
					printf("断开连接\n");
					close(i);
					FD_CLR(i, &rdset);
				}
				else
				{
					printf("%s\n", buf);
					write(i, buf, strlen(buf)+1);
				}
			}		
		}
	}
	close(lfd);		
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值