网络编程(三)TCP IO多路转接服务器编程(select)
二,select多路转接服务器(多线程)前面介绍的select多路转接服务器已经可以实现处理多个客户端的服务器,为了进一步提高效率,我们还可以将上面的select多路转接服务器改写为多线程的形式。
与前面普通的select多路转接服务器相比,要修改为多线程主要是要在主线程的while(1)的循环中做些处理:
在select检测完读集合过后,
1,如果检查到有监听文件描述符准备就绪,则开一个子线程让其去处理该次的连接;
2,如果检测到有通信文件描述符准备就绪,则开一个子线程让其去处理该次的通信。
注意:本次使用多线程改造该服务器的多线程方法仍然是C++11标准的跨平台方法。
同时,需注意:
1,在监听文件描述符就绪以后连接客户端过程中,要添加accept成功以后返回的客户端进入待检测集合中,方便下一次的检测,所以需要传入的fd_set参数为指针类型,且该子线程与主线程detach以后,子线程与主线程在运行过程中可能同时访问该fd_set数据,所以要互斥访问。
maxfd这个变量同理。
也要互斥访问。
2,在处理客户端通信的子线程中,同样子线程与主线程detach,所以要注意该子线程中的fd_set数据也可能与主线程对fd_set的访问发生冲突,所以也要注意互斥访问。
关于C++11跨平台多线程编程的方法与互斥量的应用在我前面的博客中有介绍:
1)C++新特性(六)多线程(1)线程启动、结束,创建线程、join,detach,线程传参详解
2)C++新特性(六)多线程(2)线程中互斥量的使用,mutex对象使用,lock_guard类模板,死锁的解决
下面是多线程select多路转接服务器一个例子代码:
服务端
//server_thread.cpp
#include
#include
#include
#include
#include
#include
#include
using namespace std;
mutex fdset_mutex;
mutex maxfd_mutex;
void Connection(int lfd,fd_set*rdset,int*maxfd)//监听文件描述符,待检测的读集合,集合中的最大文件描述符
{
// 接受连接请求, 这个调用不阻塞
struct sockaddr_in cliaddr;
int cliLen = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, (socklen_t*)&cliLen);
// 得到了有效的文件描述符
// 通信的文件描述符添加到读集合
// 在下一轮select检测的时候, 就能得到缓冲区的状态
fdset_mutex.lock();
FD_SET(cfd, rdset);
fdset_mutex.unlock();
// 重置最大的文件描述符
maxfd_mutex.lock();
*maxfd = cfd > *maxfd ? cfd : *maxfd;
maxfd_mutex.unlock();
}
void Communication(int cfd,fd_set *rdset)
{
// 接收数据
char buf[1024];
// 该例子中客户端与服务端都是1024字节容量,客户端一次发多少,服务端一次能读多少。
//但是也有可能服务端容量只有10,而客户端容量有100,服务端一次只能接收10个字节, 客户端一次发送100个字节
// 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据
// 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次
// 循环会一直持续, 知道缓冲区数据被读完位置
memset(buf,0,sizeof(buf));
int len = read(cfd, buf, sizeof(buf));
if(len == 0)
{
printf("客户端关闭了连接...\n");
// 将检测的文件描述符从读集合中删除
fdset_mutex.lock();
FD_CLR(cfd, rdset);
fdset_mutex.unlock();
close(cfd);
}
else if(len > 0)
{
// 收到了数据
// 发送数据
printf("客户端say:%s\n",buf);
write(cfd, buf, len);
}
else
{
// 异常
perror("read");
}
}
int main()
{
// 1. 创建监听的fd
int lfd = socket(AF_INET, SOCK_STREAM, 0);
// 2. 绑定
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5000);
addr.sin_addr.s_addr = INADDR_ANY;
bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
// 3. 设置监听
listen(lfd, 128);
// 将监听的fd的状态检测委托给内核检测
int maxfd = lfd;
// 初始化检测的读集合
fd_set rdset;
fd_set rdtemp;
// 清零
FD_ZERO(&rdset);
// 将监听的lfd设置到检测的读集合中
FD_SET(lfd, &rdset);
// 通过select委托内核检测读集合中的文件描述符状态, 检测read缓冲区有没有数据
// 如果有数据, select解除阻塞返回
// 应该让内核持续检测
while(1)
{
// 默认阻塞
// rdset 中是委托内核检测的所有的文件描述符
fdset_mutex.lock();//主线程与子线程互斥访问rdset
rdtemp = rdset;
fdset_mutex.unlock();
maxfd_mutex.lock();
int num = select(maxfd+1, &rdtemp, NULL, NULL, NULL);
maxfd_mutex.unlock();
// rdset中的数据被内核改写了, 只保留了发生变化的文件描述的标志位上的1, 没变化的改为0
// 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了
// 判断
// 有没有新连接
fdset_mutex.lock();
if(FD_ISSET(lfd, &rdtemp))
{
// 接受连接请求, 这个调用不阻塞
fdset_mutex.unlock();
thread conn(Connection,lfd,&rdset,&maxfd);
conn.detach();
}
else
fdset_mutex.unlock();
// 没有新连接, 通信
maxfd_mutex.lock();
for(int i=0; i<maxfd+1; ++i)
{
maxfd_mutex.unlock();
// 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据
fdset_mutex.lock();
if(i != lfd && FD_ISSET(i, &rdtemp))
{
fdset_mutex.unlock();
thread comm(Communication,i,&rdset);
comm.detach();
}
else
fdset_mutex.unlock();
}
}
return 0;
}
和我前面介绍的普通TCP多线程服务器一样,要编译这个c++文件,也需要加上两个参数
-std=c++11
-lpthread
同时在vscode中,如果不识别c++11标准地话,也要设置vsdode环境为c++11标准。
即在编译出红线的地方,点击该红线,编辑"includePath”设置,设置c的标准为c11,c++标准为c++11。
客户端
// client.cpp
#include
#include
#include
#include
#include
int main()
{
// 1. 创建通信的套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1)
{
perror("socket");
exit(0);
}
// 2. 连接服务器
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(5000); // 大端端口
inet_pton(AF_INET, "39.108.179.82", &addr.sin_addr.s_addr);
int ret = connect(fd, (struct sockaddr*)&addr, sizeof(addr));
if(ret == -1)
{
perror("connect");
exit(0);
}
// 3. 和服务器端通信
int number = 0;
while(1)
{
// 发送数据
char buf[1024];
sprintf(buf, "你好, 服务器...%d\n", number++);
write(fd, buf, strlen(buf)+1);
// 接收数据
memset(buf, 0, sizeof(buf));
int len = read(fd, buf, sizeof(buf));
if(len > 0)
{
printf("服务器say: %s\n", buf);
}
else if(len == 0)
{
printf("服务器断开了连接...\n");
break;
}
else
{
perror("read");
break;
}
sleep(1); // 每隔1s发送一条数据
}
close(fd);
return 0;
}
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)