【问题标题】:Terminating C program on command line, but make sure writer is finished writing在命令行上终止 C 程序,但确保编写器已完成编写
【发布时间】:2016-12-09 15:08:55
【问题描述】:

我有一个将数据包数据写入输出文件的 C 服务器。问题是我使用控制 c 终止了终端中的服务器,并且我注意到我的输出文件从未完成写入。有没有办法确保我的输出文件在退出之前被完全写入?

(我知道我可以让服务器运行更长时间,所以我确定我想要的数据已经写入,但我正在寻找替代方法)

谢谢

这是我的代码供参考。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <sys/time.h>

int PORT_NUM = 0; 
int RecordRate = 3000; 

typedef struct timeval timeval;
timeval time_;


void error(const char *msg)
{
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[])
{
     int sockfd, newsockfd, portno;
     socklen_t clilen;
     char buffer[1000000];
     struct sockaddr_in serv_addr, cli_addr;
     int n;
     FILE *fp;

     PORT_NUM = atoi(argv[1]);  
     fp = fopen(argv[2],"w");   

     sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if (sockfd < 0) 
            error("ERROR opening socket");
     bzero((char *) &serv_addr, sizeof(serv_addr));
     //portno = atoi(argv[1]);
     portno = PORT_NUM;
     serv_addr.sin_family = AF_INET;
     serv_addr.sin_addr.s_addr = INADDR_ANY;
     serv_addr.sin_port = htons(portno);
     if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
        error("ERROR on binding");
     listen(sockfd,10);
     clilen = sizeof(cli_addr);

     newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen);
     if (newsockfd < 0) 
            error("ERROR on accept");

    int counter = 0;    
    int total_bytes_sent = 0;

     while(1){  

         bzero(buffer,1000000);
         n = read(newsockfd,buffer,999999);

         if (n < 0) {
             error("ERROR reading from socket");
         }
         else if (n != 0) {
             total_bytes_sent += n;
             gettimeofday(&time_, NULL);

             if(counter%RecordRate==0){
             printf("counter  %d \n", counter); 
             fprintf(fp,"\"%d\",\"%ld\",\"%d\",\"%d\",\"%d\"\n", counter, time_.tv_sec, time_.tv_usec, n,total_bytes_sent);
             }

             counter++; 

             //print format: packet number, time Sec, time milli Sec, bytes in packet, total bytes sent 
            // example: "11182","1465921447","196422","3100","26821100"

         }
     }
     fclose(fp);
     close(newsockfd);
     close(sockfd);
     return 0; 
     }

【问题讨论】:

  • 一种选择是在执行输出后执行fflush(),以便将保存数据的任何文件输出缓冲区刷新到输出文件。 stackoverflow.com/questions/18170410/… 这可能会稍微降低性能,因为它会覆盖管理文件写入的操作系统以优化时间。
  • 另一种选择是拦截SIGINT 信号并在关闭前刷新缓冲区。 thegeekstuff.com/2012/03/catch-signals-sample-c-code。假设你在 linux 上。
  • @CHendrix 另一种选择是拦截SIGINT 信号并在关闭之前刷新缓冲区。 thegeekstuff.com/2012/03/catch-signals-sample-c-code。假设您使用的是 linux。 Ooof。在信号处理程序中唯一可以安全调用的函数是那些特别标记为异步信号安全的函数。 See the Linux signal man page Linux 上的异​​步信号安全函数列表。如果未列出该函数,则它不是异步信号安全的。 printf()(来自您的示例)和 fflush() 都不是异步信号安全的。

标签: c file file-writing


【解决方案1】:

您可以为 SIGINT 设置一个自定义处理程序,在其中设置一个标志,然后在设置该标志时优雅地退出。

#include <stdlib.h>
#include <signal.h>

volatile sig_atomic_t flag = 0;

void handle_int(int sig)
{
  flag = 1;
}

int main()
{
  signal(SIGINT, handle_int); // intercept SIGINT

  while (!flag)
  {
    // do work ...
  }
  // cleanup, fflush, etc...
  return 0;
}

请注意,直接在信号处理程序中调用fflush 是不安全的。

// do work 部分显然应该相对较短,以便在应用程序的整个生命周期中定期检查flag

【讨论】:

    【解决方案2】:

    在 Linux 中,您可以使用 EVIOCGKEY ioctl 检查是否按下了某个键,然后使用 break 退出 while (1) 循环。

    #include <fcntl.h>
    #include <linux/input.h>
    #include <sys/ioctl.h>
    
    int rdKey(int fd, int key) {//faster not to open file every time the function is called
    //  const char *keyboard = "/dev/input/by-path/platform-i8042-serio-0-event-kbd";
    //  int fd = open(keyboard, O_RDONLY);
        char arr[(KEY_MAX+7)/8];
        memset(arr, 0, sizeof(arr));
        ioctl(fd, EVIOCGKEY(sizeof(arr)), arr);
        return !!(arr[key/8] & (1<<(key % 8)));
    }
    

    您将传递输入设备(如键盘)的文件描述符,然后传递来自input.h 的键号或宏,如用于“a”的KEY_A,如果按下该键,它将返回1,并且0 如果没有。

    【讨论】:

      【解决方案3】:

      要考虑的另一种方法是让应用程序fork() 将子进程变为服务器,而父进程保持连接到终端等待输入,两者通过套接字或管道进行通信。

      在执行fork() 之前,父进程将创建一个套接字或管道,用于与子进程通信。然后,用户可以在仍连接到终端的父进程中键入命令,然后父进程将通过套接字或管道将命令回显给子进程。

      孩子将使用select() 并将通信套接字/管道包含在描述符列表中。因此,当您上面的代码仅使用带有 read() 的单个套接字时,您将改为使用 select() 与父级共享的套接字以及接受的套接字,并检查以确定是否存在来自父级的命令等待读取或更多服务器工作要做。然后,孩子可以进行任何需要的整理并干净地退出,而父母则执行wait() 以完成它,然后父母自行退出。

      抱歉,我手头没有任何代码示例。有关套接字和select() 的详细信息,请参阅以下stackoverflow。

      Is it possible (and safe) to make an accepting socket non-blocking?

      c++ select async programming

      Asynchronous C client for a multiclient C server

      How do I change a TCP socket to be non-blocking?

      working of fork in c language

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-05-10
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-07-23
        • 1970-01-01
        相关资源
        最近更新 更多