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;
}
相关推荐
网安蟹佬霸1 小时前
密码学安全实战:从加密原理到哈希破解的完整攻防指南
网络·算法·安全·web安全·开源·密码学·哈希算法
刚入门的大一新生2 小时前
Linux-Linux的进程状态
linux·运维·服务器
AI备忘录2 小时前
(十一)DHCP 配置命令五厂商对照:华为 华三 锐捷 迈普 思科
服务器·网络·网络协议·网络安全·华为
随便做点啥2 小时前
32卡×4090 24GB,Qwen3.8-27B-FP8 集群部署报告
服务器·经验分享·docker·vllm
Brilliantwxx2 小时前
313131
服务器
迪康coolmu2 小时前
企业文件外发防泄漏实践:从通道封堵到三层全链路管控
大数据·运维·网络·人工智能·安全·阿里云
星恒讯工业路由器10 小时前
新一代通信网:从“连接万物”到“智联万物”的关键跃迁
网络·物联网·智能路由器·工业路由器·工业物联网·5g-a·新一代通信网
暴力求解12 小时前
Linux网络---传输层协议TCP(二)
linux·服务器·开发语言·网络·tcp/ip
石头猫灯12 小时前
WordPress wp2shell 漏洞链完整拆解流程
网络·数据结构·安全·web安全