2024.12.29(进程线程实现并发服务器)

作业

多进程多线程并发服务器实现一遍提交。

服务器

复制代码
#include <myhead.h>
#define PORT 12345
#define IP "192.168.124.123"

void *fun(void *fd)
{
	int newfd = *(int *)fd;
	char buff[1024];
	while(1)
	{
		int res = recv(newfd,buff,sizeof(buff),0);
		if(res == 0)
		{
			printf("当前客户端已经退出\n");
			break;
		}
		printf("%s\n",buff);
		strcat(buff,"作业写完了");
		send(newfd,buff,sizeof(buff),0);
	}
	pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
	//创建套接字
	int oldfd = socket(AF_INET,SOCK_STREAM,0);
	if(oldfd == -1)
	{
		perror("socket");
		return -1;
	}
	//绑定
	struct sockaddr_in server = {
		.sin_family = AF_INET,
		.sin_port = htons(PORT),
		.sin_addr.s_addr = inet_addr(IP)
	};
	if(bind(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
	{
		perror("bind");
		return -1;
	}

	//监听
	if(listen(oldfd,22)==-1)
	{
		perror("listen");
		return -1;
	}

	struct sockaddr_in client;
	int client_len = sizeof(client);
	int newfd;
	pthread_t tid;
	while(1)
	{
		//接收新客户端连入请求
		newfd = accept(oldfd,(struct sockaddr *)&client,&client_len);
		if(newfd == -1)
		{
			perror("accept");
			return -1;
		}
		//创建子线程与客户端通话
		if(pthread_create(&tid,NULL,fun,&newfd)==-1)
		{
			perror("pthread_create");
			return -1;
		}
	}
	pthread_join(tid,NULL);
	close(newfd);
	close(oldfd);
	return 0;
}

客户端

复制代码
#include <myhead.h>
#define PORT 12345
#define IP "192.168.124.123"
int main(int argc, const char *argv[])
{
	//创建套接字
	int oldfd = socket(AF_INET,SOCK_STREAM,0);
	if(oldfd == -1)
	{
		perror("socket");
		return -1;
	}

	//连接服务器
	struct sockaddr_in server = {
		.sin_family = AF_INET,
		.sin_port = htons(PORT),
		.sin_addr.s_addr = inet_addr(IP)
	};
	if(connect(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
	{
		perror("connect");
		return -1;
	}
	//收发消息
	char buff[1024];
	while(1)
	{
		fgets(buff,sizeof(buff),stdin);
		buff[strlen(buff)-1] = '\0';
		send(oldfd,buff,strlen(buff),0);
		if(strcmp(buff,"quit")==0)
		{
			break;
		}
		bzero(buff,sizeof(buff));
		recv(oldfd,buff,sizeof(buff),0);
		printf("服务器发来消息:%s\n",buff);
	}
	return 0;
}

学习笔记

端口号快速复用函数

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);

功能: 获取套接字或者其他层级的属性

参数1:套接字描述符

参数2:要获取的层级

参数3:操作名称每一层级名称都不一样,具体见下表。

参数4:变量的地址(表中的数据类型定义的变量)

参数5:参数4 的大小。

返回值:成功返回0,失败返回-1,并置位错误码

int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

功能:设置套接字或者其他层级的属性

参数1:套接字描述符

参数2:要获取的层级

参数3:操作名称每一层级名称都不一样,具体见下表。

参数4:变量的地址(表中的数据类型定义的变量)

参数5:参数4 的大小。

返回值:成功返回0,失败返回-1,并置位错误码

函数使用:

复制代码
#include <myhead.h>

int main(int argc, const char *argv[])
{
    int oldfd= socket(AF_INET,SOCK_STREAM,0);
    if(oldfd==-1)
    {
        return -1;
    }

    //获取端口号快速复用的属性(默认关闭)
    int k;
    int k_len = sizeof(int);
    if(getsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&k,&k_len)==-1)
    {
        perror("getsockopt");
        return -1;
    }
    printf("k = %d\n",k);//k=0

    //设置开启端口号快速复用属性(k!=0即可)
    k = 2;
    if(setsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&k,sizeof(k))==-1)
    {
        perror("setsockopt");
        return -1;
    }
    printf("开启端口号快速复用功能\n");
    
    
    return 0;
}

1、循环服务器模型

创建套接字

绑定

监听

循环:

创建新的用于通讯的套接字

收消息

发消息

关闭新的套接字

关闭旧的套接字

代码:

缺点:新客户端要通信,必须使旧的客户端先退出。

复制代码
#include <myhead.h>
#define IP "192.168.124.34"
#define PORT 6666
int main(int argc, const char *argv[])
{
    //1、创建套接字
    //2、绑定
    //3、监听
    
    //4、循环连接不同客户端
    //5、循环收发信息
    //1创建套接字    
    int oldfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1==oldfd)
    {
        perror("socket");
        return -1;
    }
    //2、绑定
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = inet_addr(IP)
    };
    
    if(bind(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
    {
        perror("bind");
        return -1;
    }
    //3、监听
    if(listen(oldfd,20)==-1)
    {
        perror("listen");
        return -1;
    }
    //4、连接
    int newfd;
    struct sockaddr_in client;
    int client_len = sizeof(client);
    while(1)
    {
        if((newfd = accept(oldfd,(struct sockaddr *)&client,&client_len))==-1)
        {
            perror("accept");
            return -1;
        }
        //5、信息收发
        char buff[1024];
        while(1)
        {
            int res = recv(newfd,buff,sizeof(buff),0);
            if(res==0)
            {
                printf("您的客户端已经下线\n");
                break;
            }
            printf("%s\n",buff);
            strcat(buff,"中午吃啥");
            send(newfd,buff,sizeof(buff),0);
        }
    }
    close(oldfd);
    close(newfd);
    return 0;
}

2、基于TCP并发服务器,目前有多进程和多线程并发。

2.1、多进程并发服务器,父进程只负责处理不同客户端的链接请求,每一个客户端请求连接就创建出一个子进程进行处理通话。

多进程服务器模型

1、子进程在哪创建

2、子进程怎么回收

3、终端打开的文件描述符有限。

1、多进程并发执行

模型:

定义信号处理函数,非阻塞回收僵尸进程。

绑定子进程退出时的信号。

1、创建套接字

2、绑定

3、监听

4、循环接收客户端信息

5、让父进程接收客户端请求并关闭新文件描述符,子进程关闭旧的描述符只负责数据收发。

服务器代码:

复制代码
#include <myhead.h>
#define IP "192.168.124.34"
#define PORT 8888
void handle(int sss)
{
    if(sss==SIGCHLD)
    {
        while(waitpid(-1,NULL,WNOHANG)>0);//非阻塞循环回收子进程资源
    }
    printf("回收成功\n");
}
int main(int argc, const char *argv[])
{
    //1、创建套接字
    //2、绑定
    //3、监听
    //4、父进程连接新的客户端
    //5、创建子父进程,父进程关闭新的描述符
    //5、子进程负责数据收发并关闭旧的描述符
    //1、创建套接字
    int oldfd = socket(AF_INET,SOCK_STREAM,0);
    if(-1==oldfd)
    {
        perror("socket");
        return -1;
    }
    //端口号快速复用
    int k = 999;
    if(setsockopt(oldfd,SOL_SOCKET,SO_REUSEADDR,&k,sizeof(k))==-1)
    {
        perror("setsockopt");
        return -1;
    }
    printf("端口号快速复用成功\n");

    //2、绑定
    struct sockaddr_in server = {
        .sin_family = AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr = inet_addr(IP)
    };
    if(bind(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
    {
        perror("bind");
        return -1;
    }
    //3、监听
    if(listen(oldfd,88)==-1)
    {
        perror("listen");
        return -1;
    }

    //4、父进程连接新的客户端 
    struct sockaddr_in client;
    int client_len = sizeof(client);
    while(1)
    {
        int newfd = accept(oldfd,(struct sockaddr *)&client,&client_len);
        if(newfd==-1)
        {
            perror("accept");
            return -1;
        }
        printf("%s:%d\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));//输出新客户端信息

        //5、创建子父进程
        pid_t pid = fork();
        char buff[1024];
        if(pid>0)
        {
            if(signal(SIGCHLD,handle)==SIG_ERR)//绑定子进程退出时的信号
            {
                perror("signal");
                return -1;
            }
            //父进程关闭新的描述符
            close(newfd);
        }
        else if(pid==0)
        {
            //子进程关闭旧的描述符信息收发
            close(oldfd);
            while(1)
            {
                int res = recv(newfd,buff,sizeof(buff),0);
                if(res==0)
                {
                    printf("客户端已经退出\n");
                    break;
                }
                printf("%s\n",buff);
                strcat(buff,"今天周日啊");
                send(newfd,buff,sizeof(buff),0);
            }
            exit(EXIT_SUCCESS);//子进程退出
        }
        else
        {
            perror("fork");
            return -1;
        }
    }
    close(oldfd);
    return 0;
}

TCP客户端:

复制代码
#include <myhead.h>
#define IP "192.168.124.34"
#define SERPORT 8888
int main(int argc, const char *argv[])
{
    //1、创建套接字
    //2、绑定(不是必须绑定)
    //3、连接
    //4、收发消息
    
    int oldfd = socket(AF_INET,SOCK_STREAM,0);
    if(oldfd==-1)
    {
        perror("socket");
        return -1;
    }
#if 0
    //绑定固定的IP和端口号(不是必须的)
    struct sockaddr_in client = {
        .sin_family  =AF_INET,
        .sin_port = htons(7899),//自定义端口号
        .sin_addr.s_addr = inet_addr("192.168.124.34")
    };
    if(bind(oldfd,(struct sockaddr *)&client,sizeof(client))==-1)
    {
        perror("bind");
        return -1;
    }
#endif
    //连接服务器
    struct sockaddr_in server = {
        .sin_family  =AF_INET,
        .sin_port = htons(SERPORT),//注意端口号需要服务器端口
        .sin_addr.s_addr = inet_addr(IP)
    };
    
    if(connect(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
    {
        perror("connect");
        return -1;
    }
    //收发消息
    char buff[1024];
    while(1)
    {
        fgets(buff,sizeof(buff),stdin);
        buff[strlen(buff)-1] = '\0';
        send(oldfd,buff,strlen(buff),0);
        if(strcmp(buff,"quit")==0)//退出客户端
        {
            break;
        }
        bzero(buff,sizeof(buff));
        recv(oldfd,buff,sizeof(buff),0);//阻塞接收服务器消息
        printf("服务器发来消息:%s\n",buff);
    }
    return 0;
}

2、多线程并发服务器

1、大部分的多任务并发执行,我们都选择多线程,而不是多进程,因为多线程资源开销小,而且创建销毁比进程容易。

2、如果客户端过多,需要建立一个线程池,有客户端请求,就从线程池拿出一个线程分配给该客户端。

3、由于线程是提前创建好的,所以响应速度很快。

4、客户端退出后,线程会被销毁,由于线程占用资源较少,销毁也不会占用太多开销。

模型:

线程函数:

收发消息

关闭新描述符

子线程退出

建立原始套接字

绑定主机IP 监听客户端

循环:

accept:获取客户端请求

建立子线程

线程挂起

关闭旧的文件描述符

多线程并发服务器

复制代码
#include <myhead.h>
#define IP "192.168.124.34"
#define PORT 8888
void *fun(void *fd)
{
    int newfd = *(int *)fd; 
    char buff[1024];
    while(1)
    {
        int res = recv(newfd,buff,sizeof(buff),0);
        if(res==0)
        {
            printf("客户端下线\n");
            break;
        }
        printf("%s\n",buff);
        strcat(buff,"5点放学");
        send(newfd,buff,sizeof(buff),0);
    }
    pthread_exit(NULL);//子线程退出
}

int main(int argc, const char *argv[])
{
    //创建套接字
    int oldfd = socket(AF_INET,SOCK_STREAM,0);
    if(oldfd==-1)
    {
        perror("socket");
        return -1;
    }
    //绑定
    struct sockaddr_in server = {
        .sin_family  =AF_INET,
        .sin_port = htons(PORT),
        .sin_addr.s_addr  =inet_addr(IP)
    };
    if(bind(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
    {
        perror("bind");
        return -1;
    }
    //监听
    if(listen(oldfd,20)==-1)
    {
        perror("listen");
        return -1;
    }
    struct sockaddr_in client;
    int client_len = sizeof(client);
    int newfd;
    pthread_t tid;
    while(1)
    {
        //接收新客户端连入请求
        newfd = accept(oldfd,(struct sockaddr *)&client,&client_len);
        if(newfd==-1)
        {
            perror("accept");
            return -1;
        }
        //创建子线程与客户端通话
        if(pthread_create(&tid,NULL,fun,&newfd)==-1)
        {
            perror("pthread_create");
            return -1;
        }
    }
    pthread_join(tid,NULL);//回收子线程资源
    close(newfd);
    close(oldfd);
    return 0;
}

多线程客户端:

复制代码
#include <myhead.h>
#define IP "192.168.124.34"
#define SERPORT 8888
int main(int argc, const char *argv[])
{
    //1、创建套接字
    //2、绑定(不是必须绑定)
    //3、连接
    //4、收发消息
    
    int oldfd = socket(AF_INET,SOCK_STREAM,0);
    if(oldfd==-1)
    {
        perror("socket");
        return -1;
    }
#if 0
    //绑定固定的IP和端口号(不是必须的)
    struct sockaddr_in client = {
        .sin_family  =AF_INET,
        .sin_port = htons(7899),//自定义端口号
        .sin_addr.s_addr = inet_addr("192.168.124.34")
    };
    if(bind(oldfd,(struct sockaddr *)&client,sizeof(client))==-1)
    {
        perror("bind");
        return -1;
    }
#endif
    //连接服务器
    struct sockaddr_in server = {
        .sin_family  =AF_INET,
        .sin_port = htons(SERPORT),//注意端口号需要服务器端口
        .sin_addr.s_addr = inet_addr(IP)
    };
    
    if(connect(oldfd,(struct sockaddr *)&server,sizeof(server))==-1)
    {
        perror("connect");
        return -1;
    }
    //收发消息
    char buff[1024];
    while(1)
    {
        fgets(buff,sizeof(buff),stdin);
        buff[strlen(buff)-1] = '\0';
        send(oldfd,buff,strlen(buff),0);
        if(strcmp(buff,"quit")==0)//退出客户端
        {
            break;
        }
        bzero(buff,sizeof(buff));
        recv(oldfd,buff,sizeof(buff),0);//阻塞接收服务器消息
        printf("服务器发来消息:%s\n",buff);
    }
    return 0;
}

作业:多进程多线程并发服务器实

思维导图

相关推荐
利刃大大23 分钟前
【高并发服务器:HTTP应用】十四、Util工具类的设计与实现
服务器·http·高并发·项目·cpp
kblj555524 分钟前
学习Linux——网络——网卡
linux·网络·学习
暖阳之下24 分钟前
学习周报二十
人工智能·深度学习·学习
zhanglianzhao26 分钟前
基于云服务器自建Rustdesk远程桌面——以京东云为例
运维·服务器·京东云
Physicist in Geophy.38 分钟前
新版ubuntu中sac安装问题(缺少libncurses5)
linux·运维·ubuntu
可乐大数据38 分钟前
Docker安装(基于云服务器ECS实例 CentOS 7.9系统)
服务器·docker·centos
菲兹园长1 小时前
微服务组件(E、L、N、O、G)
linux·服务器·gateway
朝新_1 小时前
【SpringBoot】玩转 Spring Boot 日志:级别划分、持久化、格式配置及 Lombok 简化使用
java·spring boot·笔记·后端·spring·javaee
charlie1145141911 小时前
CSS学习笔记3:颜色、字体与文本属性基础
css·笔记·学习·教程·基础
LBuffer1 小时前
破解入门学习笔记题二十五
服务器·前端·microsoft