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

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

思维导图

相关推荐
潇-xiao3 分钟前
Qt 验证自动释放 + 乱码问题(6)
c++·笔记·qt
FAREWELL0007514 分钟前
Unity基础学习(十)Camera组件
学习·unity·c#·游戏引擎
Watink Cpper15 分钟前
[Linux]多线程(二)原生线程库---pthread库的使用
android·linux·运维·原生线程库·pthread库
网络空间小黑1 小时前
渗透测试行业术语2
服务器·网络·安全·网络安全·中间件
Brookty1 小时前
【Java学习】枚举(匿名类详解)
java·学习
wgc2k2 小时前
Java游戏服务器开发流水账(4)游戏的数据持久化
java·服务器·游戏
chennalC#c.h.JA Ptho2 小时前
archlinux 详解系统层面
linux·经验分享·笔记·系统架构·系统安全
R-sz2 小时前
如何创建伪服务器,伪接口
运维·服务器
python算法(魔法师版)2 小时前
Docker容器启动失败?无法启动?
linux·运维·nginx·docker·容器
酷爱码2 小时前
Ubuntu日志文件清空的三种方式
linux·运维·ubuntu