실전 스마트클라이언트 그림판 을 끝으로 관련 단원을 마무리 하려다가, 그림판 샘플도 많이 아쉽고, 허접하여 한가지 샘플을 더 작성해 보았다.
이번에 소개할 샘플도 그림판 샘플과 거의 같은 맥락이지만, 좀 더 난이도가 있는 샘플을 만들어 보았다.
 
ActiveX 로만 보아왔던 업로드/다운로드 컨트롤을 스마트클라이언트로 만들어 볼 것이다.
업로드 스마트클라이언트 구성
 
샘플을 업로드/다운로드가 모두 가능한 컨트롤로 만들어 볼까 해봤었는데, 시간 관계상, 샘플이 비대해 질 것 같아서 업로드만 가능하도록 구성해 보았다.
데이터는 HTTP 를 통해 전송될 것이고, 서버측에서 데이터를 받도록 하였다.
소켓을 통해 사용자별 전송속도를 제한하려 하였으나, 본 주제와 많이 벗어나게 되므로, 단순히 HTTP 로 구성하게 되었다.
 
 
보안 설정 및 샘플 미리보기
 
CAS 보안설정 배포
http://umc.pe.kr/umccas/UmcCas.application ( CAS 보안설정 후 브라우져를 재시작 하세요 )
 
다중파일 업로드 스마트클라이언트 샘플
 
독립 스마트클라이언트로 작동하는 샘플 ( 작동오류시, 신뢰 할 수 있는 사이트로 등록 )
http://umc.pe.kr/sample/fileuploadsmartclient/umc.fileuploadctrl.win.exe

파일업로드 스크린샷


UploadFileInfo 클래스
 
UploadFileInfo 클래스는 업로드 하기위한 파일의 간략한 정보를 저장하는 클래스이다.
그렇게 중요한 부분은 아니므로 소스만 대충 훓어보면 될 것 같다.
 

public class UploadFileInfo
{
         private string filename;
         private string fullname;
         private long filesize;
 
         public UploadFileInfo(string filename, string fullname, long filesize)
         {
                  this.filename = filename;
                  this.fullname = fullname;
                  this.filesize = filesize;
         }
 
         public string FileName
         {
                  get { return filename; }
                  set { filename = value; }
         }
 
         public string FullName
         {
                  get { return fullname; }
                  set { fullname = value; }
         }
 
         public long FileSize
         {
                  get { return filesize; }
                  set { filesize = value; }
         }
}
UploadFileInfo 클래스

 
 
파일 추가/삭제
 
UserControl 에 ListView 컨트롤을 올리자. 파일추가/삭제 의 버튼의 액션에 따라, OpenFileDialog 를 띄워 파일을 추가하고, 삭제버튼을 통해 ListView에서 추가된 파일을 삭제할 것이다.
이 부분은 중요한 부분이 아니므로, 첨부된 소스를 참고하기 바란다.
 
 
작업 진행율을 위한 frmProgress 폼 디자인
 
frmProgress 에서 단 한줄의 코드도 추가하지 않았다. 개인적으로 샘플로 제공되는 소스가 3개이상의 클래스로 분리되어 있는 것을 좋아하지 않아서, 모든 이벤트는 UserControl 에 구현하였다.
 

frmProgress 폼 디자인

 
 
스레드를 통한 비동기 업로드 시작
 
업로드가 진행되는 동안, UI 의 변화가 지속적으로 변화됨과 사용자의 취소 작업이 이루어 질 수 있다. 때문에 스레드를 통해 업로드 작업이 진행되지 않으면, 사용자는 UI, 즉, 취소 버튼을 클릭할 수 없게 된다.
 
다음은 “전송” 버튼에서 호출하는 Upload() 메서드이다.
 

public void Upload()
{
         …
         fileProgressBytes = progressBytes = uploadingFilesCount = 0;
         frmProgress = new frmFileUploadProcess();
         frmProgress.btn.Click += new EventHandler(btn_Click);
         frmProgress.FormClosing += new FormClosingEventHandler(frmProgress_FormClosing);
         frmProgress.Show();
 
         // 스레드로 Upload 를 진행한다.
         OnUploadStart();
         thread = new System.Threading.Thread(new System.Threading.ThreadStart(UploadThread));
         thread.Start();
}
Upload() 메서드

 
 
진행율을 표시하는 UploadThread 메서드
 
아마 다중파일 업로드의 가장 중요한 부분의 메서드가 UploadThread 가 아닌가 싶다.
WebClient 클래스를 이용해 비동기로 데이터를 전송하는 방법과 전송중 대기하는 방법이 구현되어 있다.
 

private void UploadThread()
{
         WebClient wc = new WebClient();
         wc.UploadProgressChanged += new UploadProgressChangedEventHandler(wc_UploadProgressChanged);
         wc.UploadFileCompleted += new UploadFileCompletedEventHandler(wc_UploadFileCompleted);
 
         int cnt = 0;
         foreach (UploadFileInfo info in filesInfo)
         {
                  try
                  {
                           isBusy = true;
                           OnUploadFileChanged(info.FileName);
                           SetFileTitle(string.Format("{0} {1}/{2}", info.FileName, ++cnt, filesInfo.Count));
                           wc.UploadFileAsync(new Uri(UploadUrl + "?filename=" + info.FileName), "POST", info.FullName);
 
                           // 파일이 전송중이라면 끝날때 까지 다음파일을 전송하지 않는다.
                           while (isBusy) { }
                  }
                  catch (System.Threading.ThreadAbortException)
                  {
                  }
                  catch (WebException ex)
                  {
                           MessageBox.Show("서버에 연결할 수 없습니다\n" + ex.Message);
                  }
                  catch (Exception ex)
                  {
                           MessageBox.Show(ex.Message);
                  }
         }
 
         SetFileProgress(100);
         SetTotalProgress(100);
 
         SetBtnText("완료");
 
         OnUploadCompleted();
 
         if (thread.IsAlive)
         {
             thread.Abort();
             thread.Join();
         }
 
}
비동기로 파일을 전송하는 UploadThread() 메서드

 
가장 우선적으로 보아야 할 부분 아래의 코드이다.
 
wc.UploadFileAsync(new Uri(UploadUrl + "?filename=" + info.FileName), "POST", info.FullName);
 
WebClient 는 비동기적으로 데이터를 전송 또는 요청하는 메서드는 ~Async 가 붙는다.
UploadFileAsync, UploadDataAsync, UploadValueAsync, DownloadFileAsync, DownloadDataAsync…
 
위의 나열한 ~Async 메서드는 다른 스레드와 독립적으로 비동기 요청이 발생한다.
 
MSDN 왈~

~Async 메서드는 호출 스레드를 차단하지 않습니다.

 
무슨말일까? 일반적으로 스레드는 다른 스레드와 교차점, 즉 크로스 스레드를 전혀 신경 쓰지 않아도 된다.
스레드와 스레드가 너무나도 평범하게(?) 교차하게 되면 Cross Thread 관련 Exception 이 발생하지만, ~Async 는 메인스레드(Upload 메서드가 생성하는 스레드)가 종료 되어도 아무런 Exception 이 발생하지 않는다.
 
즉, WebClient 의 ~Aysnc 메서드는 통제할 수 없는 스레드라는 것 정도만 염두해 두도록 하자.
 
~Async 메서드가 호출됨으로써, 드디어 우리가 원하는 이벤트를 발생할 수 있다.
 
가령, UploadFileAsync 메서드가 호출되었다면 비로서 WebClient 는
 
wc.UploadFileCompleted
wc.UploadProgressChanged
 
두가지 이벤트를 발생한다.
네이밍에서 암시하듯, 전송완료, 전송율변경 이벤트가 발생한다.
( 위에서도 말했듯이, UploadFile 메서드가 호출되면 절대로 발생하지 않는다 )
 
 
WebClient 의 비동기 작업 이벤트
 
그럼 위의 두 이벤트를 어떻게 구현했는지 코드를 살펴보자.
 

void wc_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
{
         OnUploadOneFileCompleted();
         progressBytes += fileProgressBytes;
         isBusy = false;
         uploadingFilesCount++;
}
 
void wc_UploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
{
         int percent = (int)(((float)e.BytesSent / (float)e.TotalBytesToSend) * (float)100);
         OnUploadFilePercentChanged( percent );
         //SetFileProgress(e.ProgressPercentage);
         SetFileProgress( percent );
 
         fileProgressBytes = e.BytesSent;
         SetTotalProgress((int)(((float)progressBytes + e.BytesSent )/ (float)totalBytes * (float)100));
}
UploadFileAsync 가 발생하는 두가지 이벤트 구현

UploadProgressChanged 이벤트에서 UploadProgressChangedEventArgse 이벤트 인자는
ProgressPercentage 프로퍼티가 제공이 된다.
현재 업로드중인 파일의 진행율을 퍼센테이지 단위로 값을 알 수 있지만,
이 역시 액면상 UI 와 안맞는 점이 있기 때문에, 전송된 용량으로 퍼센테이지를 구했다. 
wc_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
 
분명, 위에서 WebClient 의 ~Async 메서드는 비동기 전송 메서드라고 말한 바 있다.
즉, 모든 파일이 비동기적으로 전송되는 것을 방지하기 위해 isBusy 라는 맴버를 구현하였다.
 
UploadThread() 메서드에서
 
while (isBusy) { }
 
코드로 인해, 파일 전송이 완료 되면, 다음 파일을 전송하도록 하였다. 만약, 위 코드가 존재하지 않는다면, 전송될 모든 파일이 한꺼번에 전송되고 만다.
 
아마 눈치빠른 분이라면 WebClient 의
 
wc.IsBusy
 
프로퍼티가 제공된다. 하지만, 체감상 제대로 된 동기화가 되지 않는 것 같아 따로 isBusy 맴버를 선언하여 구현한 것 뿐이다.
 
 
UI 동기화 메서드
 
우리는 ProgressBar 가 변경되면 이 Value 값을 변경하여, UI 에 적용하여야 한다.
하지만, 사용자의 취소 버튼 클릭을 위해 Thread 로 전송버튼을 구현하였고, UserControl 과 frmProgress 의 서로다른 스레드의 UI 를 변경하기 위해 Cross Thread 를 구현해야 한다.
 
Cross Thread 를 위한 코드는 흔희 볼 수 있는 코드일 것이다.
 

private delegate void FileProgressHandler(int percent);
private void SetFileProgress(int percent)
{
         if (frmProgress.pbFileProgress.InvokeRequired)
         {
                  Invoke(new FileProgressHandler(SetFileProgress), percent);
         }
         else
         {
                  frmProgress.pbFileProgress.Value = percent;
         }
}
UI 변경 작업을 위해 Delegate 에서 UI 변경작업을 위임하는 코드

 
이외에도 몇가지 메서드가 더 존재하니, 샘플 코드를 통해 확인해 보도록 하자.
 
 
Thread 종료하기
 
Upload() 메서드에서
 
frmProgress.FormClosing+=new FormClosingEventHandler(frmProgress_FormClosing);
 
와 같이 FormClosing 이벤트를 등록되어있다.
 
코드를 보자.
 

void frmProgress_FormClosing(object sender, FormClosingEventArgs e)
{
         // 폼이 닫힐때 스레드를 종료한다.
         if (thread.IsAlive)
         {
                  thread.Abort();
                  thread.Join();
         }
}
폼이 닫힐 때 Thread 를 종료하도록 한다.

 
업로드 중 사용자에 의해 “취소” 버튼이 눌려지면, 스레드를 강제로 종료하도록 구현되어 있다.
 
Thread.Abort() 메서드의 MSDN 왈~

이 메서드가 호출되는 스레드에서 ThreadAbortException을 발생시켜 스레드 종료 프로세스를 시작합니다. 이 메서드를 호출하면 대개 스레드가 종료됩니다.

 
Thread.Abort() 메서드는 그다지 아름다운 코드는 아니다.
위에 설명을 보아도 “이 메서드를 호출하면 대게 스레드가 종료됩니다” 라고 한다.
 
어쨌든, Thread.Abort() 메서드를 호출하게 되면 ThreadAbortException 이 발생하게 되고, 스레드가 종료되는 시점을 catch 하여 스레드 종료에 관련된 코드를 삽입할 수 있다.
 
UploadThread() 메서드에서
 
catch (System.Threading.ThreadAbortException)
{
}
 
코드를 통해 스레드 종료에 대해 아무런 작업을 하지 않음을 알 수 있다.
저작자 표시 비영리 동일 조건 변경 허락
크리에이티브 커먼즈 라이선스
Creative Commons License
Posted by POWERUMC 엄준일(땡초)