当前位置: 首页 > article >正文

socket编程之select()

select()函数是异步IO,等待有事件来临时会触发。相对于fork()的有点就是占用的资源较少。

服务端代码

//
//  Tcp_server.cpp
//  Cpp
//
//  Created by JH on 2020/4/8.
//  Copyright © 2020 JH. All rights reserved.
//#include <stdio.h>
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>#define PORT 8111
#define MESSAGE_LEN 1024
#define FD_SIZE 1024 // 最大的socket连接数int main(int argc,char * argv[]){int ret = -1;int socket_fd =-1,accept_fd=-1;int backlog = 10;//等待连接队列的最大长度。int flags =1 ;int max_fd = -1; // 集合中所有文件描述符的范围,int accept_fds[FD_SIZE] = {-1,};int maxposition=0; //最大位置int curposition=-1;//当前位置int events = 0;fd_set fd_sets;//用于存储接收的socket的fd ,fd_set是数组,每个元素与已打开的fd建立联系char in_buff[MESSAGE_LEN] ={0};struct sockaddr_in localaddr,remoteaddr;socket_fd = socket(AF_INET, SOCK_STREAM, 0);//创建服务端侦听的socketif (socket_fd == -1) {exit(-1);}ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags));if (ret == -1) {std:: cout << "set option failed" << std::endl;exit(-1);}//设置socket的性质为非阻塞状态flags = fcntl(socket_fd, F_GETFL,0);fcntl(socket_fd, F_SETFL,flags | O_NONBLOCK);localaddr.sin_family = AF_INET;localaddr.sin_port = PORT;localaddr.sin_addr.s_addr = INADDR_ANY;bzero(&(localaddr.sin_zero), 8);ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));if (ret == -1) {std:: cout << "set bind failed" << std::endl;exit(-1);}ret = listen(socket_fd, backlog);if (ret == -1) {std:: cout << "set listen failed" << std::endl;exit(-1);}ssize_t size;max_fd = socket_fd; // 每次重新设置max_fdfor (int i = 0; i <FD_SIZE; i++) {accept_fds[i] = -1;}for (; ; ) {FD_ZERO(&fd_sets);//清空fd_sets都置为0FD_SET(socket_fd,&fd_sets); //侦听的socket设置进入fd_set,置为1std:: cout << "-----" << std::endl;for (int j=0; j < maxposition; j++) {if (accept_fds[j] != -1) {if (accept_fds[j] >max_fd) {max_fd = accept_fds[j];}FD_SET(accept_fds[j],&fd_sets);//将已连接的socket重新设置到fd_sets中}}//遍历所有的fd,有事件触发才会被会被调用,select执行系统会修改fd_sets中的内容events = select(max_fd + 1, &fd_sets, NULL, NULL, NULL);std::cout<<"events = "<<events<<std::endl;if (events < 0) {break;}else if (events == 0){continue;}else if (events){std::cout<<"events = "<<events <<" socket_fd: "<<socket_fd<<std::endl;if (FD_ISSET(socket_fd,&fd_sets)) { // 侦听的socket触发,表示这是新链接int a = 0;for (; a <FD_SIZE; a++) {if (accept_fds[a] == -1) {curposition = a;break;}}if (a == FD_SIZE) {std::cout << "链接已满"<<std::endl;continue;}socklen_t addr_len = sizeof(struct sockaddr);//接收请求accept_fd = accept(socket_fd, (struct sockaddr *)& remoteaddr, &addr_len);int flags = fcntl(accept_fd,F_GETFL, 0);//取出新链接的fd的相关选项fcntl(accept_fd, F_SETFL,flags |O_NONBLOCK);// 设置为非阻塞accept_fds[curposition] = accept_fd;if (curposition +1 >maxposition) {maxposition = curposition +1;}if (accept_fd >max_fd) {max_fd = accept_fd;}std::cout << "新链接的fd:"<<accept_fd << " curposition = " <<curposition<<std::endl;}for (int k = 0; k < maxposition ; k++) {if ((accept_fds[k] != -1) && FD_ISSET(accept_fds[k],&fd_sets)) { // 有事件时std:: cout <<"accpet event: " <<k  << " accpetd:"<<accept_fds[k]<< std::endl;memset(in_buff, 0, MESSAGE_LEN);size = recv(accept_fds[k], &in_buff, MESSAGE_LEN, 0);if (size == 0) {close(accept_fds[k]);accept_fds[k] = -1;break;}std:: cout <<"recv : " <<in_buff << std::endl;send(accept_fds[k], (void *)in_buff, MESSAGE_LEN, 0);}}}}close(socket_fd);return 0;
}

select()函数要与fd_set、FD_ZERO、FD_SET、FD_ISSET配合使用:

fd_set:是存储文件描述符的数组,数组中的每个元素会已经打开的文件描述符建立联系。

FD_ZERO:用于清空fd_set,都置为0

FD_SET(fd,&fd_set):将fd设置到fd_set中某个位置1

FD_ISSET(d,&fd_set):判断socket_fd是否在fd_set中

select()函数执行后,系统会修改fd_set中的内容,因此应用层要重新设置fd_set中的内容

 

代码详解

  1. 服务端程序启动时,首先初始化监听的socket,随后监听的socket使用fcntl设置为非阻塞状态,并进行绑定。进入for循环前初始化accept_fds默认-1,它是用于存储客户端连接的socket文件描述符的数组。
        //设置socket的性质为非阻塞状态flags = fcntl(socket_fd, F_GETFL,0);fcntl(socket_fd, F_SETFL,flags | O_NONBLOCK);

     

  2. 服务端随后进入for循环首先,清空fd_sets,将监听的sockt中设置到fd_sets中。然后maxposition用于找到最大的fd(select的一个参数需要一个最大的fd),并重新将accept_fds存到fd_sets中。(accept_fds是用于存储客户端连接的socket_id)。
            FD_ZERO(&fd_sets);//清空fd_setsFD_SET(socket_fd,&fd_sets); //侦听的socket设置进入fd_setfor (int j=0; j < maxposition; j++) { //初始化的maxposition为0,有一个新链接进入+1if (accept_fds[j] != -1) {   //获取最大的fdif (accept_fds[j] >max_fd) {max_fd = accept_fds[j];}FD_SET(accept_fds[j],&fd_sets); //将新加入链接的存入fd_sets}}

     

  3. 随后服务端程序执行到select()函数就被阻塞了。后面的程序不会执行,要等待事件来临触发。
     events = select(max_fd + 1, &fd_sets, NULL, NULL, NULL);

     

  4. select()何时会被触发? 当客户端连接过来后,监听的socket此时侦听到有新的sockt过来,此时select()会被触发,并且返回值events大于0,表示有新的事件要处理。
  5. event大于0,使用FD_ISSET(socket_fd,&fd_sets)判断是不是新链接。由于侦听的scoket触发了,将select()函数唤醒,FD_ISSET()此时返回大于0;当select会
     if (FD_ISSET(socket_fd,&fd_sets)) //判断是不是新连接

     

  6. 服务端使用accept()函数接收客户端的请求,返回一个新的文件描述符accept_fd,并将该accept_fd使用fcntl()设置为非阻塞状态。并存到accept_ids中。
    //接收请求
    accept_fd = accept(socket_fd, (struct sockaddr *)& remoteaddr, &addr_len);              
    int flags = fcntl(accept_fd,F_GETFL, 0);//取出新链接的fd的相关选项
    fcntl(accept_fd, F_SETFL,flags |O_NONBLOCK);// 设置为非阻塞

     

  7. 进入第二个for循环处理消息的接受和发送。由于已经有一个客户端连接了服务端maxpostion变为1,进入if中判断,但是新的请求还没有设置到fd_sets中,FD_ISSET()为0,跳出循环。
    for (int k = 0; k < maxposition ; k++) {if ((accept_fds[k] != -1) && FD_ISSET(accept_fds[k],&fd_sets)) { // 有事件时...}
    }

     

  8. 随后程序回到最外层的for循环,重新设置fd_sets,并将accept_fds中的客户端请求的fd重新设置到fd_sets中。
  9. 程序第二次执行到select()时,由于没有新的请求,所以又被阻塞。
  10. 当第一个已经连接的客户端socket发送消息时,select()函数又会被触发,此时触发的不是侦听的socket_id,而是accept_ids[0]中请求的fd。并返回events大于0.
  11. 当再次进行FD_ISSET(socket_fd_&fd_sets)判断时,由于触发的事件并非侦听的scoket,返回的值为0。
  12. 进入第二个循环,开始处理消息的接收和发送。因为只有一个连接,此时maxpostion为1,在进行(accept_fds[k] != -1) && FD_ISSET(accept_fds[k],&fd_sets)判断时,accept_fds[0]为第一个请求的文件描述符(fd),第二个判断条件FD_ISSET()由于触发的是accept_fds[0]的请求,所以判断为真。
  13. recv接收该客户端的数据,send()发送给改连接数据。
  14. 跳出该循环,又回到最外层的for循环重新设置,当执行到select()函数时又被阻塞了。当有新的事件时又被唤醒,如此反复....
http://www.lryc.cn/news/2412925.html

相关文章:

  • Matlab中rectangle函数使用
  • oracle的round函数
  • 解决“ERROR:database “xxx“ is being accessed by other users“
  • Postgresql:prepared statement S_1 already exists
  • JSP课设:论坛管理系统(附源码+调试)
  • 未能加载文件或程序集“ICSharpCode.SharpZipLib, Version=1.3.3.11, Culture=neutral, PublicKeyToken=1b03e6acf1164f
  • 利用red5搭建一个简单的流媒体直播系统(ubuntu 12.10)
  • 基于SpringBoot的个人博客系统设计与实现
  • JavaScript——取消默认事件
  • 7-27 EDG nb(20 分)
  • 启动应用程序出现MSVCIRT.DLL找不到问题解决
  • 【lizhi125】Universal Extractor 万能解包器 - 从安装包解压提取绿色文件的工具
  • 用Python在QQ群里中快速发消息,以及连点器的制作
  • 十大经典策略之一 - Dual Thrust策略(期货)
  • 计算机毕业设计 asp.net精品课程网站 毕设
  • 一位老工程师的忠告
  • 软件工程期末考试题库(超全)
  • 最新Android架构师成长路线,,安卓已死
  • 毕业以后读书报告(不定时更新)
  • VC++适合做什么
  • Linux调试工具
  • Windows XP中的命令行命令
  • 关于visual studio 2005的中文版下载(最新详细下载点)
  • Bolt引擎内置的元对象介绍
  • 导航网站WP主题BlackCandy酷黑色高逼格
  • android 休眠唤醒机制分析(二) — early_suspend
  • HEAP: Free Heap block 0000028A24DF5A10 modified at 0000028A24DF5A50 after it was freed 正确解决方法
  • 将Lumaqq移植到Android中
  • Wireshark入门-Wireshark
  • 团队博客创建