一。并发服务器(多线程)实现
#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;
}
}
}
}
}
}