什么是端口复用
举一个简单地网络服务器的例子,如果你的服务器需要和多个客户端保持连接,处理客户端的请求,属于多进程的并发问题,如果创建很多个进程来处理这些IO流,会导致CPU占有率很高。
所以人们提出了I/O多路复用模型:一个线程,通过记录I/O流的状态来同时管理多个I/O。
select只是IO复用的一种方式,其他的还有:poll,epoll等。
实现代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <signal.h>
#include <list>
#include <math.h>
std::list<int> g_client_socks;
int main()
{
fd_set m_set;
int m_maxfd = -1;
//创建套接字
int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
bool m_isListen = false;
sockaddr_in addr;
int len = sizeof(sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr("10.50.30.31");
addr.sin_port = htons(8888);
bind(serv_sock, (sockaddr*)&addr, sizeof(addr));
while (1)
{
//每次都要重新设置集合才能激发事件,对已存在的socketfd重新设置
FD_ZERO(&m_set);
//只监听一次服务端口
if(!m_isListen)
{
int ret = listen(serv_sock, 1);
m_isListen = ret==0;
printf("listen %s\n", ret==0?"ok":"error");
}
//填充FD_SET,并获取最大fd
FD_SET(serv_sock, &m_set);
m_maxfd = std::max(m_maxfd, serv_sock);
for(auto item=g_client_socks.begin(); item!=g_client_socks.end(); ++item)
{
FD_SET(*item, &m_set);
m_maxfd = std::max(m_maxfd, *item);
}
//开始select
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000; //50ms 每次都需要设置
select(m_maxfd+1, &m_set, NULL, NULL, &tv);
//监听接口有没有新连接的客户端
if(FD_ISSET(serv_sock, &m_set))
{
int new_client = accept(serv_sock, (sockaddr*)&addr, (socklen_t*)&len);
g_client_socks.push_back(new_client);
printf("serv_socket is select, fd:%d\n", new_client);
}
//遍历客户端有没有接收到数据
for(auto item=g_client_socks.begin(); item!=g_client_socks.end(); ++item)
{
if(FD_ISSET(*item, &m_set))
{
char buf[128] = {0};
int ret = recv(*item, buf, sizeof(buf), 0);
printf("recv data.ret:%d fd:%d recv:[%s]\n", ret, *item, buf);
//ret为0代表断开连接
if(ret == 0)
{
printf("fd %d disconnnect\n", *item);
close(*item);
g_client_socks.erase(item++);
}
}
}
usleep(5000);
}
return 0;
}
效果
- 可实现多路客户端同时连接
- 打印任意客户端发送的数据
- 可检测到客户端断开连接
仅用作简单的学习记录,存在逻辑考虑不周的地方希望大家多多指正。