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

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

思维导图

相关推荐
SHENGWEI胜为12 分钟前
服务器 IP 地址冲突如何排查?从 DHCP 租约到 ARP 缓存定位故障
服务器·tcp/ip·缓存
H.莓飛14 分钟前
【数据结构】快速排序专题
linux·服务器·c语言·数据结构·visual studio
小HANN15 分钟前
(索引 + SQL 优化):《MySQL 底层探秘②:B + 树索引与 Explain 实战,根治线上慢 SQL》
linux·运维·服务器·经验分享
wtblszn17 分钟前
活性炭吸附塔组态监控运维系统方案
运维
lisanmengmeng18 分钟前
pushgateway
linux·运维·服务器·网络
我滴老baby18 分钟前
自建服务20个,书签300条?我用一个Docker容器做了个“服务启动台”,手机也能打开
运维·docker·容器
Marst Code24 分钟前
(Unix)一切皆文件
服务器·unix
lwx91485225 分钟前
Docker容器模拟主机
运维·docker·容器
李纲明31 分钟前
WordPress 媒体库上传图片报「HTTP 错误」:php.ini、临时目录、安全插件和 CDN 怎么逐层查
运维·php·上传·cdn·wordpress·媒体库
Marst Code32 分钟前
(Unix) fork-exec 进程创建与程序加载解耦
服务器·unix