• 网络编程:网络超时检测(select poll setsockopt alarm)


    一、网络超时检测

    1.1 概念

    阻塞:

    以读阻塞为例,如果缓冲区中有内容,则程序正常执行,

    如果缓冲区没有内容,程序会一直阻塞,直到有内容,读取内容继续向下运行。

    非阻塞:

    以读阻塞为例,如果缓冲区中有内容,则程序正常执行,

    如果缓冲区没有内容,程序会立即返回,然后继续向下运行。

    超时检测:

    介于阻塞和非阻塞之间,需要设置一定的时间,如果时间到达之前,缓冲区中没有数据

    程序会阻塞,如果时间到了,缓冲区中还没有数据,则程序立即返回,继续向下执行。

    二超时检测的方法:

    2.3以下所用程序的客户端:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define N 128
    11. #define ERRLOG(msg) do{\
    12. printf("%s,%s,[%d]\n", __FILE__, __func__, __LINE__);\
    13. perror(msg);\
    14. exit(-1);\
    15. }while(0)
    16. int main(int argc, char const *argv[])
    17. {
    18. if (argc != 3){
    19. ERRLOG("输入参数错误\n");
    20. }
    21. int socketfd;
    22. if (-1 == (socketfd = socket(AF_INET, SOCK_STREAM, 0))){
    23. ERRLOG("socket fd error");
    24. }
    25. struct sockaddr_in clientaddr;
    26. clientaddr.sin_family = AF_INET;
    27. clientaddr.sin_addr.s_addr = inet_addr(argv[1]);
    28. clientaddr.sin_port = htons(atoi(argv[2]));
    29. socklen_t clientaddr_len = sizeof(clientaddr);
    30. if (-1 == connect(socketfd,(struct sockaddr*)&clientaddr, clientaddr_len)){
    31. ERRLOG("connect error");
    32. }
    33. printf("连接成功...\n");
    34. char buff[N];
    35. while (1){
    36. memset(buff, 0, sizeof(buff));
    37. printf("请输入>>");
    38. fgets(buff, N, stdin);
    39. buff[strlen(buff) -1] = '\0';
    40. if (-1 == send(socketfd, buff, strlen(buff), 0)){
    41. ERRLOG("send error");
    42. }
    43. if (strncmp("quit", buff, 5) == 0){
    44. break;
    45. }
    46. memset(buff, 0, sizeof(buff));
    47. if (-1 == recv(socketfd, buff, sizeof(buff), 0)){
    48. ERRLOG("recv error");
    49. }
    50. printf("%s\n", buff);
    51. }
    52. close(socketfd);
    53. return 0;
    54. }

    2.2select函数自带超时检测

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #define N 128
    12. #define ERRLOG(msg) do{\
    13. printf("%s,%s,[%d]\n", __FILE__, __func__, __LINE__);\
    14. perror(msg);\
    15. exit(-1);\
    16. }while(0)
    17. int main(int argc, char const *argv[])
    18. {
    19. if (argc != 3){
    20. printf("输入格式错误\n");
    21. exit(-1);
    22. }
    23. int socketfd;
    24. if (-1 == (socketfd = socket(AF_INET, SOCK_STREAM, 0))){
    25. ERRLOG("socketfd erro");
    26. }
    27. struct sockaddr_in serveraddr;
    28. memset(&serveraddr, 0, sizeof(serveraddr));
    29. serveraddr.sin_family = AF_INET;
    30. serveraddr.sin_port = htons(atoi(argv[2]));
    31. serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    32. socklen_t serveraddr_len = sizeof(serveraddr);
    33. if (-1 == bind(socketfd, (struct sockaddr*)&serveraddr, serveraddr_len)){
    34. ERRLOG("bind error");
    35. }
    36. if (-1 == listen(socketfd, 5)){
    37. ERRLOG("listen error");
    38. }
    39. int nbytes;
    40. int ret;
    41. int i;
    42. int maxfd = 0;
    43. int acceptfd;
    44. char buff[128] = {0};
    45. fd_set readfs;
    46. fd_set readfs_temp;
    47. FD_ZERO(&readfs);
    48. FD_ZERO(&readfs_temp);
    49. FD_SET(socketfd, &readfs);
    50. maxfd = maxfd > socketfd ? maxfd: socketfd;
    51. struct timeval tm;
    52. while (1){
    53. readfs_temp = readfs;
    54. tm.tv_sec = 5;
    55. tm.tv_usec = 0;
    56. if (-1 == (ret = select(maxfd + 1, &readfs_temp, NULL, NULL, &tm))){
    57. ERRLOG("select error");
    58. } else if (ret == 0){
    59. printf("timeout.....\n");
    60. } else {
    61. for (i = 3; i < maxfd + 1 && ret != 0; i++){
    62. if (FD_ISSET(i, &readfs_temp)){
    63. if (i == socketfd){
    64. if (-1 == (acceptfd = accept(i, NULL, NULL))){
    65. ERRLOG("acceptfd error");
    66. }
    67. printf("111111111\n");
    68. FD_SET(acceptfd, &readfs);
    69. maxfd = maxfd > acceptfd ? maxfd : acceptfd;
    70. } else {
    71. memset(buff, 0, sizeof(buff));
    72. if (-1 == (nbytes = recv(i, buff, sizeof(buff), 0))){
    73. ERRLOG("recv error");
    74. } else if(nbytes == 0){
    75. printf("断开链接...\n");
    76. FD_CLR(i, &readfs);
    77. close(i);
    78. continue;
    79. }
    80. if (!strncmp("quit", buff, 5)){
    81. printf("退出链接...\n");
    82. FD_CLR(i, &readfs);
    83. close(i);
    84. continue;
    85. }
    86. printf("%s\n", buff);
    87. strcat(buff, "hhhh");
    88. if (-1 == send(i, buff, sizeof(buff), 0)){
    89. ERRLOG("send error");
    90. }
    91. }
    92. ret--;
    93. }
    94. }
    95. }
    96. }
    97. close(socketfd);
    98. return 0;
    99. }

    2.2poll函数也自带超时器

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #define ERRLOG(msg) do{\
    12. printf("%s %s(%d):", __FILE__, __func__, __LINE__);\
    13. perror(msg);\
    14. exit(-1);\
    15. }while(0)
    16. #define N 128
    17. int main(int argc, char const *argv[])
    18. {
    19. if (argc != 3){
    20. printf("输入过程\n");
    21. exit(-1);
    22. }
    23. int socketfd;
    24. if (-1 == (socketfd = socket(AF_INET, SOCK_STREAM, 0))){
    25. ERRLOG("socket error");
    26. }
    27. struct sockaddr_in serveraddr;
    28. memset(&serveraddr, 0, sizeof(serveraddr));
    29. serveraddr.sin_family = AF_INET;
    30. serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    31. serveraddr.sin_port = htons(atoi(argv[2]));
    32. socklen_t serveraddr_len = sizeof(serveraddr);
    33. if (-1 == bind(socketfd, (struct sockaddr*)&serveraddr, serveraddr_len)){
    34. ERRLOG("bind error");
    35. }
    36. if (-1 == listen(socketfd, 5)){
    37. ERRLOG("listen error");
    38. }
    39. int maxfd = 0;
    40. int ret = 0;
    41. int nbytes = 0;
    42. int i;
    43. int j;
    44. int acceptfd;
    45. char buff[128];
    46. struct pollfd my_fds[1024];
    47. for (i = 0; i < 1024; i++){
    48. my_fds[i].fd = -1;
    49. }
    50. my_fds[0].fd = socketfd;
    51. my_fds[0].events |= POLLIN;
    52. maxfd = maxfd > socketfd ? maxfd : socketfd;
    53. while (1){
    54. if (-1 == (ret = poll(my_fds, maxfd, 5000))){
    55. } else if (ret == 0){
    56. printf("time out...\n");
    57. } else {
    58. for (i = 0; i <= maxfd && ret != 0; i++){
    59. if (my_fds[i].revents & POLLIN != 0){
    60. if (my_fds[i].fd == socketfd){
    61. if (-1 == (acceptfd = accept(socketfd, NULL, NULL))){
    62. ERRLOG("accepfd error");
    63. }
    64. printf("客户端[%d]连接了\n", acceptfd);
    65. maxfd = maxfd > acceptfd ? maxfd : acceptfd;
    66. for (j = 0; j < 1024 ; j++){
    67. if (my_fds[j].fd == -1){
    68. my_fds[j].fd = acceptfd;
    69. my_fds[j].events = POLLIN;
    70. break;
    71. }
    72. }
    73. if (j == 1024){
    74. printf("满了\n");
    75. close(acceptfd);
    76. }
    77. }else {
    78. memset(buff, 0, sizeof(buff));
    79. if (-1 == (nbytes = recv(my_fds[i].fd, buff, 128, 0))){
    80. ERRLOG("recv error");
    81. }else if (nbytes == 0){
    82. printf("断开连接...\n");
    83. close(my_fds[i].fd);
    84. my_fds[i].fd = -1;
    85. continue;
    86. }
    87. if (!strncmp("quit", buff, 5)){
    88. printf("退出连接..\n");
    89. printf("断开连接...\n");
    90. close(my_fds[i].fd);
    91. my_fds[i].fd = -1;
    92. continue;
    93. }
    94. printf("%s\n", buff);
    95. strcat(buff, "jhhh");
    96. if (-1 == send(my_fds[i].fd, buff, sizeof(buff), 0)){
    97. ERRLOG("send errro");
    98. }
    99. }
    100. }
    101. }
    102. }
    103. }
    104. close(socketfd);
    105. return 0;
    106. }

    2.3使用 setsockopt 函数实现超时检测

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #define ERRLOG(msg) do{\
    14. printf("%s %s(%d):", __FILE__, __func__, __LINE__);\
    15. perror(msg);\
    16. exit(-1);\
    17. }while(0)
    18. #define N 128
    19. typedef struct _MSG{
    20. int acceptfd;
    21. struct sockaddr_in clientaddr;
    22. }msg_t;
    23. void *deal_read_write(void *arg){
    24. msg_t msg = *(msg_t *)arg;
    25. char buff[N] = {0};
    26. int nbytes = 0;
    27. printf("客户端 [%s:%d] 连接了\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
    28. //收发数据
    29. while(1){
    30. memset(buff, 0, N);
    31. //接受数据
    32. //由已经设置过超时时间的sockfd产生的accpetfd会继承超时属性
    33. //如果想设置的超时时间是一样的,直接使用即可
    34. //如果不想设置一样,也可以对每个acceptfd单独调用 setsockopt 进行设置
    35. if(-1 == (nbytes = recv(msg.acceptfd, buff, 128, 0))){
    36. if(errno == EAGAIN){
    37. printf("recv timeout..\n");
    38. close(msg.acceptfd);
    39. break;
    40. }
    41. perror("recv error");
    42. break;
    43. }else if(0 == nbytes){
    44. printf("[%s:%d]:断开了连接...\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
    45. break;
    46. }
    47. if(!strncmp(buff, "quit", 5)){
    48. printf("[%s:%d]:退出了...\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port));
    49. break;
    50. }
    51. //打印数据
    52. printf("[%s:%d]:[%s]\n", inet_ntoa(msg.clientaddr.sin_addr), ntohs(msg.clientaddr.sin_port), buff);
    53. //组装应答
    54. strcat(buff, "--hqyj");
    55. //发送应答
    56. if(-1 == send(msg.acceptfd, buff, N, 0)){
    57. ERRLOG("send error");
    58. }
    59. }
    60. close(msg.acceptfd);
    61. pthread_exit(NULL);
    62. }
    63. int main(int argc, const char *argv[]){
    64. if(3 != argc){
    65. printf("Usage: %s \n", argv[0]);
    66. return -1;
    67. }
    68. //创建流式套接字
    69. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    70. if(-1 == sockfd){
    71. ERRLOG("socket error");
    72. }
    73. //填充服务器网络信息结构体
    74. struct sockaddr_in serveraddr;
    75. memset(&serveraddr, 0, sizeof(serveraddr));
    76. serveraddr.sin_family = AF_INET;
    77. //网络字节序的端口号 8888 9999 6789 等 都可以
    78. serveraddr.sin_port = htons(atoi(argv[2]));
    79. //网络字节序的IP地址,IP地址不能乱填
    80. //自己的主机ifconfig 查到的ip地址是多少就填多少
    81. //如果本机测试使用 也可以填写 127.0.0.1
    82. serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    83. socklen_t serveraddr_len = sizeof(serveraddr);
    84. //设置允许端口复用
    85. int on = 1;
    86. if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
    87. perror("setsockopt error");
    88. }
    89. //将套接字和网络信息结构体绑定
    90. if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
    91. ERRLOG("bind error");
    92. }
    93. //将套接字设置成被动监听状态
    94. if(-1 == listen(sockfd, 5)){
    95. ERRLOG("listen error");
    96. }
    97. //设置sockfd接收超时时间为 5s
    98. struct timeval tm;
    99. tm.tv_sec = 5;
    100. tm.tv_usec = 0;
    101. if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tm, sizeof(tm))){
    102. ERRLOG("setsockopt error");
    103. }
    104. //定义一个保存客户端信息的结构体
    105. struct sockaddr_in clientaddr;
    106. memset(&clientaddr, 0, sizeof(clientaddr));
    107. socklen_t clientaddr_len = sizeof(clientaddr);
    108. msg_t msg;
    109. pthread_t tid = 0;
    110. //阻塞等待客户端连接
    111. int acceptfd = 0;
    112. while(1){
    113. if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
    114. if(errno == EAGAIN){
    115. printf("5秒内没有客户端连接到服务器..\n");
    116. continue;
    117. }
    118. ERRLOG("acceptfd error");
    119. }
    120. memset(&msg, 0, sizeof(msg));
    121. msg.clientaddr = clientaddr;
    122. msg.acceptfd = acceptfd;
    123. //有客户端连接就创建线程专门处理读写
    124. if(0 != pthread_create(&tid, NULL, deal_read_write, (void *)&msg)){
    125. ERRLOG("pthread_create error");
    126. }
    127. //设置线程分离属性 由操作系统回收线程的资源
    128. pthread_detach(tid);
    129. }
    130. close(sockfd);
    131. return 0;
    132. }

    2.4使用 alarm 闹钟实现超时检测

    2.4.1 概念

    alarm是一个函数,可以通过他设置一个时间,当时间到达的时候,会给进程发一个

    SIGALRM 信号。

    使用alarm函数设置完超时时间后,进程会继续执行,直到设置的时间到了,

    进程收到 SIGALRM 信号

    进程对该信号默认的处理方式是 终止进程,我们的是服务器程序,不能使用默认的方式

    所以需要对 该信号进行一个捕捉的操作。

    如果使用 捕捉的操作,当信号产生时,回去执行信号处理函数,执行完信号处理函数

    进程会继续向下执行,这种属性,叫做信号的自重启属性。

    我们想要使用该信号实现超时检测,也就是说,执行完信号处理函数,不应该直接向下运行

    而是应该给我们返回一个错误,也就是说,需要关闭信号的自重启属性。

    2.4.2关闭自重启属性的方式,使用 sigaction 函数

    1. struct sigaction action;
    2. //获取旧的行为
    3. sigaction(SIGALRM, NULL, &action);
    4. //取消自重启属性
    5. action.sa_flags &= (~SA_RESTART);
    6. //指定信号处理函数
    7. action.sa_handler = my_function;
    8. //再将行为重新设置回去
    9. sigaction(SIGALRM, &action, NULL);

    2.4.3代码实现

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #define ERRLOG(msg) do{\
    13. printf("%s %s(%d):", __FILE__, __func__, __LINE__);\
    14. perror(msg);\
    15. exit(-1);\
    16. }while(0)
    17. #define N 128
    18. void my_function(int x){
    19. //什么都不用作 我们只是通过这种方法捕获信号
    20. printf("helle\n");
    21. }
    22. int main(int argc, const char *argv[]){
    23. if(3 != argc){
    24. printf("Usage: %s \n", argv[0]);
    25. return -1;
    26. }
    27. //创建流式套接字
    28. int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    29. if(-1 == sockfd){
    30. ERRLOG("socket error");
    31. }
    32. //填充服务器网络信息结构体
    33. struct sockaddr_in serveraddr;
    34. memset(&serveraddr, 0, sizeof(serveraddr));
    35. serveraddr.sin_family = AF_INET;
    36. //网络字节序的端口号 8888 9999 6789 等 都可以
    37. serveraddr.sin_port = htons(atoi(argv[2]));
    38. //网络字节序的IP地址,IP地址不能乱填
    39. //自己的主机ifconfig 查到的ip地址是多少就填多少
    40. //如果本机测试使用 也可以填写 127.0.0.1
    41. serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    42. socklen_t serveraddr_len = sizeof(serveraddr);
    43. //设置端口复用
    44. int on = 1;
    45. if(-1 == setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))){
    46. perror("setsockopt error");
    47. }
    48. //将套接字和网络信息结构体绑定
    49. if(-1 == bind(sockfd, (struct sockaddr *)&serveraddr, serveraddr_len)){
    50. ERRLOG("bind error");
    51. }
    52. //将套接字设置成被动监听状态
    53. if(-1 == listen(sockfd, 5)){
    54. ERRLOG("listen error");
    55. }
    56. //定义一个保存客户端信息的结构体
    57. struct sockaddr_in clientaddr;
    58. memset(&clientaddr, 0, sizeof(clientaddr));
    59. socklen_t clientaddr_len = sizeof(clientaddr);
    60. char buff[N] = {0};
    61. int nbytes = 0;
    62. //关闭SIGALRM信号的自重启属性
    63. struct sigaction action;
    64. //获取旧的行为
    65. sigaction(SIGALRM, NULL, &action);
    66. //取消自重启属性
    67. action.sa_flags &= (~SA_RESTART);
    68. //指定信号处理函数
    69. action.sa_handler = my_function;
    70. //再将行为重新设置回去
    71. sigaction(SIGALRM, &action, NULL);
    72. //阻塞等待客户端连接
    73. int acceptfd = 0;
    74. while(1){
    75. alarm(5);
    76. if(-1 == (acceptfd = accept(sockfd, (struct sockaddr *)&clientaddr, &clientaddr_len))){
    77. if(errno == EINTR){
    78. printf("accept timeout..\n");
    79. continue;
    80. }
    81. ERRLOG("acceptfd error");
    82. }
    83. printf("客户端 [%s:%d] 连接了\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    84. //收发数据
    85. while(1){
    86. memset(buff, 0, N);
    87. //接受数据
    88. alarm(5);
    89. if(-1 == (nbytes = recv(acceptfd, buff, 128, 0))){
    90. if(errno == EINTR){
    91. printf("recv timeout..\n");
    92. break;
    93. }
    94. ERRLOG("recv error");
    95. }else if(0 == nbytes){
    96. printf("[%s:%d]:断开了连接...\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    97. break;
    98. }
    99. if(!strncmp(buff, "quit", 5)){
    100. printf("[%s:%d]:退出了...\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));
    101. break;
    102. }
    103. //打印数据
    104. printf("[%s:%d]:[%s]\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port), buff);
    105. //组装应答
    106. strcat(buff, "--hqyj");
    107. //发送应答
    108. if(-1 == send(acceptfd, buff, N, 0)){
    109. ERRLOG("send error");
    110. }
    111. }
    112. //关闭套接字
    113. close(acceptfd);
    114. }
    115. //这句代码一般不会执行到
    116. //服务器程序一般不会主动退出
    117. close(sockfd);
    118. return 0;
    119. }

  • 相关阅读:
    springboot二手书籍线上回收网站java ssm-0401u
    大数据技术基础实验十三:Kafka实验——订阅推送示例
    Maven—POM简介
    框架中的单例模式
    天龙八部科举答题问题和答案(全1/8)
    浅谈终端安全接入
    前端开发调试技巧
    受害者被锤 法官遭殃 背后的它公关赢了?
    聊起日本文化大家首先会想起什么
    通达OA V12版本,好用的自定义函数
  • 原文地址:https://blog.csdn.net/a2998658795/article/details/126449828