Linux网络编程 5 - select模式的TCP服务器
2018-03-31 23:18
309 查看
为了同时处理多个客户端的连接,上一篇介绍了利用多线程的方法实现,每个连接新建一个线程,然后各自去处理。这是最简单也是最容易想到的方式。客户端的连接存在,线程就存在。
但是,对于每一个客户端,并不是时时刻刻都会向服务端发送消息的,随着客户端连接数量的增加,创建的线程也越来越多,系统在线程和进程之间切换的开销就会变得非常大。
再者,如果客户端频繁的创建连接又断开,服务端就会随之创建或销毁线程,使得效率非常低。
这时,就要用到select模式了:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <pthread.h>
#include <vector>
using namespace std;
//获取客户端 ip:port 格式的字符串
string getpeeraddrstr(int sockfd)
{
struct sockaddr_in addr = {0};
unsigned int size = sizeof(addr);
getpeername(sockfd, (struct sockaddr*)&addr, &size);
stringstream ssaddr;
ssaddr << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
return ssaddr.str();
}
//入参 cltsockets 客户端socket的集合
//入参 svrfd 服务端绑定的集合
//出参 maxfd 服务端和客户端中最大的socket
//出参 readset 可读的fdset
//根据2个入参获取2个出参 即select需要的两个参数
void select_fdset(const vector<int>& cltsockets, int& svrfd, int& maxfd, fd_set& readset)
{
FD_ZERO(&readset);
for(unsigned int i = 0; i < cltsockets.size(); i++)
{
if(-1 != cltsockets[i])
{
FD_SET(cltsockets[i], &readset);
maxfd = cltsockets[i] > maxfd ? cltsockets[i] : maxfd;
}
}
if(-1 != svrfd)
{
FD_SET(svrfd, &readset);
maxfd = svrfd > maxfd ? svrfd : maxfd;
}
}
//如果是服务端的socket可读 则调用accept接收连接
void select_server(int svrfd, vector<int>& cltsockets, const fd_set& readset)
{
if(-1 != svrfd && FD_ISSET(svrfd, &readset))
{
struct sockaddr_in cltaddr = {0};
unsigned int addrlen = sizeof(cltaddr);
//服务端阻塞等待客户端的连接
int cltfd = accept(svrfd, (struct sockaddr*)&cltaddr, &addrlen);
if(-1 == cltfd)
{
perror("accept failed");
return;
}
cout << "connect accept: " << getpeeraddrstr(cltfd) << endl;
cltsockets.push_back(cltfd);
}
}
//如果是客户端的socket可读 则接收客户端发来的消息 并打印
void select_client(vector<int>& cltsockets, const fd_set& readset)
{
for(unsigned int i = 0; i < cltsockets.size(); i++)
{
if(-1 != cltsockets[i] && FD_ISSET(cltsockets[i], &readset))
{
char buffer[1024] = {0};
int recvlen = 0;
string straddr = getpeeraddrstr(cltsockets[i]);
recvlen = recv(cltsockets[i], buffer, sizeof(buffer), 0);
if(0 < recvlen) //接收成功
{
cout << "recv from " << straddr << " " << buffer;
}
else
{
cout << "client " << straddr <<" closed" << endl;
close(cltsockets[i]);
cltsockets[i] = -1;
break;
}
}
}
}
int main()
{
int opt = 1;
int svrfd = -1;
unsigned short svrport = 9999;
struct sockaddr_in svraddr = {0};
vector<int> cltsockets; //保存客户端的socket集合
fd_set readset; //可读的socket集合
struct timeval tv; //select等待时间
int maxfd = -1; //select的socket范围 是服务端客户端所有socket中最大的socket值
//创建socket
svrfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == svrfd)
{
perror("socket failed");
return -1;
}
//设置地址重用
setsockopt(svrfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
//绑定ip地址和端口
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(svrport); //服务端绑定端口
svraddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务端绑定任意IP
if(-1 == bind(svrfd, (struct sockaddr*)&svraddr, sizeof(svraddr)))
{
perror("bind failed");
close(svrfd);
return -1;
}
//开始监听
if(-1 == listen(svrfd, 10))
{
perror("listen failed");
close(svrfd);
return -1;
}
while(1)
{
tv.tv_sec = 1;
tv.tv_usec = 0;
//根据客户端和服务端所有socket的集合 获取select的两个参数
select_fdset(cltsockets, svrfd, maxfd, readset);
//开始select 最多等待tv时长
int ret = select(maxfd+1, &readset, NULL, NULL, &tv);
if(ret < 0)
{
perror("select error");
break;
}
else if(0 == ret)
{
//select的tv时间到 继续select
continue;
}
else
{
select_server(svrfd, cltsockets, readset);
select_client(cltsockets, readset);
}
}
}
但是,对于每一个客户端,并不是时时刻刻都会向服务端发送消息的,随着客户端连接数量的增加,创建的线程也越来越多,系统在线程和进程之间切换的开销就会变得非常大。
再者,如果客户端频繁的创建连接又断开,服务端就会随之创建或销毁线程,使得效率非常低。
这时,就要用到select模式了:
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <iostream>
#include <sstream>
#include <pthread.h>
#include <vector>
using namespace std;
//获取客户端 ip:port 格式的字符串
string getpeeraddrstr(int sockfd)
{
struct sockaddr_in addr = {0};
unsigned int size = sizeof(addr);
getpeername(sockfd, (struct sockaddr*)&addr, &size);
stringstream ssaddr;
ssaddr << inet_ntoa(addr.sin_addr) << ":" << ntohs(addr.sin_port);
return ssaddr.str();
}
//入参 cltsockets 客户端socket的集合
//入参 svrfd 服务端绑定的集合
//出参 maxfd 服务端和客户端中最大的socket
//出参 readset 可读的fdset
//根据2个入参获取2个出参 即select需要的两个参数
void select_fdset(const vector<int>& cltsockets, int& svrfd, int& maxfd, fd_set& readset)
{
FD_ZERO(&readset);
for(unsigned int i = 0; i < cltsockets.size(); i++)
{
if(-1 != cltsockets[i])
{
FD_SET(cltsockets[i], &readset);
maxfd = cltsockets[i] > maxfd ? cltsockets[i] : maxfd;
}
}
if(-1 != svrfd)
{
FD_SET(svrfd, &readset);
maxfd = svrfd > maxfd ? svrfd : maxfd;
}
}
//如果是服务端的socket可读 则调用accept接收连接
void select_server(int svrfd, vector<int>& cltsockets, const fd_set& readset)
{
if(-1 != svrfd && FD_ISSET(svrfd, &readset))
{
struct sockaddr_in cltaddr = {0};
unsigned int addrlen = sizeof(cltaddr);
//服务端阻塞等待客户端的连接
int cltfd = accept(svrfd, (struct sockaddr*)&cltaddr, &addrlen);
if(-1 == cltfd)
{
perror("accept failed");
return;
}
cout << "connect accept: " << getpeeraddrstr(cltfd) << endl;
cltsockets.push_back(cltfd);
}
}
//如果是客户端的socket可读 则接收客户端发来的消息 并打印
void select_client(vector<int>& cltsockets, const fd_set& readset)
{
for(unsigned int i = 0; i < cltsockets.size(); i++)
{
if(-1 != cltsockets[i] && FD_ISSET(cltsockets[i], &readset))
{
char buffer[1024] = {0};
int recvlen = 0;
string straddr = getpeeraddrstr(cltsockets[i]);
recvlen = recv(cltsockets[i], buffer, sizeof(buffer), 0);
if(0 < recvlen) //接收成功
{
cout << "recv from " << straddr << " " << buffer;
}
else
{
cout << "client " << straddr <<" closed" << endl;
close(cltsockets[i]);
cltsockets[i] = -1;
break;
}
}
}
}
int main()
{
int opt = 1;
int svrfd = -1;
unsigned short svrport = 9999;
struct sockaddr_in svraddr = {0};
vector<int> cltsockets; //保存客户端的socket集合
fd_set readset; //可读的socket集合
struct timeval tv; //select等待时间
int maxfd = -1; //select的socket范围 是服务端客户端所有socket中最大的socket值
//创建socket
svrfd = socket(AF_INET, SOCK_STREAM, 0);
if(-1 == svrfd)
{
perror("socket failed");
return -1;
}
//设置地址重用
setsockopt(svrfd, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
//绑定ip地址和端口
svraddr.sin_family = AF_INET;
svraddr.sin_port = htons(svrport); //服务端绑定端口
svraddr.sin_addr.s_addr = htonl(INADDR_ANY); //服务端绑定任意IP
if(-1 == bind(svrfd, (struct sockaddr*)&svraddr, sizeof(svraddr)))
{
perror("bind failed");
close(svrfd);
return -1;
}
//开始监听
if(-1 == listen(svrfd, 10))
{
perror("listen failed");
close(svrfd);
return -1;
}
while(1)
{
tv.tv_sec = 1;
tv.tv_usec = 0;
//根据客户端和服务端所有socket的集合 获取select的两个参数
select_fdset(cltsockets, svrfd, maxfd, readset);
//开始select 最多等待tv时长
int ret = select(maxfd+1, &readset, NULL, NULL, &tv);
if(ret < 0)
{
perror("select error");
break;
}
else if(0 == ret)
{
//select的tv时间到 继续select
continue;
}
else
{
select_server(svrfd, cltsockets, readset);
select_client(cltsockets, readset);
}
}
}
相关文章推荐
- 18--SELECT-TCP-服务器编程模式(2)
- Linux网络编程:TCP服务器(单进程多用户),使用select方法实现
- Linux网络编程——tcp并发服务器(I/O复用之select
- Linux网络编程:TCP服务器(单进程多用户),使用select方法实现
- Linux网络编程——tcp并发服务器(I/O复用之select)
- select模式下的tcp服务器
- Linux网络编程【五】:TCP协议高性能服务器(http)模型之I/O多路转接select
- Linux网络编程:TCP服务器(单进程多用户),使用select方法实现
- 项目:聊天室(Tcp Select架构)(链表)—— 服务器
- Java基础—socket--TCP-客户端服务器模式
- 基于select模型的TCP服务器
- 一个Select模式的简单服务器
- Linux网络编程:tcp并发服务器(I/O复用之select)
- 基于select模型的TCP服务器
- Linux网络编程10——TCP编程之服务器
- Linux网络编程之TCP编程,select多路复用(经典),但不能够快速重启
- LINUX网络编程(fork、select、epoll三种模式)
- (TCP模式)客户端与服务器之间的文件传输
- 使用Select I/O模型来实现一个并发处理多个客户端的TCP服务器
- 用select的IO复用多客户ECHO_TCP服务器