1. 程式人生 > >遇到一個因socket未關閉引發的檔案控制代碼用完問題

遇到一個因socket未關閉引發的檔案控制代碼用完問題

“愛提踢斯”專案最近遇到一個問題,當FTP伺服器磁碟沒有空間時,裝置會不斷復位——這是測試人員反饋的。我們拿到log後,看到一個通訊所用的檔案開啟失敗。不斷列印Too many open file,然後超時裝置復位。同時我們看到資料庫檔案開啟失敗,無法寫入資料。一個現象,看到好幾處問題。還是從最初的表現來入手。雖然把bug指派給別人,但從時間、進度上考慮,週末還是去加班。而最後,解決了問題。根據老夫目測,是FTP的socket未關閉引起的。

linux系統的檔案控制代碼(我很想說“檔案描述符”,但“控制代碼”似乎更多人使用)是有限制的。檢視系統支援控制代碼最大值用如下命令:
# ulimit -n
1024

可見,預設是1024。

下面用程式碼來說明一下問題。程式碼如下:

/**
系統一次允許最大的檔案控制代碼為1024。
open foobar file for: 340
open bar file for: 340
open foo file for: 341
open foobar file failed: : Too many open files
open BAR file failed: : Too many open files

# ulimit  -a
...
open files                      (-n) 1024
...
*/

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

int foo(void)
{
    static int cnt = 1;
    int ret = 0;
    int fd =-1;
	char buf[5] = {0};
    char read_buf[5] = {0};
    fd = open("/tmp/FOO.txt",O_RDWR|O_CREAT,0666);
    if(fd < 0)
    {
        perror("open FOO file failed: ");
        return -1;
    }
    printf("open foo file for: %d\n", cnt++);
	return 0;
}

int bar(void)
{
    static int cnt = 1;
    int ret = 0;
    int fd =-1;
	char buf[5] = {0};
    char read_buf[5] = {0};
    fd = open("/tmp/BAR.txt",O_RDWR|O_CREAT,0666);
    if(fd < 0)
    {
        perror("open BAR file failed: ");
        return -1;
    }
    printf("open bar file for: %d\n", cnt++);
	return 0;
}

int foobar(void)
{
    static int cnt = 1;
    int ret = 0;
    int fd =-1;
	char buf[5] = {0};
    char read_buf[5] = {0};
    fd = open("/tmp/foobar.txt",O_RDWR|O_CREAT,0666);
    if(fd < 0)
    {
        perror("open foobar file failed: ");
        return -1;
    }
    printf("open foobar file for: %d\n", cnt++);
	return 0;
}

int main(void)
{
    int ret = 0;
    while (1) {
    foo();usleep(1);
    foobar();usleep(1);
    ret = bar();
    // if (ret < 0) break; // 不讓其退出
    usleep(100);
    }
    return 0;
}

要檢視這個程序佔用檔案控制代碼數,先獲取該程序PID:
# ps -ef | grep a.out     
root     17835  7615 17 23:01 pts/1    00:00:00 ./a.out

我們檢視該程序檔案控制代碼最大值:
# cat /proc/17835/limits | grep "files"
Max open files            1024                 4096                 files  
和系統預設值一樣。檢視已佔用值:
# ll /proc/17835/fd | wc -l 
1027

我們看到,已經佔用上千個控制代碼,一直未釋放。當達到系統最大值時,就會報Too many open files的錯誤。

匆匆已然四載,不曾想到,當年剛入職搞FTP的我,竟然會埋下地雷,讓今天的我不幸踩中。在沒有離職情況下,只好義無反顧地去解決bug。——而這個bug,正是因為未關閉socket造成的。

FTP客戶端的實現,是需要開啟2個socket的,一個是命令通道,一個是資料通道。在伺服器沒有磁碟空間情況下,write資料是返回錯誤的,使用FTP模組者認為是無法登陸,下次會再次嘗試登陸——在這種情況下,登陸是正常的,只是無法寫資料。但每次登陸,都會建立命令通道的socket,這導致了socket的洩漏,不會關閉,因為使用者並沒有呼叫logout函式退出登陸。另一個問題是最主要的,每次寫資料時,要建立資料通道的socket,但在出錯時,並沒有關閉資料通道的socket,而是直接返回。這再次導致了socket洩漏。找到了原因,解決起來就好辦了。

自從知道可以檢視某個程序佔用的檔案控制代碼,我去看看以前的裝置,發現有個別檔案佔用控制代碼較多——也就幾十個。目測是隻開啟但未關閉。出於熱心,我把情況在部門群裡說了,至於後續的事,因為那些不是我的職責範圍,不敢越趄代庖。

2015.8.1 李遲