C#에는 아주 다양한 클래스가 있습니다.

그리고, 전에 비동기 소켓을 만들 때 설명드렸던 것처럼, 통신에 특화된 클래스들도 많이 존재합니다.


이번 글에서는 웹 클라이언트(WebClient)를 이용한 파일 다운로드를 구현하는 방법을 소개해보자 합니다.


사용할 클래스는 System.Net.WebClient 입니다. (System.Net.WebClient 클래스, MSDN)

WebClient 클래스의 메서드를 보면 DownloadFile, DownloadFileAsync 이란 메서드가 있습니다.

이 메서드를 이용해서 파일을 다운로드 하는 방법을 설명드리도록 하겠습니다.

DownloadFile 메서드는 동기식이기 때문에 다운로드가 완료될 때까지 메세지가 처리되지 않으므로, DownloadFileAsync 메서드를 이용하여 실시간으로 다운로드 상태를 나타내도록 짜보도록 하겠습니다.


일단, 사용할 메서드의 매개 변수를 보도록 하겠습니다.

사용할 메서드는 DownloadFileAsync(Uri, String) 입니다.

public void DownloadFileAsync(Uri address, string fileName);


address 는 말 그대로 다운로드할 파일이 존재하는 웹상의 주소입니다.

fileName 은 다운로드한 파일이 저장될 위치를 입력합니다.



그리고, 파일 다운로드에서 아주 중요하게 사용되는 이벤트 두 개에 대해서 또 알아보겠습니다.

public event AsyncCompletedEventHandler DownloadFileCompleted;
public event DownloadProgressChangedEventHandler DownloadProgressChanged;

public delegate void AsyncCompletedEventHandler(Object sender, AsyncCompletedEventArgs e);
public delegate void DownloadProgressChangedEventHandler(Object sender, DownloadProgressChangedEventArgs e);


DownloadFileCompleted 이벤트는 비동기 파일 다운로드 작업이 완료된 경우에 발생하고, DownloadProgressChanged 이벤트는 비동기 파일 다운로드 진행상태가 변경되면 발생하게 됩니다.

* 단, 주의할 점은 이 이벤트들은 모두 비동기로 발생하는 것이기 때문에 컨트롤의 속성에 접근하거나 메서드를 호출할 때, 컨트롤이 생성된 스레드와는 별개의 스레드에서 작동되기 때문에 스레드에 안전한 (=Cross Safe) 메서드를 만들어 컨트롤에 접근해야 합니다.



그럼, 스레드에 안전하게 작업을 하기 위해 몇 개의 대리자를 선언하도록 하겠습니다.

private delegate void CSafeSetText(string text);
private delegate void CSafeSetMaximum(Int32 value);
private delegate void CSafeSetValue(Int32 value);


CSafeSetText 대리자는 폼의 제목을 설정하는데 사용되고,

CSafeSetMaximum 대리자는 프로그레스바의 최대치를 설정하는데 사용되며,

CSafeSetValue 대리자는 프로그레스바의 값을 설정하는데 사용됩니다.



그럼 이제 다시 WebClient 로 돌아갈 차례입니다.

다행스럽게도 WebClient 클래스의 생성자는 단 한 개의 매개 변수도 요구하지 않습니다.

그렇다는 말은, new 키워드를 사용하여 간단하게 개체를 만들 수 있다는 것이지요~!!

WebClient 클래스의 새 개체를 만든 후에는 뭘 해야할까요?

간단합니다. 웹 주소를 넘겨주고, DownloadFileAsync 메서드를 호출하기만 하면 끝입니다.

WebClient wc = new WebClient();
wc.DownloadFileAsync(new Uri(@"원격 주소"), @"파일이 저장될 위치");


위처럼 간단하게 호출이 가능합니다.

그럼, 폼을 디자인해보도록 할까요??


이렇게 간단하게 디자인하기로 했습니다.

프로그레스바 하나하고, 텍스트박스 하나, 라벨과 커맨드 버튼 각각 2개씩


폼의 AcceptButton 속성을 btnStart (다운로드 버튼) 으로 설정했습니다.

(Form.AcceptButton 속성, MSDN)

AcceptButton 속성은 폼에 엔터 키가 눌린 경우에 해당되는 버튼을 자동으로 눌러줍니다. (Click)

그리고, btnStart 버튼을 클릭했을 때의 명령은 이렇게 코딩했습니다.

void BtnStartClick(object sender, EventArgs e) {
    
    if ( nowDownloading ) {
        MessageBox.Show("이미 다운로드가 진행 중입니다.""오류", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        return;
    }
    
    String remoteAddress = txtAddress.Text.Trim();
    if ( String.IsNullOrEmpty(remoteAddress) ) {
        MessageBox.Show("주소가 입력되지 않았습니다.""오류", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
        return;
    }
    
    // 파일이 저장될 위치를 저장한다.
    String fileName = String.Format("C:\\downloadedFiles\\{0}", System.IO.Path.GetFileName(remoteAddress));
    
    // 폴더가 존재하지 않는다면 폴더를 생성한다.
    if ( !System.IO.Directory.Exists("C:\\downloadedFiles") )
        System.IO.Directory.CreateDirectory("C:\\downloadedFiles");
    
    try {
        
        // C 드라이브 밑의 downloadFiles 폴더에 파일 이름대로 저장한다.
        wc.DownloadFileAsync(new Uri(remoteAddress), fileName);
        
        // 다운로드 중이라는걸 알리기 위한 값을 설정하고,
        // 프로그레스바의 크기를 0으로 만든다.
        // 그리고, 텍스트박스 및 시작 버튼을 비활성화시켜서 작업에 중복이 되지 않도록 한다.
        prgDownload.Value = 0;
        setBaseSize = false;
        nowDownloading = true;
        btnStart.Enabled = false;
        txtAddress.Enabled = false;
        
    } catch (Exception ex) {
        MessageBox.Show(ex.Message, ex.GetType().FullName, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
    }
}


DownloadFileAsync 메서드는 폴더를 자동으로 생성해주지 않으므로, 디렉터리가 있는지 확인한 후, 디렉터리가 없을 경우엔 디렉터리를 생성해줘야 합니다.

디렉터리 생성 작업이 끝나면, 비동기 다운로드를 시작합니다.

그리고 파일의 바이트 크기 만큼 프로그레스 바의 최대치(Maximum)를 설정하고, 파일을 다운로드 중이라는 것을 알리기 위해 변수를 설정합니다.


각 이벤트에 대한 콜백은 이렇게 코딩했습니다. (DownloadProgressChanged, DownloadFileCompleted)

void fileDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) {

    // e.BytesReceived
    //   받은 데이터의 크기를 저장합니다.
    
    // e.TotalBytesToReceive
    //   받아야 할 모든 데이터의 크기를 저장합니다.
    
    // 프로그레스바의 최대 크기가 정해지지 않은 경우,
    // 받아야 할 최대 데이터 량으로 설정한다.
    if ( !setBaseSize ) {
        CrossSafeSetMaximumMethod((int) e.TotalBytesToReceive);
        setBaseSize = true;
    }
    
    // 받은 데이터 량을 나타낸다.
    CrossSafeSetValueMethod((int) e.BytesReceived);
    
    // 받은 데이터 / 받아야할 데이터 (퍼센트) 로 나타낸다.
    CrossSafeSetTextMethod(String.Format("{0:N0} / {1:N0} ({2:P})", e.BytesReceived, e.TotalBytesToReceive, (Double)e.BytesReceived / (Double)e.TotalBytesToReceive));
}

void fileDownloadCompleted(object sender, AsyncCompletedEventArgs e) {
    nowDownloading = false;
    btnStart.Enabled = true;
    txtAddress.Enabled = true;
    MessageBox.Show("파일 다운로드 완료!""오류", MessageBoxButtons.OK, MessageBoxIcon.Information);
}


이 프로그램에 대한 프로젝트 파일입니다. (압축)

SimpleDownloader.zip


위 소스 코드를 분석하다 보시면 프로그램이 어떻게 돌아가는지 파악하실 수 있을 것 같네요!


글 읽어 주셔서 감사합니다!!

모르는 점, 틀린 점, 지적 사항 등은 댓글로 남겨주시면 바로 반영하도록 하겠습니다!

+ Recent posts