2014年2月18日 星期二

Socket

http://pws.niu.edu.tw/~ttlee/os.101.1/day/socket/

Socket的由來:
Socket 是一組具體的 UNIX 系統呼叫 是 80 年代美國的研究單位 研究在UNIX中接收 TCP/IP 軟體的問題 並使其亦適應於其他場合 所以研究者們創造一種新的通訊介面 盡可能使用現有的UNIX系統呼叫 然後為支援那些不易被整合於現有函式庫的 TCP/IP 函數 新定義了一些系統呼叫函數 這便是承接口介面(Socket Interface)至今已被廣泛的認可與應用, 成為一種標準了
什麼是TCP/IP:
TCP/IP(Transmission Control Protcol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標準的協議集,它是為廣域網(WANs)設計的



各個層介紹:
l   應用層 (Application Layer)
各種網路應用如 telnet、Ftp、WWW、Email、News、BBS等。
l   傳輸層 (Transport Layer)
負責網路連接之建立、分割/組合傳送訊息、提供使用節點間資料流量的控制、決定提供網路資料傳送的服務品質等。提供可靠、有效率的連接給網路應用節點使用。
l   網路層 (IP Network Layer)
承接傳輸層傳送的封包,依所欲傳送的位址自動Route轉送路徑、轉換不同協定的封包格式、監控網路流量狀況,動態建構網路整體拓樸架構,動態提供最佳的Route資料傳送路徑。Internet網路層協定為 IP(Internet Protocol)。
l   鏈接層 (Network Access Layer)
承接網路層傳送的封包,做更細的資料框(Frame)的切割/組合、檢出/更正錯誤的傳送資料、運用ACK 判斷資料的正常傳送與控制傳送速度,及負責傳送由0與 1組成的原始網路資料。

協議的關係圖:

Socket是什麼呢?
Socket是應用層與TCP/IP協議通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議隱藏在Socket介面後面,對用戶來說,一組簡單的介面就是全部,讓Socket去組織數據,以符合指定的協議。

Socket在哪裡?





Socket的建立:

為了建立Socket,程式可以調用Socket函數,該函數返回一個類似文件描述符的控制碼。

socket 函數原型為:
  int socket(int domain, int type, int protocol);
domain指明所使用的協議族,通常為PF_INET,表示互聯網協議族(TCP/IP協議族);type參數指定socket的類型:SOCK_STREAM 或SOCK_DGRAM,Socket介面還定義了原始Socket(SOCK_RAW),允許程式使用低層協議;protocol通常賦值"0"。Socket()調用返回一個整型socket描述符,你可以在面的調用使用它。
Socket描述符是一個指向內部數據結構的指針,它指向描述符表入口。調用Socket函數時,socket執行體將建立一個Socket,實際上"建立一個Socket"意味著為一個Socket數據結構分配存儲空間。Socket執行體為你管理描述符表。
兩個網絡程式之間的一個網絡連接包括五種資訊:通信協議、本地協議位址、本地主機埠、遠端主機位址和遠端協議埠。Socket數據結構中包含這五種資訊。

Socket的配置:

通過socket調用返回一個socket描述符,在使用socket進行網絡傳輸以前,必須配置該socket。
面向連接的socket客戶端通過調用Connect函數在socket數據結構中保存本地和遠端資訊。無連接socket的客戶端和服務端以及面向連接socket的服務端通過調用bind函數來配置本地資訊。
Bind函數將socket與本機上的一個埠相關聯,隨你就可以在該埠監聽服務請求。Bind函數原型為:
  int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
  Sockfd是調用socket函數返回的socket描述符,my_addr是一個指向包含有本機IP位址及埠號等資訊的sockaddr類型的指針;addrlen常被設置為sizeof(struct sockaddr)。
  struct sockaddr結構類型是用來保存socket資訊的:
  struct sockaddr {
   unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字節的協議地址 */
};
  sa_family一般為AF_INET,代表Internet(TCP/IP)地址族;sa_data則包含該socket的IP位址和埠號。
  另外還有一種結構類型:
  struct sockaddr_in {
   short int sin_family; /* 地址族 */
   unsigned short int sin_port; /* 埠號 */
   struct in_addr sin_addr; /* IP地址 */
   unsigned char sin_zero[8]; /* 填充0 以保持與struct sockaddr同樣大小 */
  };
  這個結構更方便使用。sin_zero用來將sockaddr_in結構填充到與struct sockaddr同樣的長度,可以用bzero()或memset()函數將其置為零。指向sockaddr_in 的指針和指向sockaddr的指針可以相互轉換,這意味著如果一個函數所需參數類型是sockaddr時,你可以在函數調用的時候將一個指向 sockaddr_in的指針轉換為指向sockaddr的指針;或者相反。
  使用bind函數時,可以用下面的賦值實現自動獲得本機IP地址和隨機獲取一個沒有被佔用的埠號:
  my_addr.sin_port = 0; /* 系統隨機選擇一個未被使用的埠號 */
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */
通過將my_addr.sin_port置為0,函數會自動為你選擇一個未佔用的埠來使用。同樣,通過將my_addr.sin_addr.s_addr置為INADDR_ANY,系統會自動填入本機IP地址。
注意在使用bind函數是需要將sin_port和sin_addr轉換成為網絡字節優先順序;而sin_addr則不需要轉換。
  計算機數據存儲有兩種字節優先順序:高位字節優先和低位字節優先。Internet上數據以高位字節優先順序在網絡上傳輸,所以對在內部是以低位元字節優先方式存儲數據的機器,在Internet上傳輸數據時就需要進行轉換,否則就會出現數據不一致。
  下麵是幾個字節順序轉換函數:
﹒htonl():把32位值從主機字節序轉換成網絡字節序
﹒htons():把16位值從主機字節序轉換成網絡字節序
﹒ntohl():把32位值從網絡字節序轉換成主機字節序
﹒ntohs():把16位值從網絡字節序轉換成主機字節序
  Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"並將errno置為相應的錯誤號。需要注意的是,在調用bind函數時一般不要將埠號置為小1024的值,因為1到1024是保留埠號,你可以選擇大1024中的任何一個沒有被佔用的埠號。


連結建立:
面向連接的客戶程式使用Connect函數來配置socket並與遠端服務器建立一個TCP連接,其函數原型為:
  int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd是socket函數返回的socket描述符;serv_addr是包含遠端主機IP位址和埠號的指針;addrlen是遠端地質結構的長度。Connect函數在出現錯誤時返回-1,並且設置errno為相應的錯誤碼。進行客戶端程式設計無須調用bind(),因為這種情況下只需知道目的機器的IP位址,而客戶通過哪個埠與服務器建立 連接並不需要關心,socket執行體為你的程式自動選擇一個未被佔用的埠,並通知你的程式數據什時候到打斷口。
    Connect函數啟動和遠端主機的直接連接。只有面向連接的客戶程式使用socket時才需要將此socket與遠端主機相連。無連接協議從不建立直接連接。面向連接的服務器也從不啟動一個連接,它只是被動的在 協議埠監聽客戶的請求。
    Listen函數使socket處被動的監聽模式,並為該socket建立一個輸入數據隊列,將到達的服務請求
保存在此隊列中,直到程式處理它們。
  int listen(int sockfd, int backlog);
    Sockfd是Socket系統調用返回的socket 描述符;backlog指定在請求隊列中允許的最大請求數,進入的連 接請求將在隊列中等待accept()它們(參考下文)。Backlog對隊列中等待服務的請求的數目進行了限制, 大多數系統缺省值為20。如果一個服務請求到來時,輸入隊列已滿,該socket將拒絕連接請求,客戶將收到一個出錯資訊。當出現錯誤時listen函數返回-1,並置相應的errno錯誤碼。  
accept()函數讓服務器接收客戶的連接請求。在建立好輸入隊列,服務器就調用accept函數,然後睡眠並等待客戶的連接請求。
  int accept(int sockfd, void *addr, int *addrlen);    
    sockfd是被監聽的socket描述符,addr通常是一個指向sockaddr_in變量的指針,該變量用來存放提出連接請求服務的主機的資訊(某台主機從某個埠發出該請求);addrten通常為一個指向值為
sizeof(struct sockaddr_in)
的整型指針變量。出現錯誤時accept函數返回-1並置相應的errno值。
  首先,當accept函數監視的socket收到連接請求時,socket執行體將建立一個新的socket,執行體將這個新socket和請求連接進程的地址聯系起來,收到服務請求的初始socket仍可以繼續在以前的 socket上監聽, 同時可以在新的socket描述符上進行數據傳輸操作。

數據傳輸:
Send()和recv()這兩個函數用面向連接的socket上進行數據傳輸。
  Send()函數原型為:
  int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用來傳輸數據的socket描述符;msg是一個指向要發送數據的指針;Len是以字節為單位的數據的長度;flags一般情況下置為0(關該參數的用法可參照man手冊)。
  Send()函數返回實際上發送出的字節數,可能會少你希望發送的數據。在程式中應該將send()的返回值與欲發送的字節數進行比較。當send()返回值與len不匹配時,應該對這種情況進行處理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
  recv()函數原型為:
  int recv(int sockfd,void *buf,int len,unsigned int flags);
  Sockfd是接受數據的socket描述符;buf 是存放接收數據的緩沖區;len是緩沖的長度。Flags也被置為0。
Recv()返回實際上接收的字節數,當出現錯誤時,返回-1並置相應的errno值。
Sendto()和recvfrom()用在無連接的數據報socket方式下進行數據傳輸。由本地socket並沒有與遠端機器建立連接,所以在發送數據時應指明目的地址。
sendto()函數原型為:
  int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);   該函數比send()函數多了兩個參數,to表示目地機的IP位址和埠號資訊,而tolen常常被賦值為
sizeof (struct sockaddr)。Sendto 函數也返回實際發送的數據字節長度或在出現發送錯誤時返回-1。
  Recvfrom()函數原型為:
  int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
  from是一個struct sockaddr類型的變量,該變量保存源機的IP位址及埠號。fromlen常置為sizeof (struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的數據字節數。
Recvfrom()函數返回接收到的字節數或當出現錯誤時返回-1,並置相應的errno。
如果你對數據報socket調用了connect()函數時,你也可以利用send()和recv()進行數據傳輸,但該socket仍然是數據報socket,並且利用傳輸層的UDP服務。但在發送或接收數據報時,內核會自動為之加上目地和源位址資訊。
結束傳輸
  當所有的數據操作結束以,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何數據操作:
 close(sockfd);
  你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的數據傳輸,而一個方向上的數據傳輸繼續進行。如你可以關閉某socket的寫操作而允許繼續在該socket上接受數據,直至讀入所有數據。
  int shutdown(int sockfd,int how);
  Sockfd是需要關閉的socket的描述符。參數 how允許為shutdown操作選擇以下幾種方式:
  ﹒0-------不允許繼續接收數據
  ﹒1-------不允許繼續發送數據
   
﹒2-------不允許繼續發送和接收數據,
    ﹒均為允許則調用close ()
  shutdown在操作成功時返回0,在出現錯誤時返回-1並置相應errno。



















下面兩隻程式在 linux 利用 socket 來傳送檔案,目前設定為讀取本機端的 TEST.MP3 送到本機端的 GET.MP3

 Server.C
 #include <stdio.h>
 #include <stdlib.h>
 #include <errno.h>
 #include <string.h>
 #include <unistd.h>
 #include <netinet/in.h>
 #include <sys/socket.h>
 #include <sys/wait.h>
 #include <sys/types.h>
 #include <sys/stat.h>

int main()
{
 int sockfd, new_fd, numbytes, sin_size;
 char buf[1000000];
 struct sockaddr_in my_addr;
 struct sockaddr_in their_addr;
 struct stat filestat;
 FILE *fp;
   //TCP socket
  if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
            perror("socket");
            exit(1);
}
  //Initail, bind to port 2323
 my_addr.sin_family = AF_INET;
 my_addr.sin_port = htons(2324);
 my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
 bzero( &(my_addr.sin_zero), 8 );
//binding
 if ( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr)) == -1 ){
         perror("bind");
         exit(1);
  }  
 //Start listening
 if ( listen(sockfd, 10) == -1 ){
          perror("listen");
          exit(1);
 }  
 //Get file stat
 if ( lstat("TEST.MP3", &filestat) < 0){
          exit(1);
 } printf("The file size is %lu\n", filestat.st_size);
  fp = fopen("TEST.MP3", "rb");
 //Connect
 if ( (new_fd = accept(sockfd, (struct sockaddr*)&their_addr, &sin_size)) == -1 ){
           perror("accept"); exit(1);
 }
//Sending file
 while(!feof(fp)){
 numbytes = fread(buf, sizeof(char), sizeof(buf), fp);
 printf("fread %d bytes, ", numbytes);
 numbytes = write(new_fd, buf, numbytes);
 printf("Sending %d bytes\n",numbytes);
 }

 close(new_fd);
 close(sockfd);
 return 0;
 }

解釋一下 Sending file 的部份:

除非你打算把程式碼寫死成為只能傳送特定大小的檔案,否則,為了讓程式可以傳送任意大小的檔案,我們會在 server 端每次讀取一部分檔案出來傳送,一直讀到檔案結尾為止,所以我們需要一個 while 迴圈,迴圈終止的條件是『fread』讀到檔案結尾。

1.先用 fread 將 fp 開啟的檔案讀到 buf 裡面,讀取 sizeof(char) 大小,sizeof(buf) 次。為避免不同平臺可能會有不同 char 大小問題,一定要用 sizeof(char)來寫比較保險。
2.印出目前讀到多少bytes。
3.用 write 將 buf 裡面的資料寫入上面已經連線好了的 new_fd 這個檔案描述子,第三個參數是用來限制要寫入多少 bytes 過去,因為不見得每次都可以把 buf 填滿,例如每次都讀出1M來傳送,則最後通常會有不足1M的剩餘檔案部份需要傳送,所以不能寫 sizeof(buf) ,所以利用 fread 的回傳值來判斷要從 buf 裡面讀出多少來傳送,讀太多的話會送垃圾資訊過去。把傳送過去的大小的值回傳到numbytes裡。
4.印出送了多少 bytes 到遠端。
Client.C

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <sys/socket.h>

int main(int argc, char* argv[])
{
               int sockfd, numbytes;
               char buf[1000000];
               struct sockaddr_in address;
               FILE *fp;
               //TCP socket
               if ( ( sockfd = socket(AF_INET, SOCK_STREAM, 0) ) == -1 ){
                               perror("socket");
                               exit(1);
               }
               //Initial, connect to port 2323
               address.sin_family = AF_INET;
               address.sin_port = htons(2324);
               address.sin_addr.s_addr = inet_addr("127.0.0.1");
               bzero( &(address.sin_zero), 8 );
               //Connect to server
               if ( connect(sockfd, (struct sockaddr*)&address, sizeof(struct sockaddr)) == -1){
                               perror("connect");
                               exit(1);
               }
               //Open file
               if ( (fp = fopen("GET.MP3", "wb")) == NULL){
                               perror("fopen");
                               exit(1);
               }
               //Receive file from server
               while(1){
                               numbytes = read(sockfd, buf, sizeof(buf));
                               printf("read %d bytes, ", numbytes);
                               if(numbytes == 0){
                                              break;
                               }
                               numbytes = fwrite(buf, sizeof(char), numbytes, fp);
                               printf("fwrite %d bytes\n", numbytes);
               }
               fclose(fp);
               close(sockfd);
               return 0;
}
解釋一下 Receive file from server 的部份:
所以這邊跟 server 都同樣會需要一個 while 迴圈,控制迴圈結束的條件是『接收到大小為零』,這代表檔案已經傳送完了!
1.用 read 將上面已經連線了的 sockfd 檔案描述子的內容讀取到 buf 裡面,每次最多寫入 sizeof(buf) bytes。將回傳值存在 numbytes 這個變數裡,下兩行的寫檔會需要這個數值。
2.印出本次 read 接收到了多少 bytes 。
3.如果接收到的 bytes 為零的話就結束迴圈,因為我們認為這樣的條件代表檔案傳送完了。
4.用 fwrite 將 buf 的資料寫入 fp ,每次寫入 sizeof(char) bytes,寫入 numbytes 次,寫入太多的話會寫入垃圾資訊,所以要用剛才 read 的回傳值來控制要寫入多少 bytes。
5.印出本次 fwrite 寫入了多少 byets 。

使用socket的Linux上的C語言檔傳輸順序伺服器和客戶

伺服器端程式的編譯
gcc -o file_server file_server.c
用戶端程式的編譯
gcc -o file_client file_client.c
伺服器程式和用戶端程應當分別運行在2台電腦上.
伺服器端程式的運行,在一個電腦的終端執行
./file_server用戶端程式的運行,在另一個電腦的終端中執行
./file_client運行伺服器程式的電腦的IP位址
根據提示輸入要傳輸的伺服器上的檔,該檔在伺服器的運行目錄上。在實際編程和測試中,可以用2個終端代替2個電腦,這樣就可以在一台電腦上測試網路程式,
伺服器端程式的運行,在一個終端執行
./file_server
用戶端程式的運行,在另一個終端中執行
./file_client 127.0.0.1
說明: 任何電腦都可以通過127.0.0.1訪問自己. 也可以用電腦的實際IP位址代替127.0.0.1
//////////////////////////////////////////////////////////////////////////////////////
// file_server.c 檔傳輸順序伺服器示例
//////////////////////////////////////////////////////////////////////////////////////
//本檔是伺服器的代碼
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero

#define HELLO_WORLD_SERVER_PORT    6666
#define LENGTH_OF_LISTEN_QUEUE 20
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char **argv)
{
    //
設置一個socket位址結構server_addr,代表伺服器internet位址, 埠
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr)); //
把一段記憶體區的內容全部設置為0
    server_addr.sin_family = AF_INET; //
位址資料族系,同樣設定為AF_INET
    server_addr.sin_addr.s_addr = htons(INADDR_ANY);
//主機IP位址 in_addr資料格式
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
//主機開啟的通訊埠號 用htons() 寫入
    //創建用於internet的流協議(TCP)socket,用server_socket代表伺服器socket
    int server_socket = socket(PF_INET,SOCK_STREAM,0);
    if( server_socket < 0)
    {
        printf("Create Socket Failed!");
        exit(1);
    }
     //
把socket和socket位址結構聯繫起來
    if( bind(server_socket,(struct sockaddr*)&server_addr,sizeof(server_addr)))
    {
        printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT);
        exit(1);
    }
    //server_socket用於監聽
    if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )
    {
        printf("Server Listen Failed!");
        exit(1);
    }
    while (1) //
伺服器端要一直運行
    {
        //
定義用戶端的socket位址結構client_addr
        struct sockaddr_in client_addr;
        socklen_t length = sizeof(client_addr);
        //接受一個到server_socket代表的socket的一個連接
        //
如果沒有連接請求,就等待到有連接請求--這是accept函數的特性
        //accept
函數返回一個新的socket,這個socket(new_server_socket)用於同連接到的客戶的通信
        //new_server_socket
代表了伺服器和用戶端之間的一個通信通道
        //accept
函數把連接到的用戶端資訊填寫到用戶端的socket位址結構client_addr中
        int new_server_socket = accept(server_socket,(struct sockaddr*)&client_addr,&length);
        if ( new_server_socket < 0)
        {
            printf("Server Accept Failed!\n");
            break;
        }
        char buffer[BUFFER_SIZE];
        bzero(buffer, BUFFER_SIZE);
        length = recv(new_server_socket,buffer,BUFFER_SIZE,0);
        if (length < 0)
        {
            printf("Server Recieve Data Failed!\n");
            break;
        }
        char file_name[FILE_NAME_MAX_SIZE+1];
        bzero(file_name, FILE_NAME_MAX_SIZE+1);
        strncpy(file_name, buffer, strlen(buffer)>FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));
//        int fp = open(file_name, O_RDONLY);
//        if( fp < 0 )
        FILE * fp = fopen(file_name,"r");
        if(NULL == fp )
        {
            printf("File:\t%s Not Found\n", file_name);
        }
        else
        {
            bzero(buffer, BUFFER_SIZE);
            int file_block_length = 0;
//            while( (file_block_length = read(fp,buffer,BUFFER_SIZE))>0)
            while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))>0)
            {
                printf("file_block_length = %d\n",file_block_length);
                //
發送buffer中的字串到new_server_socket,實際是給用戶端
                if(send(new_server_socket,buffer,file_block_length,0)<0)
                {
                    printf("Send File:\t%s Failed\n", file_name);
                    break;
                }
                bzero(buffer, BUFFER_SIZE);
            }
//            close(fp);
            fclose(fp);
            printf("File:\t%s Transfer Finished\n",file_name);
        }
        //
關閉與用戶端的連接
        close(new_server_socket);
    }
    //
關閉監聽用的socket
    close(server_socket);
    return 0;
}

//////////////////////////////////////////////////////////////////////////////////////
// file_client.c
檔傳輸用戶端程式示例
//////////////////////////////////////////////////////////////////////////////////////
//
本檔是客戶機的代碼
#include <netinet/in.h>    // for sockaddr_in
#include <sys/types.h>    // for socket
#include <sys/socket.h>    // for socket
#include <stdio.h>        // for printf
#include <stdlib.h>        // for exit
#include <string.h>        // for bzero

#define HELLO_WORLD_SERVER_PORT    6666
#define BUFFER_SIZE 1024
#define FILE_NAME_MAX_SIZE 512

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        printf("Usage: ./%s ServerIPAddress\n",argv[0]);
        exit(1);
    }
    //設置一個socket位址結構client_addr,代表客戶機internet位址, 埠
    struct sockaddr_in client_addr;
    bzero(&client_addr,sizeof(client_addr)); //
把一段記憶體區的內容全部設置為0
    client_addr.sin_family = AF_INET;    //internet
協議族
    client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY
表示自動獲取本機位址
    client_addr.sin_port = htons(0);    //0
表示讓系統自動分配一個空閒埠
    //
創建用於internet的流協議(TCP)socket,用client_socket代表客戶機socket
    int client_socket = socket(AF_INET,SOCK_STREAM,0);
    if( client_socket < 0)
    {
        printf("Create Socket Failed!\n");
        exit(1);
    }
    //
把客戶機的socket和客戶機的socket位址結構聯繫起來
    if( bind(client_socket,(struct sockaddr*)&client_addr,sizeof(client_addr)))
    {
        printf("Client Bind Port Failed!\n");
        exit(1);
    }
  //
設置一個socket位址結構server_addr,代表伺服器的internet位址, 埠
    struct sockaddr_in server_addr;
    bzero(&server_addr,sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    if(inet_aton(argv[1],&server_addr.sin_addr) == 0) //
伺服器的IP位址來自程式的參數
    {
        printf("Server IP Address Error!\n");
        exit(1);
    }
    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);
    socklen_t server_addr_length = sizeof(server_addr);
    //
向伺服器發起連接,連接成功後client_socket代表了客戶機和伺服器的一個socket連接
    if(connect(client_socket,(struct sockaddr*)&server_addr, server_addr_length) < 0)
    {
        printf("Can Not Connect To %s!\n",argv[1]);
        exit(1);
    }
    char file_name[FILE_NAME_MAX_SIZE+1];
    bzero(file_name, FILE_NAME_MAX_SIZE+1);
    printf("Please Input File Name On Server:\t");
    scanf("%s", file_name);
  
    char buffer[BUFFER_SIZE];
    bzero(buffer,BUFFER_SIZE);
    strncpy(buffer, file_name, strlen(file_name)>BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));
    //
向伺服器發送buffer中的資料
    send(client_socket,buffer,BUFFER_SIZE,0);
//    int fp = open(file_name, O_WRONLY|O_CREAT);
//    if( fp < 0 )
    FILE * fp = fopen(file_name,"w");
    if(NULL == fp )
    {
        printf("File:\t%s Can Not Open To Write\n", file_name);
        exit(1);
    }
 //從伺服器接收資料到buffer中
    bzero(buffer,BUFFER_SIZE);
    int length = 0;
    while( length = recv(client_socket,buffer,BUFFER_SIZE,0))
    {
        if(length < 0)
        {
            printf("Recieve Data From Server %s Failed!\n", argv[1]);
            break;
        }
//        int write_length = write(fp, buffer,length);
        int write_length = fwrite(buffer,sizeof(char),length,fp);
        if (write_length<length)
        {
            printf("File:\t%s Write Failed\n", file_name);
            break;
        }
        bzero(buffer,BUFFER_SIZE);  
    }
    printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);
    fclose(fp);
    //
關閉socket
    close(client_socket);
    return 0;
}

實作範例: Client端從Server端接收文字檔 t

先ls查詢server端底下有一個檔名為t的文字檔
Cat查看文字檔t
執行./sss (serve端)

等待(client端)
以傳送文字檔t 給client端

(Server端)…..
sss
















先ls查詢client端底下有哪些檔名的檔案(沒有要接收的文字檔t)
執行./ccc 127.0.0.1 (client端)

Please Input File Name On Server
請輸入要接收的檔名

等待(Server端)
接受文字檔t 存入client端

ls查詢server端底下有一個檔名為t的文字檔
Cat查看文字檔t

確認與(Server端)的文字檔t 無誤

(Client端)…..
ccc