6.物联网LWIP之并发服务器编程

一。并发服务器(多线程)实现

复制代码
#include "socket_udp_server.h"
#include "socket_tcp_server.h"
#include "socket_wrap.h"
#include "ctype.h"

static char ReadBuff[BUFF_SIZE];

/**
  * @brief  udp 服务器任务
  * @param  None
  * @retval None
  */
void vUdpServerTask(void){

	int 	 sfd, n, i;
	struct sockaddr_in server_addr, client_addr;
	socklen_t	client_addr_len;
	
	//创建socket	udp通信
	sfd = Socket(AF_INET, SOCK_DGRAM, 0);
	server_addr.sin_family 			= AF_INET;
	server_addr.sin_port   			= htons(SERVER_PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	//绑定socket
	Bind(sfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	client_addr_len = sizeof(client_addr);
	while(1){
		//等待客户端发送数据
		n = Recvfrom(sfd, ReadBuff, BUFF_SIZE, 0, (struct sockaddr *)&client_addr, &client_addr_len);
		//进行大小写转换
		for(i = 0; i < n; i++){
		
			ReadBuff[i] = toupper(ReadBuff[i]);		
		}
		//写回客户端
		Sendto(sfd, ReadBuff, BUFF_SIZE, 0, (struct sockaddr *)&client_addr, client_addr_len);
	}
	
}

二。多路IO服用服务器模型

三。select API

1.标准select

复制代码
int select(int nfds, fd_set *readfds, fd_set *writefds,
			fd_set *exceptfds, struct timeval *timeout);

//nfds: 	监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
//readfds:	监控有读数据到达文件描述符集合,传入传出参数
//writefds:	监控写数据到达文件描述符集合,传入传出参数
//exceptfds:监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
//timeout:	定时阻塞监控时间,3种情况
			//1.NULL,永远等下去
			//2.设置timeval,等待固定时间
			//3.设置timeval里时间均为0,检查描述字后立即返回,轮询
//return:	成功:所监听的所有监听集合中,满足条件的总数!
		  //失败:0 超时
		  //错误:-1
	struct timeval {
		long tv_sec; /* seconds */
		long tv_usec; /* microseconds */
	};
	void FD_CLR(int fd, fd_set *set); 	//把文件描述符集合里fd清0
	int FD_ISSET(int fd, fd_set *set); 	//测试文件描述符集合里fd是否置1
	void FD_SET(int fd, fd_set *set); 	//把文件描述符集合里fd位置1
	void FD_ZERO(fd_set *set); 			//把文件描述符集合里所有位清0
/*
注意:
    1.	select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2.		解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
*/

2.LWIP select

复制代码
/* FD_SET used for lwip_select */
#ifndef FD_SET
#undef  FD_SETSIZE
/* Make FD_SETSIZE match NUM_SOCKETS in socket.c */
#define FD_SETSIZE    MEMP_NUM_NETCONN
#define FDSETSAFESET(n, code) do { \
  if (((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0)) { \
  code; }} while(0)
#define FDSETSAFEGET(n, code) (((n) - LWIP_SOCKET_OFFSET < MEMP_NUM_NETCONN) && (((int)(n) - LWIP_SOCKET_OFFSET) >= 0) ?\
  (code) : 0)
#define FD_SET(n, p)  FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] |=  (1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_CLR(n, p)  FDSETSAFESET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] &= ~(1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_ISSET(n,p) FDSETSAFEGET(n, (p)->fd_bits[((n)-LWIP_SOCKET_OFFSET)/8] &   (1 << (((n)-LWIP_SOCKET_OFFSET) & 7)))
#define FD_ZERO(p)    memset((void*)(p), 0, sizeof(*(p)))
//fd_set
typedef struct fd_set
{
  unsigned char fd_bits [(FD_SETSIZE+7)/8];
} fd_set;
//MEMP_NUM_NETCONN
#if !defined MEMP_NUM_NETCONN || defined __DOXYGEN__
#define MEMP_NUM_NETCONN                4
#endif
//LWIP_SOCKET_OFFSET
#if !defined LWIP_SOCKET_OFFSET || defined __DOXYGEN__
#define LWIP_SOCKET_OFFSET              0
#endif

3.select编程模型

四。并发服务器(select)实现

复制代码
#include "socket_wrap.h"
#include "socket_select_server.h"
#include "socket_tcp_server.h"
#include "string.h"
#include "FreeRTOS.h"
#include "task.h"
#include "ctype.h"

static char ReadBuff[BUFF_SIZE];
/**
  * @brief  select 并发服务器
  * @param  none
  * @retval none
  */
void vSelectServerTask(void)
{

	int 	 sfd, cfd, maxfd, i, nready, n, j;
	struct sockaddr_in server_addr, client_addr;
	socklen_t	client_addr_len;
	fd_set all_set, read_set;
	//FD_SETSIZE里面包含了服务器的fd
	int		 clientfds[FD_SETSIZE - 1];
	
	//创建socket
	sfd = Socket(AF_INET, SOCK_STREAM, 0);
	server_addr.sin_family 			= AF_INET;
	server_addr.sin_port   			= htons(SERVER_PORT);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	//绑定socket
	Bind(sfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
	//监听socket
	Listen(sfd, 5);
	
	client_addr_len = sizeof(client_addr);
	//初始化 maxfd 等于 sfd
	maxfd = sfd;
	
	//清空fdset
	FD_ZERO(&all_set);
	
	
	//把sfd文件描述符添加到集合中
	
	FD_SET(sfd, &all_set);
	
	
	//初始化客户端fd的集合
	
	for(i = 0; i < FD_SETSIZE -1 ; i++){
		//初始化为-1
		clientfds[i] = -1;
	}
	while(1)
	{
		//每次select返回之后,fd_set集合就会变化,再select时,就不能使用,
		//所以我们要保存设置fd_set 和 读取的fd_set
		read_set = all_set;
		nready = select(maxfd + 1, &read_set, NULL, NULL, NULL);
		//没有超时机制,不会返回0
		if(nready < 0){
			printf("select error \r\n");
			vTaskDelete(NULL);
		}
		//判断监听的套接字是否有数据
		if(FD_ISSET(sfd, &read_set)){
		
			//有了客户端进行连接了
			//客户接入
			cfd = accept(sfd, (struct sockaddr *)&client_addr, &client_addr_len);
			if(cfd < 0){
				printf("accept socket error\r\n");
				//继续select
				continue;
			}
			printf("new client connect fd = %d\r\n", cfd);
			//把新的cfd 添加到fd_set集合中
			FD_SET(cfd, &all_set);
			//更新要select的maxfd
			maxfd = (cfd > maxfd)?cfd:maxfd;
			//把新的cfd 保存到cfds集合中
			for(i = 0; i < FD_SETSIZE -1 ; i++){
				if(clientfds[i] == -1){
					clientfds[i] = cfd;
					//退出,不需要添加
					break;
				
				}
			}
			//没有其他套接字需要处理:这里防止重复工作,就不去执行其他任务
			if(--nready == 0){
				//继续select
				continue;
			}
		
		}
		//遍历所有的客户端文件描述符
		for(i = 0; i < FD_SETSIZE -1 ; i++){
			if(clientfds[i] == -1){
				//继续遍历
				continue;
			}
			//是否在我们fd_set集合里面
			if(FD_ISSET(clientfds[i], &read_set)){
				n = Read(clientfds[i], ReadBuff, BUFF_SIZE);
				//Read函数已经关闭了这个客户端的fd
				if(n <= 0){
					//从集合里面清除
					FD_CLR(clientfds[i], &all_set);
					//当前的客户端fd 赋值为-1
					clientfds[i] = -1;
				}else{
					//进行大小写转换
					for(j = 0; j < n; j++){
					
						ReadBuff[j] = toupper(ReadBuff[j]);		
					}
					//写回客户端
					n = Write(clientfds[i], ReadBuff, n);
					if(n < 0){
						//从集合里面清除
						FD_CLR(clientfds[i], &all_set);
						//当前的客户端fd 赋值为-1
						clientfds[i] = -1;		
					}				
				}
			}
		}		
	}
}
相关推荐
sunz_dragon3 分钟前
Claude Code / Codex Git 版本管理完整使用指南
服务器·人工智能
领尚4 分钟前
openclaw 极简安装(Ubuntu 24.04 server)
linux·运维·ubuntu
Gofarlic_OMS19 分钟前
Windchill的license合规使用报告自动化生成与审计追踪系统
大数据·运维·人工智能·云原生·自动化·云计算
SPC的存折26 分钟前
3、主从复制实现同步数据过滤
linux·运维·服务器
SPC的存折28 分钟前
openEuler 24.03 MariaDB Galera 集群部署指南(cz)
linux·运维·服务器·数据库·mysql
xcbrand30 分钟前
文旅行业品牌策划公司找哪家
大数据·运维·人工智能·python
SPC的存折42 分钟前
MySQL 8.0 分库分表
linux·运维·服务器·数据库·mysql
风吹迎面入袖凉1 小时前
【Redis】Redisson分布式锁原理
java·服务器·开发语言
cyber_两只龙宝1 小时前
【Oracle】Oracle之DQL中WHERE限制条件查询
linux·运维·数据库·云原生·oracle
斌味代码1 小时前
Shell 性能监控:指标采集、告警规则与可视化大盘设计
运维