1. 程式人生 > >Linux網路程式設計入門

Linux網路程式設計入門

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow

也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!

               

(一)Linux網路程式設計--網路知識介紹

Linux網路程式設計--網路知識介紹
客戶端和服務端 
        網路程式和普通的程式有一個最大的區別是網路程式是由兩個部分組成的--客戶端和伺服器端.

客戶端
        在網路程式中,如果一個程式主動和外面的程式通訊,那麼我們把這個程式稱為客戶端程式。 比如我們使用ftp程式從另外一
        個地方獲取檔案的時候,是我們的ftp程式主動同外面進行通訊(獲取檔案), 所以這個地方我們的ftp程式就是客戶端程式。 
服務端
        和客戶端相對應的程式即為服務端程式。被動的等待外面的程式來和自己通訊的程式稱為服務端程式。 
        比如上面的檔案獲取中,另外一個地方的程式就是服務端,我們從服務端獲取檔案過來。 
互為客戶和服務端
        實際生活中有些程式是互為服務和客戶端。在這種情況專案, 一個程式既為客戶端也是服務端。

常用的命令 
        由於網路程式是有兩個部分組成,所以在除錯的時候比較麻煩,為此我們有必要知道一些常用的網路命令 
netstat 
        命令netstat是用來顯示網路的連線,路由表和介面統計等網路的資訊.netstat有許多的選項. 
        我們常用的選項是-na 用來顯示詳細的網路狀態.至於其它的選項我們可以使用幫助手冊獲得詳細的情況. 
telnet 
        telnet是一個用來登入遠端的程式,但是我們完全可以用這個程式來除錯我們的服務端程式的. 
        比如我們的伺服器程式在監聽8888埠,我們可以用
                telnet localhost 8888
        來檢視服務端的狀況. 
pingping 程式用來判斷網路的狀態是否正常,最經常的一個用法是
        ping 192.168.0.1
        表示我們想檢視到192.168.0.1的硬體連線是否正常 
TCP/UDP介紹 
        TCP(Transfer Control Protocol)傳輸控制協議是一種面向連線的協議, 當我們的網路程式使用這個協議的時候,
        網路可以保證我們的客戶端和服務端的連線是可靠的,安全的.

        UDP(User Datagram Protocol)使用者資料報協議是一種非面向連線的協議, 
        這種協議並不能保證我們的網路程式的連線是可靠的,所以我們現在編寫的程式一般是採用TCP協議的.

 

(二)Linux網路程式設計--初等網路函式介紹(TCP)

   Linux系統是通過提供套接字(socket)來進行網路程式設計的.網路程式通過socket和其它幾個函式的呼叫,
   會返回一個 通訊的檔案描述符,我們可以將這個描述符看成普通的檔案的描述符來操作,這就是linux的裝置無關性的好處.
   我們可以通過向描述符讀寫操作實現網路之間的資料交流. 
(一)socket 
  
  int socket(int domain, int type,int protocol)

  domain:說明我們網路程式所在的主機採用的通訊協族(AF_UNIX和AF_INET等). 
        AF_UNIX只能夠用於單一的Unix 系統程序間通訊,
        而AF_INET是針對Internet的,因而可以允許在遠端 
        主機之間通訊(當我們 man socket時發現 domain可選項是 PF_*而不是AF_*,因為glibc是posix的實現所以用PF代替了AF,
        不過我們都可以使用的).

  type:我們網路程式所採用的通訊協議(SOCK_STREAM,SOCK_DGRAM等) 
        SOCK_STREAM表明我們用的是TCP 協議,這樣會提供按順序的,可靠,雙向,面向連線的位元流. 
        SOCK_DGRAM 表明我們用的是UDP協議,這樣只會提供定長的,不可靠,無連線的通訊.

  protocol:由於我們指定了type,所以這個地方我們一般只要用0來代替就可以了 socket為網路通訊做基本的準備.
  成功時返回檔案描述符,失敗時返回-1,看errno可知道出錯的詳細情況.


(二)bind 
  int bind(int sockfd, struct sockaddr *my_addr, int addrlen)

  sockfd:是由socket呼叫返回的檔案描述符.

  addrlen:是sockaddr結構的長度.

  my_addr:是一個指向sockaddr的指標. 在中有 sockaddr的定義

        struct sockaddr{
                unisgned short  as_family;
                char            sa_data[14];
        };

  不過由於系統的相容性,我們一般不用這個標頭檔案,而使用另外一個結構(struct sockaddr_in) 來代替.在中有sockaddr_in的定義 
        struct sockaddr_in{
                unsigned short          sin_family;     
                unsigned short int      sin_port;
                struct in_addr          sin_addr;
                unsigned char           sin_zero[8];
        }
  我們主要使用Internet所以
        sin_family一般為AF_INET,
        sin_addr設定為INADDR_ANY表示可以和任何的主機通訊,
        sin_port是我們要監聽的埠號.sin_zero[8]是用來填充的. 
  bind將本地的埠同socket返回的檔案描述符捆綁在一起.成功是返回0,失敗的情況和socket一樣

(三)listen 
  int listen(int sockfd,int backlog)

  sockfd:是bind後的檔案描述符.

  backlog:設定請求排隊的最大長度.當有多個客戶端程式和服務端相連時, 使用這個表示可以介紹的排隊長度. 
  listen函式將bind的檔案描述符變為監聽套接字.返回的情況和bind一樣.


(四)accept 
  int accept(int sockfd, struct sockaddr *addr,int *addrlen)

  sockfd:是listen後的檔案描述符.

  addr,addrlen是用來給客戶端的程式填寫的,伺服器端只要傳遞指標就可以了. bind,listen和accept是伺服器端用的函式,
  accept呼叫時,伺服器端的程式會一直阻塞到有一個 客戶程式發出了連線. accept成功時返回最後的伺服器端的檔案描述符,
  這個時候伺服器端可以向該描述符寫資訊了. 失敗時返回-1

(五)connect 
   int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)

   sockfd:socket返回的檔案描述符.

   serv_addr:儲存了伺服器端的連線資訊.其中sin_add是服務端的地址

   addrlen:serv_addr的長度

   connect函式是客戶端用來同服務端連線的.成功時返回0,sockfd是同服務端通訊的檔案描述符 失敗時返回-1.

(六)例項

伺服器端程式

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/******* 伺服器程式  (server.c) ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
        int sockfd,new_fd;
        struct sockaddr_in server_addr;
        struct sockaddr_in client_addr;
        int sin_size,portnumber;
        char hello[]="Hello! Are You Fine?\n";

        if(argc!=2)
        {
                fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
                exit(1);
        }

        if((portnumber=atoi(argv[1]))<0)
        {
                fprintf(stderr,"Usage:%s portnumber\a\n",argv[0]);
                exit(1);
        }

        /* 伺服器端開始建立socket描述符 */
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)  
        {
                fprintf(stderr,"Socket error:%s\n\a",strerror(errno));
                exit(1);
        }

        /* 伺服器端填充 sockaddr結構  */ 
        bzero(&server_addr,sizeof(struct sockaddr_in));
        server_addr.sin_family=AF_INET;
        server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
        server_addr.sin_port=htons(portnumber);

        /* 捆綁sockfd描述符  */ 
        if(bind(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
        {
                fprintf(stderr,"Bind error:%s\n\a",strerror(errno));
                exit(1);
        }

        /* 監聽sockfd描述符  */
        if(listen(sockfd,5)==-1)
        {
                fprintf(stderr,"Listen error:%s\n\a",strerror(errno));
                exit(1);
        }

        while(1)
        {
                /* 伺服器阻塞,直到客戶程式建立連線  */
                sin_size=sizeof(struct sockaddr_in);
                if((new_fd=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)
                {
                        fprintf(stderr,"Accept error:%s\n\a",strerror(errno));
                        exit(1);
                }

                fprintf(stderr,"Server get connection from %s\n",
                inet_ntoa(client_addr.sin_addr));
                if(write(new_fd,hello,strlen(hello))==-1)
                {
                        fprintf(stderr,"Write Error:%s\n",strerror(errno));
                        exit(1);
                }
                /* 這個通訊已經結束     */
                close(new_fd);
                /* 迴圈下一個     */  
        }
        close(sockfd);
        exit(0);
}

客戶端程式

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/******* 客戶端程式  client.c ************/
/******* 客戶端程式  client.c ************/
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>

int main(int argc, char *argv[])
{
        int sockfd;
        char buffer[1024];
        struct sockaddr_in server_addr;
        struct hostent *host;
        int portnumber,nbytes;

        if(argc!=3)
        {
                fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
                exit(1);
        }

        if((host=gethostbyname(argv[1]))==NULL)
        {
                fprintf(stderr,"Gethostname error\n");
                exit(1);
        }

        if((portnumber=atoi(argv[2]))<0)
        {
                fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
                exit(1);
        }

        /* 客戶程式開始建立 sockfd描述符  */
        if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
        {
                fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
                exit(1);
        }

        /* 客戶程式填充服務端的資料       */
        bzero(&server_addr,sizeof(server_addr));
        server_addr.sin_family=AF_INET;
        server_addr.sin_port=htons(portnumber);
        server_addr.sin_addr=*((struct in_addr *)host->h_addr);

        /* 客戶程式發起連線請求         */ 
        if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr))==-1)
        {
                fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
                exit(1);
        }

        /* 連線成功了           */
        if((nbytes=read(sockfd,buffer,1024))==-1)
        {
                fprintf(stderr,"Read Error:%s\n",strerror(errno));
                exit(1);
        }
        buffer[nbytes]='\0';
        printf("I have received:%s\n",buffer);
        /* 結束通訊     */
        close(sockfd);
        exit(0);
}

MakeFile
這裡我們使用GNU 的make實用程式來編譯. 關於make的詳細說明見 Make 使用介紹

CODE:  [Copy to clipboard]

#########  Makefile       ###########
all:server client
server:server.c
        gcc $^ -o [email protected]
client:client.c
        gcc $^ -o [email protected]

執行make後會產生兩個程式server(伺服器端)和client(客戶端) 先執行./server portnumber&  
        (portnumber隨便取一個大於1204且不在/etc/services中出現的號碼 就用8888好了),
        然後執行  ./client localhost 8888 看看有什麼結果. (你也可以用telnet和netstat試一試.) 
        上面是一個最簡單的網路程式,不過是不是也有點煩.上面有許多函式我們還沒有解釋. 我會在下一章進行的詳細的說明.


(七) 總結 
總的來說網路程式是由兩個部分組成的--客戶端和伺服器端.它們的建立步驟一般是:

伺服器端
socket-->bind-->listen-->accept

客戶端
socket-->connect

(三)Linux網路程式設計--3. 伺服器和客戶機的資訊函式


這一章我們來學習轉換和網路方面的資訊函式. 
3.1 位元組轉換函式 
在網路上面有著許多型別的機器,這些機器在表示資料的位元組順序是不同的, 比如i386晶片是低位元組在記憶體地址的低端,
高位元組在高階,而alpha晶片卻相反. 為了統一起來,在Linux下面,有專門的位元組轉換函式. 
unsigned long  int htonl(unsigned long  int hostlong)
unsigned short int htons(unisgned short int hostshort)
unsigned long  int ntohl(unsigned long  int netlong)
unsigned short int ntohs(unsigned short int netshort)

在這四個轉換函式中,h 代表host, n 代表 network.s 代表short l 代表long 
        第一個函式的意義是將本機器上的long資料轉化為網路上的long. 其他幾個函式的意義也差不多.

3.2 IP和域名的轉換 
在網路上標誌一臺機器可以用IP或者是用域名.那麼我們怎麼去進行轉換呢?

struct hostent *gethostbyname(const char *hostname)
struct hostent *gethostbyaddr(const char *addr,int len,int type)
在中有struct hostent的定義
struct hostent{
        char *h_name;           /* 主機的正式名稱  */
        char *h_aliases;        /* 主機的別名 */
        int   h_addrtype;       /* 主機的地址型別  AF_INET*/
        int   h_length;         /* 主機的地址長度  對於IP4 是4位元組32位*/
        char **h_addr_list;     /* 主機的IP地址列表 */
        }
  #define h_addr h_addr_list[0]  /* 主機的第一個IP地址*/

gethostbyname可以將機器名(如 linux.yessun.com)轉換為一個結構指標.在這個結構裡面儲存了域名的資訊 
gethostbyaddr可以將一個32位的IP地址(C0A80001)轉換為結構指標.

這兩個函式失敗時返回NULL 且設定h_errno錯誤變數,呼叫h_strerror()可以得到詳細的出錯資訊


3.3 字串的IP和32位的IP轉換. 
在網路上面我們用的IP都是數字加點(192.168.0.1)構成的, 而在struct in_addr結構中用的是32位的IP, 
我們上面那個32位IP(C0A80001)是的192.168.0.1 為了轉換我們可以使用下面兩個函式

int inet_aton(const char *cp,struct in_addr *inp)
char *inet_ntoa(struct in_addr in)

函式裡面 a 代表 ascii n 代表network.第一個函式表示將a.b.c.d的IP轉換為32位的IP,
儲存在 inp指標裡面.第二個是將32位IP轉換為a.b.c.d的格式.


3.4 服務資訊函式 
在網路程式裡面我們有時候需要知道埠.IP和服務資訊.這個時候我們可以使用以下幾個函式

int getsockname(int sockfd,struct sockaddr *localaddr,int *addrlen)
int getpeername(int sockfd,struct sockaddr *peeraddr, int *addrlen)
struct servent *getservbyname(const char *servname,const char *protoname)
struct servent *getservbyport(int port,const char *protoname)
struct servent
        {
                char *s_name;          /* 正式服務名 */
                char **s_aliases;      /* 別名列表 */  
                int s_port;            /* 埠號 */
                char *s_proto;         /* 使用的協議 */ 
        }

一般我們很少用這幾個函式.對應客戶端,當我們要得到連線的埠號時在connect呼叫成功後使用可得到 
系統分配的埠號.對於服務端,我們用INADDR_ANY填充後,為了得到連線的IP我們可以在accept呼叫成功後 使用而得到IP地址. 
在網路上有許多的預設埠和服務,比如埠21對ftp80對應WWW.為了得到指定的埠號的服務 我們可以呼叫第四個函式,
相反為了得到埠號可以呼叫第三個函式.

3.5 一個例子

CODE:  [Copy to clipboard]

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

int main(int argc ,char **argv)
{
        struct sockaddr_in addr;
        struct hostent *host;
        char **alias;
        
        if(argc<2)
        {
         fprintf(stderr,"Usage:%s hostname|ip..\n\a",argv[0]);
         exit(1);
        }
        
        argv++;
        for(;*argv!=NULL;argv++)
        {
                /* 這裡我們假設是IP*/   
                if(inet_aton(*argv,&addr.sin_addr)!=0)
                {
                   host=gethostbyaddr((char   *)&addr.sin_addr,4,AF_INET); 
                   printf("Address information of Ip %s\n",*argv); 
                } 
                else 
                {
                      /* 失敗,難道是域名?*/
                      host=gethostbyname(*argv); printf("Address information
                      of host %s\n",*argv); 
                }
                if(host==NULL)
                {
                        /* 都不是 ,算了不找了*/
                        fprintf(stderr,"No address information of %s\n",*argv);
                        continue;
                }
                printf("Official host name %s\n",host->h_name);
                printf("Name aliases:");
                for(alias=host->h_aliases;*alias!=NULL;alias++)
                 printf("%s ,",*alias);
                printf("\nIp address:");
                for(alias=host->h_addr_list;*alias!=NULL;alias++)
                  printf("%s ,",inet_ntoa(*(struct in_addr *)(*alias)));
        }
}

在這個例子裡面,為了判斷使用者輸入的是IP還是域名我們呼叫了兩個函式,第一次我們假設輸入的是IP所以呼叫inet_aton, 
失敗的時候,再呼叫gethostbyname而得到資訊.

(四)Linux網路程式設計--4. 完整的讀寫函式

一旦我們建立了連線,我們的下一步就是進行通訊了.在Linux下面把我們前面建立的通道看成是檔案描述符,
這樣伺服器端和客戶端進行通訊時候,只要往檔案描述符裡面讀寫東西了. 就象我們往檔案讀寫一樣.

4.1 寫函式write 
ssize_t write(int fd,const void *buf,size_t nbytes)

write函式將buf中的nbytes位元組內容寫入檔案描述符fd.成功時返回寫的位元組數.失敗時返回-1. 並設定errno變數. 
在網路程式中,當我們向套接字檔案描述符寫時有倆種可能.

        1)write的返回值大於0,表示寫了部分或者是全部的資料.

        2)返回的值小於0,此時出現了錯誤.我們要根據錯誤型別來處理.

如果錯誤為EINTR表示在寫的時候出現了中斷錯誤. 
如果為EPIPE表示網路連接出現了問題(對方已經關閉了連線). 
為了處理以上的情況,我們自己編寫一個寫函式來處理這幾種情況.

int my_write(int fd,void *buffer,int length)
{
int bytes_left;
int written_bytes;
char *ptr;

ptr=buffer;
bytes_left=length;
while(bytes_left>0)
{
        /* 開始寫*/
        written_bytes=write(fd,ptr,bytes_left);
        if(written_bytes<=0) /* 出錯了*/
        {       
                if(errno==EINTR) /* 中斷錯誤 我們繼續寫*/
                        written_bytes=0;
                else             /* 其他錯誤 沒有辦法,只好撤退了*/
                        return(-1);
        }
        bytes_left-=written_bytes;
        ptr+=written_bytes;     /* 從剩下的地方繼續寫  */
}
return(0);
}

4.2 讀函式read 
ssize_t read(int fd,void *buf,size_t nbyte) read函式是負責從fd中讀取內容.當讀成功時, 
read返回實際所讀的位元組數,如果返回的值是0 表示已經讀到檔案的結束了,小於0表示出現了錯誤.
        如果錯誤為EINTR說明讀是由中斷引起的, 
        如果是ECONNREST表示網路連接出了問題. 和上面一樣,我們也寫一個自己的讀函式.

int my_read(int fd,void *buffer,int length)
{
int bytes_left;
int bytes_read;
char *ptr;
  
bytes_left=length;
while(bytes_left>0)
{
   bytes_read=read(fd,ptr,bytes_read);
   if(bytes_read<0)
   {
     if(errno==EINTR)
        bytes_read=0;
     else
        return(-1);
   }
   else if(bytes_read==0)
       break;
    bytes_left-=bytes_read;
    ptr+=bytes_read;
}
return(length-bytes_left);
}


4.3 資料的傳遞 
有了上面的兩個函式,我們就可以向客戶端或者是服務端傳遞資料了.比如我們要傳遞一個結構.可以使用如下方式


/*  客戶端向服務端寫 */
struct my_struct my_struct_client;
write(fd,(void *)&my_struct_client,sizeof(struct my_struct);

/* 服務端的讀*/ 
char buffer[sizeof(struct my_struct)];
struct *my_struct_server;
read(fd,(void *)buffer,sizeof(struct my_struct)); 
my_struct_server=(struct my_struct *)buffer;   

在網路上傳遞資料時我們一般都是把資料轉化為char型別的資料傳遞.接收的時候也是一樣的 注意的是我們沒有必要在網路上傳
遞指標(因為傳遞指標是沒有任何意義的,我們必須傳遞指標所指向的內容)


(五)Linux網路程式設計--5. 使用者資料報傳送


我們前面已經學習網路程式的一個很大的部分,由這個部分的知識,我們實際上可以寫出大部分的基於TCP協議的網路程式了.
現在在 Linux下的大部分程式都是用我們上面所學的知識來寫的.我們可以去找一些源程式來參考一下.這一章,我們簡單的學習一
下基於UDP協議的網路程式.

5.1 兩個常用的函式 
   int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr * from int *fromlen)
   int sendto(int sockfd,const void *msg,int len,unsigned int flags,struct sockaddr *to int tolen)

sockfd,buf,len的意義和read,write一樣,分別表示套接字描述符,傳送或接收的緩衝區及大小.
recvfrom負責從 sockfd接收資料,如果from不是NULL,那麼在from裡面儲存了資訊來源的情況,如果對資訊的來源不感興趣,
可以將from和fromlen 設定為NULL.sendto負責向to傳送資訊.此時在to裡面儲存了收資訊方的詳細資料.


5.2 一個例項

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/*           服務端程式  server.c           */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define SERVER_PORT     8888
#define MAX_MSG_SIZE    1024

void udps_respon(int sockfd)
{
        struct sockaddr_in addr;
        int    n;
                socklen_t addrlen;
        char    msg[MAX_MSG_SIZE];
        
        while(1)
        {       /* 從網路上讀,寫到網路上面去   */
                                memset(msg, 0, sizeof(msg));
                                addrlen = sizeof(struct sockaddr);
                                n=recvfrom(sockfd,msg,MAX_MSG_SIZE,0,
                        (struct sockaddr*)&addr,&addrlen);
                /* 顯示服務端已經收到了資訊  */
                fprintf(stdout,"I have received %s",msg);
                sendto(sockfd,msg,n,0,(struct sockaddr*)&addr,addrlen);
        }
}

int main(void)
{
        int sockfd;
        struct sockaddr_in      addr;
        
        sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd<0)
        {
                fprintf(stderr,"Socket Error:%s\n",strerror(errno));
                exit(1);
        }
        bzero(&addr,sizeof(struct sockaddr_in));
        addr.sin_family=AF_INET;
        addr.sin_addr.s_addr=htonl(INADDR_ANY);
        addr.sin_port=htons(SERVER_PORT);
        if(bind(sockfd,(struct sockaddr *)&addr,sizeof(struct sockaddr_in))<0)
        {
                fprintf(stderr,"Bind Error:%s\n",strerror(errno));
                exit(1);
        }
        udps_respon(sockfd);
        close(sockfd);
}

客戶端程式

CODE:  [Copy to clipboard]


--------------------------------------------------------------------------------

/*          客戶端程 序             */
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#define MAX_BUF_SIZE    1024

void udpc_requ(int sockfd,const struct sockaddr_in *addr,socklen_t len)
{
        char buffer[MAX_BUF_SIZE];
        int n;
        while(fgets(buffer,MAX_BUF_SIZE,stdin))        
        {        /*   從鍵盤讀入,寫到服務端   */
                sendto(sockfd,buffer,strlen(buffer),0,addr,len);
                bzero(buffer,MAX_BUF_SIZE);

                /*   從網路上讀,寫到螢幕上    */
                                memset(buffer, 0, sizeof(buffer));
                n=recvfrom(sockfd,buffer,MAX_BUF_SIZE, 0, NULL, NULL);
                if(n <= 0)
                                {
                                        fprintf(stderr, "Recv Error %s\n", strerror(errno));
                                        return;
                                }
                                buffer[n]=0;
                fprintf(stderr, "get %s", buffer);
        }
}


int main(int argc,char **argv)
{
        int sockfd,port;
        struct sockaddr_in      addr;
        
        if(argc!=3)
        {
                fprintf(stderr,"Usage:%s server_ip server_port\n",argv[0]);
                exit(1);
        }
        
        if((port=atoi(argv[2]))<0)
        {
                fprintf(stderr,"Usage:%s server_ip server_port\n",argv[0]);
                exit(1);
        }
        
        sockfd=socket(AF_INET,SOCK_DGRAM,0);
        if(sockfd<0)
        {
                fprintf(stderr,"Socket  Error:%s\n",strerror(errno));
                exit(1);
        }       
        /*      填充服務端的資料      */
        bzero(&addr,sizeof(struct sockaddr_in));
        addr.sin_family=AF_INET;
        addr.sin_port=htons(port);
        if(inet_aton(argv[1],&addr.sin_addr)<0)
        {
                fprintf(stderr,"Ip error:%s\n",strerror(errno));
                exit(1);
        }
                 if(connect(sockfd, (struct sockaddr *)&addr, sizeof(struct sockaddr_in)) == -1)
                {
                        fprintf(stderr, "connect error %s\n", strerror(errno));
                        exit(1);
                }
        udpc_requ(sockfd,&addr,sizeof(struct sockaddr_in));
        close(sockfd);
}

########### 編譯檔案 Makefile        ##########
all:server client
server:server.c
        gcc -o server server.c
client:client.c
        gcc -o client client.c
clean:
        rm -f server
        rm -f client
        rm -f core

執行UDP Server程式
執行./server &命令來啟動服務程式。我們可以使用netstat -ln命令來觀察服務程式繫結的IP地址和埠,部分輸出資訊如下:
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 0.0.0.0:32768 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:6000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:631 0.0.0.0:* LISTEN
udp 0 0 0.0.0.0:32768 0.0.0.0:*
udp 0 0 0.0.0.0:8888 0.0.0.0:*
udp 0 0 0.0.0.0:111 0.0.0.0:*
udp 0 0 0.0.0.0:882 0.0.0.0:*
可以看到udp處有“0.0.0.0:8888”的內容,說明服務程式已經正常執行,可以接收主機上任何IP地址且埠為8888的資料。

3、執行UDP Client程式
執行./client 127.0.0.1 8888命令來啟動客戶程式,使用127.0.0.1來連線服務程式,執行效果如下:
Hello, World!
Hello, World!
this is a test
this is a test
^d
輸入的資料都正確從服務程式返回了,按ctrl+d可以結束輸入,退出程式。

(六)Linux網路程式設計--6. 高階套接字函式

在前面的幾個部分裡面,我們已經學會了怎麼樣從網路上讀寫資訊了.前面的一些函式(read,write)是網路程式裡面最基本的函式.
也是最原始的通訊函式.在這一章裡面,我們一起來學習網路通訊的高階函式.這一章我們學習另外幾個讀寫函式.

6.1 recv和send 
  recv和send函式提供了和read和write差不多的功能.不過它們提供 了第四個引數來控制讀寫操作.

         int recv(int sockfd,void *buf,int len,int flags)
         int send(int sockfd,void *buf,int len,int flags)

前面的三個引數和read,write一樣,第四個引數可以是0或者是以下的組合 
_______________________________________________________________
|  MSG_DONTROUTE        |  不查詢路由 表                         |
|  MSG_OOB              |  接受或者傳送帶外數 據                 |
|  MSG_PEEK             |  檢視資料,並不從系統緩衝區移走資料    |
|  MSG_WAITALL          |  等待所有數 據                         |
|---------------------------------------------------------------|

MSG_DONTROUTE:是send函式使用的標誌.這個標誌告訴IP協議.目的主機在本地網路上面,沒有必要查詢路由表.
        這個標誌一般用網路診斷和路由程式裡面.

MSG_OOB:表示可以接收和傳送帶外的資料.關於帶外資料我們以後會解釋的.

MSG_PEEK:是recv函式的使用標誌,表示只是從系統緩衝區中讀取內容,而不清除系統緩衝區的內容.這樣下次讀的時候,
        仍然是一樣的內容.一般在有多個程序讀寫資料時可以使用這個標誌.

MSG_WAITALL是recv函式的使用標誌,表示等到所有的資訊到達時才返回.使用這個標誌的時候recv回一直阻塞,直到指定的條件滿足,或者是發生了錯誤.
        1)當讀到了指定的位元組時,函式正常返回.返回值等於len 
        2)當讀到了檔案的結尾時,函式正常返回.返回值小於len 
        3) 當操作發生錯誤時,返回-1,且設定錯誤為相應的錯誤號(errno)

如果flags為0,則和read,write一樣的操作.還有其它的幾個選項,不過我們實際上用的很少,
可以檢視 Linux Programmer's Manual得到詳細解釋.

6.2 recvfrom和sendto 
        這兩個函式一般用在非套接字的網路程式當中(UDP),我們已經在前面學會了.

6.3 recvmsg和sendmsg 
        recvmsg和sendmsg可以實現前面所有的讀寫函式的功能.

int recvmsg(int sockfd,struct msghdr *msg,int flags)
int sendmsg(int sockfd,struct msghdr *msg,int flags)

  struct msghdr
        {
                void *msg_name;
                int msg_namelen;
                struct iovec *msg_iov;
                int msg_iovlen;
                void *msg_control;
                int msg_controllen;
                int msg_flags;
        }

struct iovec
        {
                void *iov_base; /* 緩衝區開始的地址  */