Linux網絡編程入門(一)

(一)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 $@
client:client.c
        gcc $^ -o $@

運行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; /* 緩衝區開始的地址  */
                size_t iov_len; /* 緩衝區的長度      */
        }

        msg_name和 msg_namelen當套接字是非面向連接時(UDP),它們存儲接收和發送方的地址信息.
        msg_name實際上是一個指向struct sockaddr的指針,
        msg_namelen是結構的長度.當套接字是面向連接時,這兩個值應設爲NULL. 
        msg_iov和 msg_iovlen指出接受和發送的緩衝區內容.msg_iov是一個結構指針,msg_iovlen指出這個結構數組的大小.  
        msg_control和msg_controllen這兩個變量是用來接收和發送控制數據時的 msg_flags指定接受和發送的操作選項.
        和 recv,send的選項一樣 

6.4 套接字的關閉 
關閉套接字有兩個函數close和shutdown.用close時和我們關閉文件一樣. 

6.5 shutdown 

int shutdown(int sockfd,int howto)  

TCP連接是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希望只關閉一個方向,這個時候我們可以使用
shutdown.針對不同的howto,系統回採取不同的關閉方式. 
        howto=0這個時候系統會關閉讀通道.但是可以繼續往接字描述符寫. 
        howto=1關閉寫通道,和上面相反,着時候就只可以讀了. 
        howto=2關閉讀寫通道,和close一樣 在多進程程序裏面,如果有幾個子進程共享一個套接字時,如果我們使用shutdown, 
        那麼所有的子進程都不能夠操作了,這個時候我們只能夠使用close來關閉子進程的套接字描述符.



本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/jenshy/archive/2006/04/18/667944.aspx


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章