您的位置:首页 > 理论基础 > 计算机网络

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);
}
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息