TCP并发服务器

1、并发服务器

tcp通信目前只能实现一个服务器端对应一个客户端,不能实现一个服务器对应多个客户端,为了实现该操作需要引入并发操作

循环服务器:

cpp 复制代码
#include <myhead.h>
#define SER_PORT 8888          
#define SER_IP "192.168.xx.xx"     
//服务器端口号
//服务器IP地址
int main()
{
   //1、创建用于连接的套接字文件描述符
   int sfd=socket(AF_INET,SOCK_STREAM,0);
   //参数1:AF_INET表示使用的是ipv4的通信协议
	//参数2:SOCK_STREAM表示使用的是tcp通信
	//参数3:由于参数2指定了协议,参数3填0即可
	if(sfd==-1)
	{
	  perror("socket error");
	  return -1;
	}
	printf("socket success sfd=%d\n".sfd);

  //2、绑定ip地址和端口号
  //填充要绑定的IP地址和端口号结构体
  struct socketaddr_in sin;
  sin.sin_family=AF_INET;  //通信域
  sin.sin_port=htons(SER_PORT);//端口号
  sin.sin_addr.s_addr=inet_addr(SER_IP);//ip地址
   
   //2.2 绑定工作
    //参数1:要被绑定的套接字文件描述符
    //参数2:要绑定的地址信息结构体,需要进行强制类型转换,防止警告
    //参数3:参数2的大小
     if(bind(sfd, (structsockaddr*)&sin,sizeof(sin)) ==-1)
    {
        perror("bind error");
        return -1;
    }
    printf("bind success\n");
  //3、启动监听
  //参数1:要启动监听的文件描述符
  //参数2:挂起队列长度
  if(listen(sfd,128)==-1)
  {
     perror("listen error");
        return -1;
  }
   printf("listen success\n");
   //4、阻塞等待客户端的连接请求
   //定义变量,用于接受客户端地址信息结构体
   struct sockaddr_in cin;//用于接收地址信息结构体的
   socklen_t socklen=sizeof(cin); //用于接收地址信息的长度
   while(1)
   {
      int newfd=accept(sfd,(struct sockaddr *)&cin &socklen);
       //参数1:服务器套接字文件描述符
        //参数2:用于接收客户端地址信息结构体的容器,如果不接收,也可以填NULL
        //参数3:接收参数2的大小,如果参数2为NULL,则参数3也是NULL
         if(newfd == -1)
        {
            perror("accept error");
            return -1;
        }
        printf("[%s:%d]:已连接成功,newfd = %d!!!!\n", inet_ntoa(cin.sin_addr), 
 ntohs(cin.sin_port), newfd);

      //5、数据收发
      char rbuf[128]=""; //数据容器
      while(1)
      {
         //清空容器的内容
         bzero(rbuf,sizeof(rbuf));
         //从套接字中读取消息
         int res=recv(newfd,rbuf,sizeof(rbuf),0);
         if(res==0)
         {
            printf("对端已经下线\n");
                break;
         }
         printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), 
 ntohs(cin.sin_port), rbuf);
          //对收到的数据处理一下,回给客户端
            strcat(rbuf, "*_*");
        //将消息发送给客户端
            if(send(newfd, rbuf, strlen(rbuf), 0) == -1)
            {
                perror("send error");
                return -1;
            }
            printf("发送成功\n");
      }
       //6、关闭套接字
        close(newfd); 
   }
    close(sfd);
    std::cout << "Hello, World!" << std::endl;
    return 0;
 


}

循环服务器的模型

cpp 复制代码
sfd = socket();    //创建用于链接的套接字文件描述符
bind();             //绑定ip地址和端口号
listen();            //启动被动监听状态
while(1)
{
        newfd = accept();       //阻塞接受客户端连接请求
        
        //跟客户端进行数据收发
        recv();
        send();
        close(newfd);
}
close(sfd);

accept函数和recv函数都是阻塞函数,但是,我们想让这两个阻塞任务同时执行,互不相干扰,此时,我们就要引入并发机制:多进程、多线程、IO多路复用。

2 多进程实现并发服务器

原理:主进程可以用于完成对客户端的连接请求,子进程可以完成对客户端通信操作

代码

cpp 复制代码
#include <myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.xx.xx" //服务器IP地址
//自定义信号处理函数
void handler(int sign)
{
  if(sign==SIGCHLD)
  {
    while(waitpid(-1,NULL,WNOHANG)>0);//循环以非阻塞的形式回收僵尸进程
  }
}
/********************主程序***************************** */
int main(int argc, const char *argv[])
{
  //将子进程发送的SIGCHLD信号连接到自定义信号处理函数中
    if(signal(SIGCHLD, handler) == SIG_ERR)
	{
		perror("signal error");
		return -1;
	}
   //1、创建用于连接的套接字文件描述符
   int sfd=socket(AF_INET,SOCK_STREAM,0);
   //参数1:AF_INET表示使用的是ipv4的通信协议
	//参数2:SOCK_STREAM表示使用的是tcp通信
	//参数3:由于参数2指定了协议,参数3填0即可
	if(sfd==-1)
	{
	  perror("socket error");
	  return -1;
	}
	  printf("socket success sfd = %d\n", sfd); //3

    //2、绑定ip地址和端口号
	//2.1 填充要绑定的ip地址和端口号结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; //通信域
	sin.sin_port = htons(SER_PORT); //端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
	//2.2 绑定工作
	//参数1:要被绑定的套接字文件描述符
	//参数2:要绑定的地址信息结构体,需要进行强制类型转换,防止警告
	//参数3:参数2的大小
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//3、启动监听
	//参数1:要启动监听的文件描述符
	//参数2:挂起队列的长度
	if(listen(sfd, 128) ==-1)
	{
		perror("listen error");
		return -1;
	}
	printf("listen success\n");
	//4、阻塞等待客户端的连接请求
	//定义变量,用于接受客户端地址信息结构体
	struct sockaddr_in cin; //用于接收地址信息结构体的
	socklen_t socklen = sizeof(cin); //用于接收地址信息的长度
    
    while(1)
	{
		int newfd = accept(sfd, (struct sockaddr *)&cin, &socklen);
		//参数1:服务器套接字文件描述符
		//参数2:用于接收客户端地址信息结构体的容器,如果不接收,也可以填NULL
		//参数3:接收参数2的大小,如果参数2为NULL,则参数3也是NULL
		if(newfd == -1)
		{
			perror("accept error");
			return -1;
		}
		printf("[%s:%d]:已连接成功,newfd = %d!!!!\n", inet_ntoa(cin.sin_addr),ntohs(cin.sin_port), newfd);
		//创建子进程,用于跟客户端进行通信
		pid_t pid = fork();
  
       if(pid > 0)
		{
			close(newfd);
		}else if(pid == 0)
		{
			//关闭sfd,因为子进程主要专注于跟客户端的通信,主要使用newfd
			close(sfd);
			//5、数据收发
			char rbuf[128] = ""; //数据容器
			while(1)
			{
				//清空容器中的内容
				bzero(rbuf, sizeof(rbuf));
				//从套接字中读取消息
				int res = recv(newfd, rbuf, sizeof(rbuf), 0);
				if(res == 0)
				{
					printf("对端已经下线\n");
					break;
				}
				printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr),
				ntohs(cin.sin_port), rbuf);
				//对收到的数据处理一下,回给客户端
				strcat(rbuf, "*_*");
				//将消息发送给客户端
				if(send(newfd, rbuf, strlen(rbuf), 0) == -1)
				{
					perror("send error");
					return -1;
					}
						printf("发送成功\n");
					}
					//6、关闭套接字
					close(newfd);
					//退出子进程
					exit(EXIT_SUCCESS);
				}else
				{
					perror("fork error");
					return -1;
				}
		}
		close(sfd); //关闭监听
		std::cout << "Hello, World!" << std::endl;
		return 0;
}

3 多线程实现并发服务器

原理:主线程用于接收客户端的连接请求, 分支线程用于跟客户端进行通信

cpp 复制代码
#include <myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.31.49" //服务器IP地址
//定义用于传送数据的结构体类型
struct Info
{
	int newfd; //套接字文件描述符
	struct sockaddr_in cin; //客户端套接字地址信息结构体
};
//分支线程,用户跟客户端进行通信
void *deal_cli_msg(void *arg)
{
		//解析传过来的数据
		int newfd = ((struct Info *)arg)->newfd; //解析套接字文件描述符
		struct sockaddr_in cin = ((struct Info *)arg)->cin;
		//5、数据收发
		char rbuf[128] = ""; //数据容器
		while(1)
		{
			//清空容器中的内容
			bzero(rbuf, sizeof(rbuf));
			//从套接字中读取消息
			int res = recv(newfd, rbuf, sizeof(rbuf), 0);
			if(res == 0)
			{
				printf("对端已经下线\n");
				break;
			}
			printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr),
			ntohs(cin.sin_port), rbuf);
			//对收到的数据处理一下,回给客户端
			strcat(rbuf, "*_*");
			//将消息发送给客户端
			if(send(newfd, rbuf, strlen(rbuf), 0) == -1)
			{
				perror("send error");
				return NULL;
			}
			printf("发送成功\n");
	  }
	//6、关闭套接字
	close(newfd);
	//退出分支线程
	pthread_exit(NULL);
}
/******************************主程序********************************* */
int main(int argc, const char *argv[])
{
	//1、创建用于连接的套接字文件描述符
	int sfd = socket(AF_INET, SOCK_STREAM, 0);
	//参数1:AF_INET表示使用的是ipv4的通信协议
	//参数2:SOCK_STREAM表示使用的是tcp通信
	//参数3:由于参数2指定了协议,参数3填0即可
	if(sfd == -1)
	{
		perror("socket error");
		return -1;
	}
	printf("socket success sfd = %d\n", sfd); //3
	//2、绑定ip地址和端口号
	//2.1 填充要绑定的ip地址和端口号结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET; //通信域
	sin.sin_port = htons(SER_PORT); //端口号
	sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址
	//2.2 绑定工作
	//参数1:要被绑定的套接字文件描述符
	//参数2:要绑定的地址信息结构体,需要进行强制类型转换,防止警告
	//参数3:参数2的大小
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) ==-1)
	{
		perror("bind error");
		return -1;
	}
	printf("bind success\n");
	//3、启动监听
	//参数1:要启动监听的文件描述符
	//参数2:挂起队列的长度
	if(listen(sfd, 128) ==-1)
	{
		perror("listen error");
		return -1;
	}
	printf("listen success\n");
	//4、阻塞等待客户端的连接请求
	//定义变量,用于接受客户端地址信息结构体
	struct sockaddr_in cin; //用于接收地址信息结构体的
	socklen_t socklen = sizeof(cin); //用于接收地址信息的长度
	while(1)
	{
		//对于accept函数而言,当程序执行到该函数时,会给当前客户端预分配一个最小未使用的文件
		描述符
		//后期再有新的小的文件描述符,也不使用了,新的最小的,用于下一次客户端的选定
		int newfd = accept(sfd, (struct sockaddr *)&cin, &socklen);
		//参数1:服务器套接字文件描述符
		//参数2:用于接收客户端地址信息结构体的容器,如果不接收,也可以填NULL
		//参数3:接收参数2的大小,如果参数2为NULL,则参数3也是NULL
		if(newfd == -1)
		{
			perror("accept error");
			return -1;
		}
		printf("[%s:%d]:已连接成功,newfd = %d!!!!\n", inet_ntoa(cin.sin_addr),
		ntohs(cin.sin_port), newfd);
		//填充要传输给分支线程的数据结构体变量
		struct Info buf = {newfd, cin};
		//创建分支线程,用于处理客户端的操作
		pthread_t tid = -1;
		if(pthread_create(&tid, NULL, deal_cli_msg, &buf) != 0)
		{
			printf("pthread_create error\n");
			return -1;
		}
		//完成对分支线程资源的处理
		//pthread_join(tid, NULL); //? 不能使用该函数回收,因为该函数时阻塞函数
		pthread_detach(tid); //将线程设置成分离态,执行结束后,系统自动回收资源
	}
	close(sfd);
	std::cout << "Hello, World!" << std::endl;
	return 0;
}
相关推荐
竣达技术5 分钟前
UPS 供电风险保护方案网络远程控制:免代理 SSH/Telnet UPS 安全关机方案
网络·安全·ssh
芯飞云11 分钟前
芯飞云服务器日常使用体验:从配置选型到实际负载的技术分析
运维·服务器
小程序设计11 分钟前
工业园区、企业、公司网络规划与设计
网络
Splashtop高性能远程控制软件17 分钟前
AI 辅助端点运维先接手补丁分级、合规可视和同台处置
运维·网络·人工智能·自动化·远程工作·splashtop
严谨的麻辣烫40 分钟前
AI Agent为什么越来越需要固定出口IP?从API白名单到生产环境网络管理
服务器·网络·tcp/ip·代理服务器
POMAGTOR44 分钟前
【5G 通讯 POGOPIN 弹簧顶针连接器解决方案】
网络·嵌入式硬件·5g
牢姐与蒯1 小时前
Linux文件(一).前传之基础IO
linux·运维·服务器
captain3761 小时前
TCP网络编程:ServerSocket与Socket详解
服务器·网络·tcp/ip
新时代牛马2 小时前
Linux 磁盘管理:分区、文件系统、挂载与扩容
linux·运维·服务器
Bruce_Liuxiaowei2 小时前
数字签名与“公钥制作者之谜“:信任从何而来
网络·安全·web安全