分别通过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;
}
相关推荐
吾日三省吾码31 分钟前
JVM 性能调优
java
LNTON羚通1 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
弗拉唐2 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
sun0077002 小时前
ubuntu dpkg 删除安装包
运维·服务器·ubuntu
oi772 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀2 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
哭泣的眼泪4082 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame