分别通过select、多进程、多线程实现一个并发服务器

select

cs 复制代码
#include<myhead.h>
 
#define PORT 8888              //端口号
#define IP "192.168.114.109"       //IP地址
 
 
int main(int argc, const char *argv[])
{
	//1、创建用于接受连接的套接字
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sfd == -1)
	{
		perror("socket error");
		return -1;
	}
 
	printf("socket success\n"); 
 
 
	//设置端口号快速重用
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
	{
		perror("setsockopt error");
		return -1;
	}
	printf("设置端口快速重用成功\n");
 

	//绑定IP地址和端口号
	//填充要绑定的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family 	= AF_INET;         //表明是ipv4
	sin.sin_port 	= htons(PORT);        //端口号
	sin.sin_addr.s_addr = inet_addr(IP);     //IP地址
 
	//绑定
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
 
	//套接字设置成被动监听状态
	if(listen(sfd, 128) == -1)
	{
		perror("listen error");
		return -1;
	}
 
	printf("listen success\n");
 
	//阻塞等待客户端连接请求,如果有新的客户端连接,则创建一个新的用于通信的套接字
	//定义客户端地址信息结构体
	struct sockaddr_in cin;             //客户端地址信息结构体
	cin.sin_family 	= AF_INET;
	socklen_t socklen = sizeof(cin);          //客户端地址信息的大小
 
 
	定义一个用于检测文件描述符的集合
	fd_set readfds, tempfds;                          //在栈区定义
 
	清空容器中的内容
	FD_ZERO(&readfds);
	将要检测的文件描述符放入集合中
	FD_SET(sfd, &readfds);           //将sfd文件描述符放入
	FD_SET(0, &readfds);             //将0号文件描述符放入
 
 
 
	//定义一个容器
	char buf[128] = "";
	int res = 0;             //接收select的返回值
	int newfd = -1;          //存放用于最新连接客户端的套接字
	int maxfd = sfd;          //定义控制select函数中最大文件描述符
 
	struct sockaddr_in saveCin[1024];       //用于存放客户端地址信息结构体
 
 
	while(1)
	{
		将集合内容复制一份
		tempfds = readfds;
 
		使用select阻塞等待集合中的文件描述符有事件产生
		res = select(maxfd+1, &tempfds, NULL, NULL, NULL);
		if(res == -1)
		{
			perror("select error");
			return -1;
		}else if(res == 0)
		{
			printf("time out\n");
			return -1;
		}
 
 
		//遍历所有集合中文件描述符
		for(int i=0; i<=maxfd; i++)
		{
			//判断当前i是否在集合中,如果不在,直接判断下一个
			if(!FD_ISSET(i, &tempfds))
			{
				continue;
			}
 
			判断sfd是否还在集合中
			if( i == sfd)
			{
				//阻塞接收客户端的链接请求,并且获取客户端的地址信息
				newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
				if(newfd == -1)
				{
					perror("accept error");
					return -1;
				}
				printf("accept success\n");
 
				将newfd放入readfds中
				FD_SET(newfd , &readfds);
 
				//更新maxfd
				if(newfd > maxfd)
				{
					maxfd = newfd;
				}
 
				//将最新的客户端套接字放入数组的下标为new的位置
				saveCin[newfd] = cin;
 
			}else if(i == 0 )	//判断是否是终端输入
 
			{
				char buf1[128] = "";
 
				bzero(buf, sizeof(buf));
				//从终端获取数据
				fgets(buf, sizeof(buf), stdin);       //从终端获取数据
				buf[strlen(buf)-1]='\0';
				printf("触发终端输入事件:%s\n", buf);
 
				sprintf(buf1, "%s%s", "系统消息:", buf);
 
				//将数据发送给所有客户端
				for(int j=4; j<=maxfd; j++)
				{
					send(j, buf1,sizeof(buf1), 0);
				}
 
 
			}else
			{
				//收发数据使用newfd完成通信
				char buf[128] = "";
				//清空字符串
				bzero(buf, sizeof(buf));
				int ret = recv(i, buf, sizeof(buf), 0);   //从套接字中读取客户端发来的消息
 
				//判断收到的结果
				if(ret == 0)
				{
					printf("客户端已经下线\n");	
					close(i);             //关闭通信的套接字
 
					将当前的文件描述符从集合中删除
					FD_CLR(i, &readfds);
 
					更新maxfd
					for(int j=maxfd; j>=0; j--)
					{
						//判断当前的j是否在集合中,如果在,则为maxfd
						if(FD_ISSET(j, &readfds))
						{
							maxfd = j;
							continue;           //继续判断下一个
						}
					}
 
 
				}else if(ret < 0)
				{
					perror("recv error");
					return -1;
				}
 
				printf("[%s:%d]:%s\n", inet_ntoa(saveCin[i].sin_addr), ntohs(saveCin[i].sin_port), buf);
 
				//将读取的信息,加上一些字符发送回去
				strcat(buf, "*_*");
				send(i, buf, sizeof(buf), 0); 
 
 
			}
		}
 
	}
  
 
	//关闭所有套接字
	close(sfd);               
 
	return 0;
}

多进程

cs 复制代码
#include<myhead.h>

#define PORT 8888              //端口号
#define IP "192.168.114.74"       //IP地址

//定义函数处理客户端信息
int deal_cli_msg(int newfd, struct sockaddr_in cin)
{

    //收发数据使用newfd完成通信
    char buf[128] = "";
    while(1)
    {
        //清空字符串
        bzero(buf, sizeof(buf));
        //read(newfd, buf, sizeof(buf));        //从套接字中读取客户端发来的消息
        int res = recv(newfd, buf, sizeof(buf), 0);        //从套接字中读取客户端发来的消息
        //buf[strlen(buf)-1] = '\0';

        //判断收到的结果
        if(res == 0)
        {
            printf("客户端已经下线\n");
            break;
        }else if(res < 0)
        {
            perror("recv error");
            return -1;
        }

        printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

        //将读取的信息,加上一些字符发送回去
        strcat(buf, "*_*");
        //    write(newfd, buf, sizeof(buf));
        send(newfd, buf, sizeof(buf), 0); 
    }

    close(newfd);             //关闭通信的套接字



    return 0;
}



//定义信号处理函数
void handler(int signo)
{
    if(signo == SIGCHLD)
    {
        while(waitpid(-1, NULL, WNOHANG) > 0);       //非阻塞形式回收僵尸进程
    }
}


int main(int argc, const char *argv[])
{
    //创建用于接受连接的套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }

    printf("socket success\n");    


    //设置端口号快速重用
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("设置端口快速重用成功\n");

    //绑定IP地址和端口号
    //填充要绑定的地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family     = AF_INET;         //表明是ipv4
    sin.sin_port     = htons(PORT);        //端口号
    sin.sin_addr.s_addr = inet_addr(IP);     //IP地址

    //绑定
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    //将套接字设置成被动监听状态
    if(listen(sfd, 128) == -1)
    {
        perror("listen error");
        return -1;
    }
    printf("listen success\n");

    
    //定义客户端地址信息结构体
    struct sockaddr_in cin;             //客户端地址信息结构体
    cin.sin_family     = AF_INET;
    socklen_t socklen = sizeof(cin);          //客户端地址信息的大小

    //定义子进程变量
    pid_t pid;

    //将SIGCHLD信号绑定到自定义信号处理函数中
    if(signal(SIGCHLD, handler) == SIG_ERR)
    {
        perror("signal error");
        return -1;
    }

    while(1)
    {    
        //阻塞接收客户端的链接请求,并且获取客户端的地址信息
        int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
        if(newfd == -1)
        {
            perror("accept error");
            return -1;
        }
        printf("accept success\n");

        //创建子进程,让子进程完成通信
        pid = fork();
        if(pid > 0)
        {
            //关闭newfd
            close(newfd);

            //回收僵尸进程
            //wait(NULL);        

            
        }else if(pid == 0)
        {       
            close(sfd);
            //调用处理客户端函数
            deal_cli_msg(newfd, cin);

            //退出子进程
            exit(EXIT_SUCCESS);
            
        }else
        {
            perror("fork error");
            return -1;
        }
    }

    
    //关闭所有套接字
    close(sfd);              

    return 0;
}

多线程

cs 复制代码
#include<myhead.h>

#define PORT 8888              //端口号
#define IP "192.168.114.74"       //IP地址

//定义用于向线程体传参的结构体类型
struct msg_info
{
    int newfd;
    struct sockaddr_in cin;
};


//定义线程体函数
void *deal_cli_msg(void *arg)
{
    //获取主线程传递的信息
    int newfd = ((struct msg_info*)arg) -> newfd;
    struct sockaddr_in cin = ((struct msg_info*)arg) -> cin;

    //收发数据使用newfd完成通信
    char buf[128] = "";
    while(1)
    {
        //清空字符串
        bzero(buf, sizeof(buf));
        //read(newfd, buf, sizeof(buf));        //从套接字中读取客户端发来的消息
        int res = recv(newfd, buf, sizeof(buf), 0);        //从套接字中读取客户端发来的消息
        //buf[strlen(buf)-1] = '\0';

        //判断收到的结果
        if(res == 0)
        {
            printf("客户端已经下线\n");
            break;
        }else if(res < 0)
        {
            perror("recv error");
            return NULL;
        }

        printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

        //将读取的信息,加上一些字符发送回去
        strcat(buf, "*_*");
        //    write(newfd, buf, sizeof(buf));
        send(newfd, buf, sizeof(buf), 0); 
    }

    close(newfd);             //关闭通信的套接字
    ptread_exit(NULL);               //退出线程

}



int main(int argc, const char *argv[])
{
    //创建用于接受连接的套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sfd == -1)
    {
        perror("socket error");
        return -1;
    }

    printf("socket success sfd = %d\n", sfd);    //4


    //设置端口号快速重用
    int reuse = 1;
    if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
    {
        perror("setsockopt error");
        return -1;
    }
    printf("设置端口快速重用成功\n");


    //绑定IP地址和端口号
    //填充要绑定的地址信息结构体
    struct sockaddr_in sin;
    sin.sin_family     = AF_INET;         //表明是ipv4
    sin.sin_port     = htons(PORT);        //端口号
    sin.sin_addr.s_addr = inet_addr(IP);     //IP地址

    //绑定
    if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin))==-1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");

    //将套接字设置成被动监听状态
    if(listen(sfd, 128) == -1)
    {
        perror("listen error");
        return -1;
    }

    printf("listen success\n");

    //阻塞等待客户端连接请求,如果有新的客户端连接,则创建一个新的用于通信的套接字
    //客户端地址信息结构体
    struct sockaddr_in cin;             //客户端地址信息结构体
    cin.sin_family     = AF_INET;
    socklen_t socklen = sizeof(cin);          //客户端地址信息的大小

    while(1)
    {
        //阻塞接收客户端的链接请求,并且获取客户端的地址信息
        int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
        if(newfd == -1)
        {
            perror("accept error");
            return -1;
        }
        printf("accept success\n");

        //定义用于向线程体传参的结构体变量
        struct msg_info info = {newfd, cin};

        //创建分支线程用于通信
        pthread_t tid;
        if(pthread_create(&tid, NULL, deal_cli_msg, &info) != 0)
        {
            printf("分支线程创建失败\n");
            return -1;
        }

        //将该线程分离
        if(pthread_detach(tid) != 0)
        {
            printf("分离失败\n");
            return -1;
        }

    }
  

    //关闭所有套接字
    close(sfd);               
    return 0;
}
相关推荐
玲娜贝儿--努力学习买大鸡腿版5 分钟前
hot 100 刷题记录(1)
数据结构·python·算法
123过去29 分钟前
pixiewps使用教程
linux·网络·测试工具·算法·哈希算法
深圳市快瞳科技有限公司35 分钟前
低空经济下,鸟类识别算法与无人机硬件的兼容性优化策略
算法·无人机
zdl68643 分钟前
Spring Boot文件上传
java·spring boot·后端
世界哪有真情1 小时前
哇!绝了!原来这么简单!我的 Java 项目代码终于被 “拯救” 了!
java·后端
RMB Player1 小时前
Spring Boot 集成飞书推送超详细教程:文本消息、签名校验、封装工具类一篇搞定
java·网络·spring boot·后端·spring·飞书
努力中的编程者1 小时前
二叉树(C语言底层实现)
c语言·开发语言·数据结构·c++·算法
重庆小透明1 小时前
【搞定面试之mysql】第三篇 mysql的锁
java·后端·mysql·面试·职场和发展
H_老邪1 小时前
Linux 与 Docker 常用命令
linux·运维·服务器·docker
RuoyiOffice1 小时前
企业请假销假系统设计实战:一张表、一套流程、两段生命周期——BPM节点驱动的表单变形术
java·spring·uni-app·vue·产品运营·ruoyi·anti-design-vue