1. 程式人生 > >【Linux C/C++】 第08講 多執行緒TCP傳輸檔案/select模型

【Linux C/C++】 第08講 多執行緒TCP傳輸檔案/select模型

一、多執行緒

   pthread.h

   libpthread.so   -lpthread

   1.建立多執行緒

     1.1 程式碼

          回撥函式

     1.2 執行緒ID

         pthread_t

     1.3 執行執行緒

           pthread_create

           int pthread_create(pthread_t* restrict th,

                 const pthread_attr_t * attr,//執行緒屬性 ,為NULL/0,使用程序的預設屬性

                 void* (*run) (void*), //執行緒回撥函式

                  void* data //傳遞給執行緒回撥函式的資料

                 );

              restrict -> 只修飾引數指標,第一次載入之後一直使用暫存器的值

          結論:

                  <1> 程序結束,所有子執行緒結束

                       int pthread_join(pthread_t thread,

                                        void** re//子執行緒結束的返回值);

                              等待子執行緒結束

                   <2> 建立子執行緒後,主執行緒繼續完成系統分配的時間片

                   <3> 子執行緒結束就是執行緒函式返回

                   <4> 子執行緒和主執行緒有同等優先順序

   2.執行緒的基本控制

        執行緒的狀態:

              ready -> runny -> deady

                                |

                      sleep/pause

             結束一個執行緒 

                          內部結束:

                                      pthread_exit(void* value_ptr)

                                      return 只能在執行緒函式中使用

                          外部結束:

                                      pthread_cancel(pthread_t);

             執行緒開始

                          

   3.多執行緒的問題

        資料髒

   4.多執行緒問題的解決

        互斥鎖/互斥量    mutex

        定義互斥量 pthread_mutex_t

        初始化互斥量 1  pthread_mutex_init

        互斥量操作

                置0   pthread_mutex_lock

                                判定互斥量 如果是0 -> 阻塞

                                                     如果是1 -> 置0,返回

                置1   pthread_mutex_unlock

                                 置1,返回

         釋放互斥量 pthread_mutex_destroy

         結論:

                互斥量只能保證鎖定的程式碼一個執行緒執行,

                但不能保證必須執行完

         在lock與unlock之間,呼叫pthread_exit 

               或者線上程外部呼叫pthread_cancel 其他執行緒被永久死鎖

         pthread_cleanup_push {

         pthread_cleanup_pop   }

           這對函式作用類似於atexit

           注意:

                 這不是函式,而是巨集

                 必須成對使用


二、網路程式設計

   1.基礎(ip)

     1.1  網路工具

          ping

          ping     ip地址

          ping    -b    ip廣播地址

          ifconfig   -a

          route

          lsof

          netstat 

      1.2 網路的基本概念

          網路程式設計採用socket模型,網路通訊的本質也是程序之間的IPC(程序間通訊),是不同主機之間。

          識別主機:4位元組整數 -> IP地址

          識別程序:2位元組整數 -> 應用埠

          ip地址的表示:

                   字串表示“192.168.0.13”

                    整數表示 in_addr_t

                    字結構表示 struct in_addr

        1.3 IP地址的轉換

                inet_addr         ->     把字串轉換為整數 (網路位元組序)

                inet_aton          ->     把字串轉換為struct in_addr (網路位元組序)
                inet_network    ->    把字串轉換為整數(本地位元組序)

                inet_ntoa          ->     把結構體轉換為字串

         1.4 計算機系統中的網路配置

                /etc/hosts檔案   ->     配置IP,域名,主機名

                                     gethostbyname

                                     gethostbyaddr

                /etc/protocols檔案  ->   配置系統支援的協議

                /etc/services檔案  ->   配置服務

   2.TCP/UDP程式設計

       對等模型 :AF_INET    SOCK_DGRAM  預設UDP

                socket

                繫結IP地址bind

                read/recv 

                recvfrom 可以獲取傳送者的地址

                關閉close

        C/S模型: AF_INET    SOCK_STREAM  預設TCP

                

   3.TCP的伺服器程式設計模型

        TCP的服務端維護多個客戶的網路檔案描述符

        1. 多程序

         2. IO的非同步模型(select模型 poll模型)

         3. 多執行緒

         4.多程序池

         5.多執行緒池

   4.綜合應用 -- 多程序應用

        1.怎樣使用多程序

         2.多程序的缺陷,以及怎麼解決


三、多執行緒TCP傳輸檔案例項

   第04講 多程序TCP傳輸檔案 的客戶端部分不夠完善,

       沒有處理非阻塞模式下的連線問題

   1.補充一下select模型的概念

    select在socket程式設計中還是比較重要的

    可是對於初學socket的人來說都不太愛用select寫程式

    他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程式

    所謂阻塞方式block,顧名思義,就是程序或執行緒執行到這些函式時必須等待

    某個事件發生,如果事件沒有發生,程序或執行緒就被阻塞,函式不能立即返回

    可是使用select就可以完成非阻塞,所謂非阻塞方式non-block,

    就是程序或執行緒執行此函式時不必非要等待事件發生,一旦執行立即返回

    以返回值的不同來反映函式的執行情況,如果事件發生則與阻塞方式相同,

    若事件沒有發生則返回一個程式碼來告知事件未發生,而程序或執行緒繼續執行

    所以效率較高,它能夠監視需要監視的檔案描述符的變化情況-讀寫或異常


    select函式格式:

    int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timsval *timeout);

    先說明兩個結構體:

      1> struct fd_set 可以理解為一個集合,這個集合中存放的是檔案描述符

          fd_set集合可以通過一些巨集由人為來操作,比如:

           FD_ZERO(fd_set*); 清空集合

           FD_SET(int,fd_set*); 將一個檔案描述符加入集合中

           FD_CLR(int,fd_set*); 將一個檔案描述符從集合中刪除

           FD_ISSET(int,fd_set*)檢查集合中指定的檔案描述符是否可以讀寫

       2> struct timeval是一個常用的結構體,用來代表時間值,有兩個成員

               一個是秒數,另一個是毫秒。

     select的引數具體解釋:

       int maxfdp: 是一個整數值,是指集合中所有檔案描述符的範圍,

             即所有檔案描述符的最大值加1

       fd_set *readfds: 需要監視這些檔案描述符的讀變化,

              根據timeout來判斷是否超時,有可讀的返回大於0

              沒有可讀的返回0,錯誤異常返回負數

       fd_set *writefds: 需要監視這些檔案描述符的寫變化

       fd_set *errorfds: 需要監視這些檔案描述符的錯誤異常

       struct timeval* timeout: 是select的超時時間,可以使select處於三種狀態

           1> 若將NULL以形參傳入,select置於阻塞狀態

           2> 若將時間設為0秒0毫秒,就變成一個純粹的非阻塞函式,

                不管檔案描述符是否有變化,都立刻返回

           3> timeout的值大於0,select在timeout時間內阻塞,超時之內有事件返回

                超時之後立刻返回


   2.提供一個完善的Linux/Unix/iOS客戶端

    int sock = socket(AF_INET,SOCK_STREAM,0);

    

    if(-1 == sock)

    {

        return false;

    }

    struct sockaddr_in serv_addr;

    

    memset(&serv_addr, 0, sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;

    serv_addr.sin_addr.s_addr = inet_addr("192.168.0.103");

    serv_addr.sin_port = htons(8888);

    

    int r = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    

    if (-1 == r)

     {

         connected = false;

         printf("network connect fail\n");

         return false;

     }

    

    //設定為非阻塞模式

    int flags = fcntl(sock, F_GETFL, 0);

    fcntl(sock, F_SETFL, flags | O_NONBLOCK);

    struct fd_set fds,fdsErr;

    struct timeval timeout={0,0}; //select等待3秒,3秒輪詢,要非阻塞就置0

    int maxfdp = 1;

    /* 假定已經建立UDP連線,具體過程不寫,簡單,當然TCP也同理,主機ipport都已經給定,要寫的檔案已經開啟

     sock=socket(...);

     bind(...);

     fp=fopen(...); */

    while(1)

    {

        FD_ZERO(&fds); //每次迴圈都要清空集合,否則不能檢測描述符變化

        FD_ZERO(&fdsErr);

        FD_SET(sock,&fds); //新增描述符

        FD_SET(sock,&fdsErr);

//        FD_SET(fp,&fds); //同上

//        maxfdp=sock>fp?sock+1:fp+1;    //描述符最大值加1

        maxfdp = sock+1;

        switch(select(maxfdp,&fds,NULL,&fdsErr,&timeout))   //select使用

        {

            case -1: exit(-1);break; //select錯誤,退出程式

            case 0:break; //再次輪詢

            default:

                if(FD_ISSET(sock,&fds)) //測試sock是否可讀,即是否網路上有資料

                {

                    //read fd

                    char buffer[1024];

                    ssize_t len = read(sock,buffer,1024);

                    if(len > 0)

                    {

                        //

                    }

                }

                else if(FD_ISSET(sock,&fdsErr))

                {

                    //close fd

                    close(sock);

                    connected = false;

                }

                // end if break;

        }// end switch

    }//end while


   服務端的檔案傳輸部分可以直接用,把多程序改成多執行緒就行

   socket的讀寫和客戶端一樣,可以採用select/epoll  實現

   需要把收到read/recv的資料包存入一個全域性的佇列中

   然後用多執行緒處理這些資料包

   3. 多執行緒服務端

static void

create_thread(pthread_t *thread, void *(*start_routine) (void *), void *arg) {

    if (pthread_create(thread,NULL, start_routine, arg)) {

        fprintf(stderr, "Create thread failed");

        exit(1);

    }

}



static void *

thread_worker(void *p) {

    struct message_queue * q = global_mq;

    while (!quit) {

        //取出

        struct message* q = q->pop();

        if (q == NULL) {

            //file send

            ...

        }

    }

    return NULL;

}


int main(int argc, char * argv[]) {

   

    create_thread(&pid, thread_worker, 0);

}