做网络服务的时候并发服务端程序的编写必不可少。前端客户端应用程序是否稳定一部分取决于客户端自身,而更多的取决于服务器是否相应时间够迅速,够稳定.
常见的linux并发服务器模型;
多进程并发服务器
多线程并发服务器
select多路I/O转接服务器
poll多路I/O转接服务器
epool多路I/O转接服务器.
本次主要讨论select多路I/O转接服务器模型:
使用select多路I/O转接服务器模型要考虑到以下几点:
1. select能监听的文件描述符个数受限于FD_SETSIZE, 一般为1024, 单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2. 解决1024以下客户端请求使用select是很合适的, 但如果链接客户端过多, select采用的是轮询模型, 会大大降低服务器响应效率, 不应在select上投入更多精力.
主要API:
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* execptfds, struct timeval* timmeout);
nfds: 监控的文件描述符集里最大文件描述符加1, 因为此参数会告诉内核横测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合, 传入传出参数
writefds: 监控写数据到达文件描述符集合, 传入传出参数
execptfds: 监控异常发生达文件描述符集合, 如带外数据到达异常,
timeout: 设置阻塞监控时间,3种情况
1. NULL 永远等下去
2. 设置timeval, 等待固定的时间
3. 设置timeval里时间均为0, 检查描述字后立即返回, 轮询
void FD_ZERO(fd_set* set); //把文件描述符集合里初始化为0
void FD_SET(int fd, fd_set* set); //把文件描述符集合里fd位置1
void FD_CLR(int fd, fd_set* set); //把文件描述符集合里fd位置0
int FD_ISSET(int fd, fd_set* set); //测试文件描述符集合里fd是否为1 有返回1, 没有返回0
代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/types.h>
#define SERV_PORT 9096 //服务所占用端口
#define SERV_ADDR "10.10.101.105" //服务所占用ip
int main(int argc, char* argv[]){
int listenfd, connfd, maxfd;
struct sockaddr_in serv_addr, clie_addr;
socklen_t clie_addr_len;
int opt, i, j, n, nready;
char str[INET_ADDRSTRLEN]; //INET_ADDRSTRLEN 内置宏 16
char buf[BUFSIZ]; //BUFSIZ 内置宏 8192
int client[FD_SETSIZE]; //保存客户端描述符
int maxi; //记录客户端数组最大的下标
fd_set rset, allset;
//创建监听套接字
//AF_INET: ipv4
//SOCK_STREAM tcp流类型
//IPPROTO_TCP tcp协议
listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//设置端口复用
opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//初始化为0
bzero(&serv_addr, sizeof(serv_addr));
//指定族: ipv4
serv_addr.sin_family = AF_INET;
//指定端口号并转换成网络字节序
serv_addr.sin_port = htons(SERV_PORT);
//指定ip并转换为网络字节序
inet_pton(AF_INET, SERV_ADDR, &serv_addr.sin_addr.s_addr);
//绑定到监听套接字
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
//设置同时连接请求上限
listen(listenfd, SOMAXCONN);
//将客户端数组全部置为-1
for(i = 0; i < FD_SETSIZE; i++){
client[i] = -1;
}
maxi = -1;
//监听描述集初始化
FD_ZERO(&allset);
//将监听套接字添加至监听集
FD_SET(listenfd, &allset);
maxfd = listenfd;
for(;;){
rset = allset;
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
//判断是否有连接请求
if(FD_ISSET(listenfd, &rset)){
clie_addr_len = sizeof(clie_addr);
//获取连接请求
connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
//打印客户端信息
printf("%s:%d connect successfully!\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, sizeof(str)), ntohs(clie_addr.sin_port));
//添加至客户端集合
for(i = 0; i < FD_SETSIZE; i++){
if(0 > client[i]){
client[i] = connfd;
break;
}
}
//客户端数组已满
if(FD_SETSIZE == i){
printf("clients at full strength!\n");
continue;;
}
//添加至监听集
FD_SET(connfd, &allset);
//判断i下标是否大于maxi
if(i > maxi){
maxi = i;
}
//判断新的描述符是否大于maxfd
if(connfd > maxfd){
maxfd = connfd;
}
//判断是否还有事件
if(0 == (--nready)){
continue;
}
}
for(i = 0; i <= maxi; i++){
if(0 > client[i])
continue;
if(FD_ISSET(client[i], &rset)){
bzero(buf, sizeof(buf));
n = read(client[i], buf, sizeof(buf));
//当对方关闭
if(0 == n){
clie_addr_len = sizeof(clie_addr);
//获取客户端信息
getpeername(client[i], (struct sockaddr*)&clie_addr, &clie_addr_len);
printf("%s:%d disconnect successfully!\n", inet_ntop(AF_INET, &clie_addr.sin_addr.s_addr, str, sizeof(str)), ntohs(clie_addr.sin_port));
//关闭连接
close(client[i]);
//从监听中清除
FD_CLR(client[i], &allset);
//客户端数组位置恢复为-1
client[i] = -1;
}else if(0 < n){
//全部转为大写
for(j = 0; j < n; j++){
buf[j] = toupper(buf[j]);
}
//回写给客户端
write(client[i], buf, n);
}
//判断是否还有事件
if(0 == (--nready)){
break;
}
}
}
}
close(listenfd);
}
转载于:https://blog.51cto.com/lisea/1791125