Linux--->网络编程(TCP并发服务器构建:[ 多进程、多线程、select ])

TCP并发服务器构建

一、服务器

单循环服务器:服务端同一时刻只能处理一个客户端的任务(TCP)

并发服务器:服务端同一时刻可以处理多个客户端的任务(UDP)

二、TCP服务端并发模型

1、多进程

进程资源开销大,安全性高。

以下是一个实现多进程TCP服务端代码:

cs 复制代码
#define SER_PORT 62300
#define SER_IP "192.168.0.165"

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return 0;
    }
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SER_PORT);
    seraddr.sin_addr.s_addr = inet_addr(SER_IP);
    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        printf("bind error");
        return -1;
    }
    int cnt = listen(sockfd, 100);
    if(cnt < 0)
    {
        perror("listen error");
        return -1;
    }
    return sockfd;
}

void wait_handler(int signo)
{
    wait(NULL);
}

int main(int argc, char *argv[])
{
    signal(SIGCHLD, wait_handler);
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        perror("init_tcp_ser error");
        return -1;
    }
    while(1)
    {
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen); 
        if(connfd < 0)
        {
            perror("accept error");
            return -1;
        }
        pid_t pid = fork();
        if(pid > 0)
        {
        }
        else if(pid == 0)
        {
            char buff[1024] = {0};
            while(1)
            {
                memset(buff, 0, sizeof(buff));
                ssize_t ret = recv(connfd, buff, sizeof(buff), 0);
                if(ret < 0)
                {
                    perror("send error");
                    return -1;
                }
                else if(ret == 0)
                {
                    printf("[%s : %d]: offline!\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
                    break;
                }
                printf("[%s : %d]: %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);
                strcat(buff, "----OK");
                ssize_t cnt = send(connfd, buff, strlen(buff), 0);
                if(cnt < 0)
                {
                    perror("send error");
                    break;
                }
            }   
            close(connfd);
        }
    }
    close(sockfd);
    return 0;
}

2、多线程

线程相对与进程资源开销小,相同资源环境下,并发量比进程大。

以下是一个实现多线程TCP服务端代码:

cs 复制代码
#include "head.h"

#define SER_PORT 62300
#define SER_IP "192.168.0.165"

int init_tcp_ser()
{
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        perror("socket error");
        return 0;
    }
    //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:
	int optval = 1;
	setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(SER_PORT);
    seraddr.sin_addr.s_addr = inet_addr(SER_IP);
    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if(ret < 0)
    {
        printf("bind error");
        return -1;
    }
    int cnt = listen(sockfd, 100);
    if(cnt < 0)
    {
        perror("listen error");
        return -1;
    }
    return sockfd;
}

void *task(void *sug)
{
    int connfd = *(int *)sug;
    char buff[1024] = {0};
    while(1)
    {
        memset(buff, 0, sizeof(buff));
        ssize_t ret = recv(connfd, buff, sizeof(buff), 0);
        if(ret < 0)
        {
            perror("send error");
            break;
        }
        else if(ret == 0)
        {
            printf("[%s : %d]: offline!\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
            break;
        }
        printf("[%s : %d]: %s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);
        strcat(buff, "----OK");
        ssize_t cnt = send(connfd, buff, strlen(buff), 0);
        if(cnt < 0)
        {
            perror("send error");
            break;
        }
    }   
    close(connfd);
}

int main(int argc, char *argv[])
{
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);

    int sockfd = init_tcp_ser();
    if(sockfd < 0)
    {
        perror("init_tcp_ser error");
        return -1;
    }
    while(1)
    {
        int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen); 
        if(connfd < 0)
        {
            perror("accept error");
            return -1;
        }
        pthread_t tid;
        int fd = pthread_create(&tid, NULL, task, &connfd);
        if(fd < 0)
        {
            perror("thread create error");
            return -1;
        }  
        pthread_detach(tid);
    }
    close(sockfd);
    return 0;
}

3、线程池

为了解决多线程或者多进程模型,在服务器运行过程,频繁创建和销毁线程(进程)带来的时间消耗问题。

基于生产者和消费者编程模型,以及任务队列等,实现的一套多线程框架。

4. IO多路复用

1)概念

对多个文件描述符的读写可以复用一个进程。

在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时监测。

阻塞IO模式:

2)方式

实现方式有 select、poll、epoll 三种。

5、select 实现IO多路复用

1)实现步骤

(1)创建文件描述符集合 fd_set

(2)添加关注的文件描述符到集合 FD_SET

(3)使用 select 传递集合表给内核,内核开始监测事件 select()

(4)当内核监测到事件时,应用层select将解除阻塞,并获得相关的事件结果

(5)根据 select 返回的结果做不同的任务处理

2)select 的宏

将集合清0:void FD_ZERO(fd_set *set);

向集合中添加文件描述符:void FD_SET(int fd, fd_set *set);

从集合中移除文件描述符:void FD_CLR(int fd, fd_set *set);

检查文件描述符是否在集合中:int FD_ISSET(int fd, fd_set *set);

3)select() 函数接口

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); |
| 功能:传递文件描述符结合表给内核并等待获取事件结果 参数: nfds : 关注的最大文件描述符+1 readfds:读事件的文件描述符集合 writefds:写事件的文件描述符集合 exceptfds:其他事件的文件描述符集合 timeout:设置select监测时的超时时间 若为NULL : 不设置超时时间(select一直阻塞等待) 返回值: 成功:返回内核监测的到达事件的个数 失败:-1 0 : 超时时间到达,但没有事件发生,则返回0 |

4)应用示例

(1)通过select函数构建多路复用,实现管道和终端的读

管道向服务端写的代码:

cs 复制代码
int main(void)
{
    mkfifo("./myfifo", 0664);
    int fd = open("./myfifo", O_WRONLY);
    if(fd < 0)
    {
        perror("open error");
        return -1;
    }
    while(1)
    {
        write(fd, "hello world", 11);
        sleep(1);
    }
    close(fd);
    return 0;
}

读的代码:

cs 复制代码
int main(void)
{
    char buff[1024] = {0};
    mkfifo("./myfifo", 0664);

    int fifofd = open("./myfifo", O_RDONLY);
    if(fifofd < 0)
    {
        perror("open error");
        return -1;
    }

    //1、创建文件描述符集合
    fd_set rdfds;
    fd_set rdfdstmp;

    //2、清空文件描述符集合表
    FD_ZERO(&rdfds);//初始化...集
    //3、添加关注的文件描述符到集合中
    FD_SET(0, &rdfds);//添加
    int maxfd = 0;
    FD_SET(fifofd, &rdfds);
    maxfd = maxfd > fifofd ? maxfd : fifofd;

    while(1)
    {
        rdfdstmp = rdfds;
        //4、传递集合表给内核并等待返回到达事件的结果
        int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
        if(cnt < 0)
        {
            perror("select error");
            return -1;
        }

        if(FD_ISSET(0, &rdfdstmp))//检查文件描述符0是否在集合中
        {
            fgets(buff, sizeof(buff), stdin);//0
            printf("STDIN: %s\n", buff);
        }
        if(FD_ISSET(fifofd, &rdfdstmp))
        {
            memset(buff, 0, sizeof(buff));
            read(fifofd, buff, sizeof(buff));
            printf("FIFO: %s\n", buff);
        }
    }
    close(fifofd);
    return 0;
}

(2)使用select实现TCP服务端IO多路复用代码

cs 复制代码
#define SER_PORT  50000
#define SER_IP    "192.168.0.165"

int init_tcp_ser()
{
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);
	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	ret = listen(sockfd, 100);
	if (ret < 0)
	{
		perror("listen error");
		return -1;
	}
	return sockfd;
}

int main(int argc, const char *argv[])
{
	char buff[1024] = {0};
	struct sockaddr_in cliaddr;
	socklen_t clilen = sizeof(cliaddr);

	int sockfd = init_tcp_ser();
	if (sockfd < 0)
	{
		return -1;
	}

	//1. 创建文件描述符集合
	fd_set rdfds;
	fd_set rdfdstmp;
	FD_ZERO(&rdfds);
	
	//2. 添加关注的文件描述符到集合
	FD_SET(sockfd, &rdfds);
	int maxfd = sockfd;

	while (1)
	{
		rdfdstmp = rdfds;
		//3. 传递集合到内核,并等待返回监测结果
		int cnt = select(maxfd+1, &rdfdstmp, NULL, NULL, NULL);
		if (cnt < 0)
		{
			perror("select error");
			return -1;
		}
		//4. 是否有监听套接字事件到达 ----》三次握手已完成,可以accept
		if (FD_ISSET(sockfd, &rdfdstmp))
		{
			int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
			if (connfd < 0)
			{
				perror("accept error");
				return -1;
			}
			FD_SET(connfd, &rdfds);
			maxfd = maxfd > connfd ? maxfd : connfd;
		}
		//5. 是否有通讯套接字事件到达
		for (int i = sockfd+1; i <= maxfd; i++)
		{
			if (FD_ISSET(i, &rdfdstmp))
			{
				memset(buff, 0, sizeof(buff));
				ssize_t cnt = recv(i, buff, sizeof(buff), 0);
				if (cnt < 0)
				{
					perror("recv error");
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
				else if (0 == cnt)
				{
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
				printf("%s\n", buff);
				strcat(buff, "--->ok");
				cnt = send(i, buff, strlen(buff), 0);
				if (cnt < 0)
				{
					perror("send error");
					FD_CLR(i, &rdfds);
					close(i);
					continue;
				}
			}
		}	
	}
	close(sockfd);
	return 0;
}

【END】

相关推荐
TH_118 小时前
腾讯云-(3)-Linux宝塔面板使用
linux·云计算·腾讯云
虚伪的空想家19 小时前
arm架构服务器使用kvm创建虚机报错,romfile “efi-virtio.rom“ is empty
linux·运维·服务器·javascript·arm开发·云原生·kvm
火车头-11019 小时前
【docker 部署nacos1.4.7】
运维·docker·容器
深藏bIue19 小时前
linux服务器mysql目录下的binlog文件删除
linux·服务器·mysql
虾..19 小时前
Linux 进程状态
linux·运维·服务器
测试者家园19 小时前
DevOps 到底改变了测试什么?
运维·自动化测试·软件测试·devops·持续测试·智能化测试·软件测试和开发
扛枪的书生20 小时前
Linux 通用软件包 AppImage 打包详解
linux
只想安静的写会代码20 小时前
网卡信息查询、配置、常见故障排查
linux·服务器·windows
jiayong2321 小时前
多子系统架构下的Nginx部署策略与最佳实践
运维·nginx·系统架构
皮糖小王子21 小时前
Docker打开本地镜像
运维·docker·容器