天天看點

網絡程式設計 Sockets

關于Socket程式設計,在《Linux從入門到精通》裡有簡單的介紹,更詳細的可以參考
《UNIX網絡程式設計 卷1:聯網的API:套接字與XTI 第2版》清華影印版,其中還講了
線程(Thread)程式設計。極好的參考書,可惜沒人把它翻譯過來。
胡淑瑜翻譯了一篇“網絡程式設計”,我把它收集進來了。
如有更新,請參考胡先生的個人首頁。
       
________________________________________________________________________________|                                   版權聲明                                   ||                                                                              ||  1、本文可以轉載、修改及引用,但請保留本聲明和其後所付英文原文。             ||  2、任何情況下,作者和譯者姓名不可删除。                                     ||  3、任何情況下,本文不可進入商業領域。                                       ||                                                                              ||                                                  胡淑瑜                      ||                                        ([email protected])             ||                                                                              ||                                                 1998年11月                   ||______________________________________________________________________________|第59章目錄   網絡程式設計      端口(Ports)和套接字(Sockets)      套接字程式設計         socket()系統調用(System Call)         bind()系統調用         listen()系統調用         accept()系統調用         setsockopt和getsockopt系統調用         connect()系統調用      程式清單59.1伺服器端(Server Side)面向套接字(socket-oriented)協定                 程式清單59.2用戶端函數(The lient Side function)         無連接配接(Connectionless)套接字程式設計      程式清單59.3服務端      注意      記錄(Record)和檔案鎖定(Locking)      程序間通信      小結-------------------------------------------------------------------------------                                   --第59章--                                                             網絡程式設計                                      作者 Kamran Husain,Tim Parker譯者 胡淑瑜本章内容   端口和套接字   套接字程式設計   記錄和檔案鎖定   程序間通信   閱讀本章需你具有如下網絡程式設計的基本概念   端口和套接字   記錄和檔案鎖定   程序間通信      本文不可能在幾頁之内就能與你說清網絡程式設計的全部内容.事實上,一本第一卷就有800頁的專門描述網絡程式設計的參考書是最有用的.如果你真想進行網絡程式設計的話,你需要具有編譯器,TCP/IP和網絡作業系統的大量經驗.另外,你還需有極大的耐心.      欲知TCP/IP詳細内容,請參見Tim Parker所著之<
  <自學tcp ip十四天>
   > (Sams Publish-ing).                                  端口和套接字                                    網絡程式設計全靠套接字接受和發送資訊.盡管套接字這個詞好象顯得有些神秘,但其實這個概念極易了解.   大多數網絡應用程式使用兩個協定:傳輸控制協定(TCP)和使用者資料包協定(UDP).他們都使用一個端口号以識别應用程式.端口号為主機上所運作之程式所用,這樣就可以通過号碼象名字一樣來跟蹤每個應用程式.端口号讓作業系統更容易的知道有多少個應用程式在使用系統,以及哪些服務有效.   理論上,端口号可由每台主機上的管理者自由的配置設定.但為了更好的通信通常采用一些約定的協定.這些協定使能通過端口号識别一個系統向另一個系統所請求的服務的類型.基于如此理由,大多數系統維護一個包含端口号及它們所提供哪些服務的檔案.   端口号被從1開始配置設定.通常端口号超出255的部分被本地主機保留為私有用途.1到255之間的号碼被用于遠端應用程式所請求的程序和網絡服務.每個網絡通信循環地進出主計算機的TCP應用層.它被兩個所連接配接的号碼唯一地識别.這兩個号碼合起來叫做套接字.組成套接字的這兩個号碼就是機器的IP位址和TCP軟體所使用的端口号.   因為網絡通訊至少包括兩台機器,是以在發送和接收的機器上都存在一個套接字.由于每台機器的IP位址是唯一的,端口号在每台機器中也是唯一的,是以套接字在網絡中應該是唯一的.這樣的設定能使網絡中的兩個應用程式完全的基于套接字互相對話.   發送和接收的機器維護一個端口表,它列出了所有激活的端口号.兩台機器都包括一個程序叫做綁定,這是每個任務的入口,不過在兩台機器上恰恰相反.換句話說,如果一台機器的源端口号是23而目的端口号被設定成25,那麼另一台機器的源端口号設定成25目的端口号設定成23.                                  套接字程式設計                     Linux支援伯克利(BSD)風格的套接字程式設計.它同時支援面向連接配接和不連接配接類型的套接字.在面向連接配接的通訊中伺服器和客戶機在交換資料之前先要建立一個連接配接.再不連接配接通訊中資料被作為資訊的一部分被交換.無論那一種方式,伺服器總是最先啟動,把自己綁定(Banding)在一個套接字上,然後偵聽資訊.伺服器究竟怎樣試圖去偵聽就得依靠你程式設計所設定的連接配接的類型了.   你需要了解一些系統調用         socket()            bind()            listen()            accept()            setsockopt()和getsockopt()            connect()            sendto()            recvfrom()         我們将在以下的例子中使用這些系統調用.                                     socket()系統調用                  socket()系統調用為客戶機或伺服器建立一個套接字,套接字函數在如下定義:   #include
   
          #include
    
           int socket(int family, int type, int protocol)           在Linux中family=AF_UNIX.type可以是SOCK_STREAM它是可靠的雖然通訊速度較慢,也可以是SOCK_DGRAM它通訊速度較快但不可靠.如果type=SOCK_STREAM那麼protocol=IPPROTO_TCP.如果type=SOCK_DGRAM那麼protocol=IPPROTO_UDP.   如果出錯,函數将傳回-1.否則傳回一個套接字描述符你可以在程式後面的調用中通過套接字描述符使用這個套接字.   套接字建立時沒有指定名字.客戶機用套接字的名字讀寫它.這就是如下綁定函數所要做之事.                                  bind()系統調用                  bind()系統調用為沒有名字的套接字配置設定一個名字.綁定函數是這樣定義的:   #include
     
            #include
      
             int bind(int sockfd, struct sockaddr *saddr, int addrlen)      第一個參數是一個套接字描述符.第二個參數是名字所用的一個結構.第三個參數是結構的大小.   現在你已經為你的客戶機或伺服器限定了一個位址,你可以connect()伺服器或是在伺服器上偵聽了.如果你的程式是一個伺服器,那麼它把自己設定為偵聽然後等待連接配接.讓我們來看看哪些函數能讓你試圖這樣做.                                  listen()系統調用                  listen()系統調用被伺服器所使用.下面有它的定義:      #include
       
         #include
        
          int listen(int sockfd, int backlog); sockfd是套接字描述符.backlog是在一時間内尚未被決定是否拒絕的連接配接的号碼.一般使用标準值5.如發生錯誤則傳回值小于1. 如果這個調用成功,你就已經可以接受連接配接了. accept()系統調用 accept()調用被伺服器用于接受任何從客戶機connect()調用所引入的資訊.必須明白的是,如果沒有接受到連接配接這個函數将不傳回任何值.它是象這樣定義的: #include
         
           #include
          
            int accept(int sockfd, struct sockaddr *peeraddr, int addrlen) 除peeraddr指向發出連接配接請求的客戶機的資訊外,其它參數和bind()系統調用的相同.在資訊引入的基礎上,peeraddr所指向的結構的域被填上相應的值. setsockopt()和getsockopt()系統調用 Linux所提供的socket庫含有一個錯誤(bug).此錯誤表現為你不能為一個套接字重新啟用同一個端口号,即使在你正常關閉該套接字以後.例如,比方說,你編寫一個伺服器在一個套接字上等待的程式.伺服器打開套接字并在其上偵聽是沒有問題的.無論如何,總有一些原因(不管是正常還是非正常的結束程式)使你的程式需要重新啟動.然而重新開機動後你就不能把它綁定在原來那個端口上了.從bind()系統調用傳回的錯誤代碼總是報告說你試圖連接配接的端口已經被别的程序所綁定. 問題就是Linux核心在一個綁定套接字的程序結束後從不把端口标記為未用.在大多數UNIX系統中,端口可以被一個程序重複使用,甚至可以被其它程序使用. 在Linux中繞開這個問題的辦法是,但套接字已經打開但尚未有連接配接的時候用setsockopt()系統調用在其上設定選項(options).setsockopt()調用設定選項而getsockopt()從給定的套接字取得選項. 這裡是這些調用的文法: #include
           
             #include
            
              int getsockopt(int sockfd, int level, int name , char *value, int *optlen) int setsockopt(int sockfd, int level, int name , char *value, int *optlen) sockfd必須是一個已打開的套接字.level是函數所使用的協定标準(protocol level)(TCP/IP協定使用IPPROTO_TCP,套接字标準的選項實用SOL_SOCKET),選項的名稱(name)在套接字說明書中(man page)有詳細說明.*value指向為getsockopt()函數所擷取的值或setsockopt()函數所設定的值的位址.optlen指針指向一個整數,該整數包含參數以位元組計算的長度.其值被getsockopt()設定且其值必須被程式員設定當使用一個經由setsockopt(). 選項的所有細節可以在使用手冊中setsockopt的第二節(setsockopt(2))找到. 現在我們再回到Linux的錯誤上來.當你打開一個套接字時必須同時用下面的代碼段來調用setsockopt()函數: #ifdef LINUX opt = 1; len = sizeof(opt); setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt ,&len); #endif 隻有當你想讓程式不光是在Linux系統下使用時,#ifdef和#endif描述才是必須的.有些UNIX系統可能不支援或不需要SO_REUSEADDR标志. connect()系統調用 connect()調用被在面向連接配接的系統中客戶機連接配接伺服器時使用.connect()調用必須被用在bind()調用之後.它是這樣定義的: #include
             
               #include
              
                int connect(int sockfd, struct sockaddr *servs addr, int addrlen) 除servsaddr外所有的參數都和bind調用相同,servsaddr指向客戶機所連接配接的伺服器的資訊.當接到請求後,accept調用為伺服器建立一個新的套接字.然後伺服器就可以fork()一個新程序然後再去等待其它連接配接.在伺服器端你可以象程式清單59.1所顯示的那樣編寫代碼 程式清單59.1 面向套接字協定的伺服器端 #include 
               
                #include 
                
                 #include 
                 
                  #include 
                  
                   #define MY_PORT 6545main(int argc, char *argv[]){int sockfd, newfd;int cpid; /* child id */struct sockaddr_in servaddr;struct sockaddr_in clientInfo;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { myabort("Unable to create socket"); }#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endifbzero((char *)&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_family = htons(MY_PORT);/** The htonl (for a long integer) and htons (for short integer) convert* a host oriented byte order * into a network order.*/if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); }listen(sockfd,5);for (;;) { /* wait here */ newfd=accept(sockfd,(struct sockaddr *)&clientInfo, sizeof(struct sockaddr); if (newfd < 0) { myabort("Unable to accept on socket"); } if ((cpid = fork()) < 0) { myabort("Unable to fork on accept"); } else if (cpid == 0) { /* child */ close(sockfd); /* no need for original */ do_your_thing(newfd); exit(0); } close(newfd); /* in the parent */} 在面向連接配接的協定的程式中,伺服器執行以下函數: 調用socket()函數建立一個套接字. 調用bind()函數把自己綁定在一個位址上 調用listen()函數偵聽連接配接 調用accept()函數接受所有引入的請求 調用read()函數擷取引入的資訊然後調用write()回答 現在讓我們來看看用戶端所要做的事情,見程式清單59.2. 程式清單59.2 用戶端函數 #include 
                   
                    #include 
                    
                     #include 
                     
                      #include 
                      
                       #define MY_PORT 6545#define MY_HOST_ADDR "204.25.13.1"int getServerSocketId(){ int fd, len; struct sockaddr_in unix_addr; /* create a Unix domain stream socket */ if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { return(-1); }#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endif /* fill socket address structurew/our address */ memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sin_family = AF_INET; /* convert internet address to binary value*/ unix_addr.sin_addr.s_addr = inet_addr(MY_HOST_ADDR); unix_addr.sin_family = htons(MY_PORT); if (bind(fd, (struct sockaddr *) &unix_addr, len) < 0) return(-2); memset(&unix_addr, 0, sizeof(unix_addr)); if (connect(fd, (struct sockaddr *) &unix_addr, len) < 0) return(-3); return(fd);} 在面向連接配接的通信中客戶機要做如下一些事: 調用socket()函數建立一個套接字 調用connect()函數試圖連接配接伺服器 如果連接配接成功調用write()函數請求資料,調用read()函數接收引入的應答 不連接配接(Connectionless)套接字程式設計 現在讓我們來考慮一下不連接配接的資訊交換.其伺服器端的原理和面向連接配接的協定有所不同.伺服器并不調用listen和accept而是調用recvfrom().同樣,伺服器用sendto()函數來應答資訊.伺服器端程式見程式清單59.3. 程式清單59.3 伺服器端 #include 
                       
                        #include 
                        
                         #include 
                         
                          #include 
                          
                           #define MY_PORT 6545#define MAXM 4096char mesg[MAXM];main(int argc, char *argv[]){int sockfd, newfd;int cpid; /* child id */struct sockaddr_in servaddr;struct sockaddr_in clientInfo;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { myabort("Unable to create socket"); }#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endifbzero((char *)&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_family = htons(MY_PORT);/** The htonl (for a long integer) and htons (for short integer) convert* a host oriented byte order * into a network order.*/if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); }for (;;) { /* wait here */ n = recvfrom(sockfd, mesg, MAXM, 0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); doSomethingToIt(mesg); sendto(sockfd,mesg,n,0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); }} 看見了嗎,處理每個消息隻調用了兩個函數,這比面向連接配接的協定更容易.但你必須,無論如何,得在同一時間處理每個消息,因為消息從多台客戶機向伺服器湧來.而在面向連接配接的協定中,子程序總是知道每條消息從哪裡來. 客戶機同樣不能調用connect()系統調用.但是客戶機可以直接調用sendto()函數.客戶機端和伺服器端大緻相同.隻是它在調用recvfrom()之前調用sendto():#include 
                           
                            #include 
                            
                             int sendto((int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int type, /* of routing, leave 0 */ const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr */ 注意:如果你使用的是BSD系統,請使用sendto()系統調用,不要使用sendmsg(),因為sendto()性能更好. 如出錯則傳回-1,不過僅能檢查出本地錯誤. recvfrom()系統調用是這樣定義的: #include 
                             
                              #include 
                              
                               int recvfrom(int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int flags, /* of routing, leave 0 */ const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr */ 如果一個資訊大得緩沖區都放不下,那麼附加資訊将被砍掉.該調用可以立即傳回,也可以永久的等待.這取決于你把flags設定成什麼類型.你甚至可以設定逾時(timeout)值.在說明書(man pages)中可以找到recvfrom的更多資訊. 在此你已學會了如何利用Linux的性能上的優點設計網絡應用程式的基本知識.我們不打算再進一步的描述更複雜的網絡程式設計了.獲得更多細節資訊的一個極好的起點是參考W. Richard Stevens 的<
                               
                                >(Prentice Hall, 1990).此書乃衆多大學所使用的經典教材,内容極為詳盡. 記錄和檔案鎖定 當兩個程序共享一個檔案時,這之中存在一件非常危險的事情.如果一個程序改變了檔案目錄那麼必然影響到另一個程序.基于此理由,大多數作業系統采用一個互斥原則(mutuallyexclusive principle):當一個程序擁有一個檔案時,其它程序就不能再碰這個檔案.這叫做檔案鎖定. 這個技術非常容易實作.通常所發生的事是,所謂"鎖定檔案"就是建立一個和源檔案名同名的檔案再加上.lock擴充名.這就告訴其它程序這個檔案不能再碰了.Linux假脫機列印系統以及UUCP就是這樣實作檔案鎖定的.這可能是一種粗暴的方法,但程式設計上非常簡單. 不幸的是,你有幾個程序要同時迅速的處理同一條資訊時,這項技術對你并不實用.因為等待檔案打開和關閉所産生的延時将變得很長.同樣一個程序如不能正确的釋放檔案,其它程序将會挂在那裡一直等待下去以獲得存取權限. 由于這個原因,通常使用記錄鎖定.用記錄鎖定,一個大檔案的一小部分被鎖定以防止兩個程序同時改變它.如果有必要的話,記錄鎖定可以讓多個程序同時存取相同檔案的不同部分記錄.當然實作記錄鎖定程式設計要比實作檔案鎖定更複雜. 通常,要實作記錄鎖定你需要用到檔案偏移量或是到檔案起始處的字元數.在大多數程式中,一個範圍内的字元被鎖定.程式記錄鎖定範圍的起始處和長度,并儲存在其它程序能查詢到的地方.不管是編寫檔案鎖定還是記錄鎖定都需要對作業系統有很好的了解.但是并不難.特别是可以從Internet,網絡程式設計指導書和BBS上很容易地獲得成千的程式.你可以察看這些程式的源代碼. 程序間通信 網絡程式設計中通常包括兩個或更多的程序将互相對話(interprocess communications).是以程序通信的方法在網絡程式設計中是極為重要的.網絡程式設計在一些重要的方面不同于一般程式設計通常所使用的方法.一個傳統的程式可以通過全局變量或函數調用和不同的子產品(甚至同一機器上的其它應用程式)對話.但是在網絡上卻不行. 網絡程式設計的一個重要的目标是保證程序間不互相幹涉.否則系統可能被挂起或自鎖.是以,程序間必須使用簡潔有效的方法進行通信.在此方面,UNIX具有非常顯著的健壯性.因為UNIX的許多基本性能如管道,隊列等都非常适合網絡. 和單個的應用程式代碼相比,寫程序間通信的代碼十分複雜.如果你想寫這類程式,可以學習網絡程式設計指導書和BBS站點上的例子程式.以了解這些任務是何以完成的. 小結 很少有人想寫網絡應用程式,是以程序的細節最好留給那些想寫的人.實踐和查閱大量的例子程式是開始寫網絡代碼的最好的方法.但是要掌握這門技術卻要花許多年時間.-------------------------------英文原文-------------------------------- - 59 - Network Programming Ports and Sockets Socket Programming The socket() System Call The bind() System Call The listen() System Call The accept() System Call The setsockopt() and getsockopt() System Calls The connect() System Call Listing 59.1. The server side for a socket-oriented protocol. Listing 59.2. The client side function. Connectionless Socket Programming Listing 59.3. The server side. NOTE Record and File Locking Interprocess Communications Summary- 59 -Network Programmingby Kamran Husain and Tim ParkerIN THIS CHAPTER Ports and Sockets Socket Programming Record and File Locking Interprocess CommunicationsThis chapter looks at the basic concepts you need for network programming: Ports and sockets Record and file locking Interprocess communicationsIt is impossible to tell you how to program applications for a network in just afew pages. Indeed, the best available reference to network programming takesalmost 800 pages in the first volume alone! If you really want to do networkprogramming, you need a lot of experience with compilers, TCP/IP, and networkoperating systems--and you need a great deal of patience.For details on TCP/IP, check the book Teach Yourself TCP/IP in 14 Days, by TimParker (Sams Publishing).Ports and SocketsNetwork programming relies on the use of sockets to accept and transmitinformation. Although there is a lot of mystique about sockets, the concept isactually simple to understand.Most applications that use the two primary network protocols, TransmissionControl Protocol (TCP) and User Datagram Protocol (UDP) have a port number thatidentifies the application. A port number is used for each different applicationthe machine is handling, so it can keep track of those applications by numbersrather than names. The port number makes it easier for the operating system toknow how many applications are using the system and which services areavailable.In theory, port numbers can be assigned on individual machines by the systemadministrator, but some conventions have been adopted to allow bettercommunications. These conventions enable the port number to identify the type ofservice that one system is requesting from another. For this reason, mostsystems maintain a file of port numbers and their corresponding services.Port numbers are assigned starting from the number 1. Normally, port numbersabove 255 are reserved for the private use of the local machine, but numbersbetween 1 and 255 are used for processes requested by remote applications or fornetworking services.Each network communications circuit into and out of the host computer's TCPapplication layer is uniquely identified by a combination of two numbers,together called the socket. The socket is composed of the IP address of themachine and the port number used by the TCP software.Because at least two machines are involved in network communications, there willbe a socket on both the sending and the receiving machine. Because the IPaddress of each machine is unique and the port numbers are unique to eachmachine, socket numbers are also unique across the network. This setup enablesan application to talk to another application across the network based entirelyon the socket number.The sending and receiving machines maintain a port table that lists all activeport numbers. The two machines involved have reversed entries for each sessionbetween the two, a process called binding. In other words, if one machine hasthe source port number 23 and the destination port number set at 25, the othermachine has its source port number set at 25 and the destination port number setat 23.Socket ProgrammingLinux supports BSD-style socket programming. Both connection-oriented andconnectionless types of sockets are supported. In connection-orientedcommunication, the server and client establish a connection before any data isexchanged. In connectionless communication, data is exchanged as part of amessage. In either case, the server always starts first, binds itself to asocket, and listens to messages. How the server attempts to listen depends onthe type of connection for which you have programmed it.You need to know about a few system calls: socket() bind() listen() accept() setsockopt() and getsockopt() connect() sendto() recvfrom()We will cover these system calls in the following examples.The socket() System CallThe socket() system call creates a socket for the client or the server. Thesocket function is defined as shown here:#include
                                
                                 #include
                                 
                                  int socket(int family, int type, int protocol)For Linux, you will have family = AF_UNIX. The type is either SOCK_STREAM forreliable, though slower, communications or SOCK_DGRAM for faster, but lessreliable, communications. The protocol should be IPPROTO_TCP for SOCK_STREAM andIPPROTO_UDP for SOCK_DGRAM.The return value from this function is -1 if there was an error; otherwise, it'sa socket descriptor. You will use this socket descriptor to refer to this socketin all subsequent calls in your program.Sockets are created without a name. Clients use the name of the socket to reador write to it. This is where the bind function comes in.The bind() System CallThe bind() system call assigns a name to an unnamed socket. The bind function isdefined like this:#include
                                  
                                   #include
                                   
                                    int bind(int sockfd, struct sockaddr *saddr, int addrlen)The first item is a socket descriptor. The second is a structure with the nameto use, and the third item is the size of the structure.Now that you have bound an address for your server or client, you can connect()to it or listen on it. If your program is a server, it sets itself up to listenand accept connections. Let's look at the function available for such anendeavor.The listen() System CallThe listen() system call is used by the server. It is defined in the followingway:#include
                                    
                                     #include
                                     
                                      int listen(int sockfd, int backlog);The sockfd is the descriptor of the socket. The backlog is the number ofconnections that are pending at one time before any are rejected. Use thestandard value of 5 for backlog. A returned value of less than 1 indicates anerror.If this call is successful, you can accept connections.The accept() System CallThe accept() system call is used by a server to accept any incoming messagesfrom clients' connect() calls. Be aware that this function does not return if noconnections are received. It is defined like this:#include
                                      
                                       #include
                                       
                                        int accept(int sockfd, struct sockaddr *peeraddr, int addrlen)The parameters are the same as those for the bind call, with the exception thatthe peeraddr points to information about the client that is making a connectionrequest. Based on the incoming message, the fields in the structure pointed atby peeraddr are filled out.The setsockopt() and getsockopt() System CallsThe socket libraries provided with Linux include a bug. The symptom of this bugis that you cannot reuse a port number for a socket even if you closed thesocket properly. For example, say you write your own server that waits on asocket. This server opens the socket and listens on it with no problems.However, for some reason (a crash or normal termination), when the program isrestarted, you are not able to bind to the same port. The error codes from thebind() call will always return an error indicating that the port you areattempting to connect to is already bound to another process.The problem is that the Linux kernel never marks the port as unused when theprocess bound to a socket terminates. In most other UNIX systems, the port canbe used again by another invocation of the same or even another process.The way to get around this problem in Linux is to use the setsockopt() systemcall to set the options on a socket when it is opened and before a connection ismade on it. The setsockopt() sets options and the getsockopt()call gets optionsfor a given socket.Here is the syntax for these calls:#include
                                        
                                         #include
                                         
                                          int getsockopt(int sockfd, int level, int name, char *value, int *optlen)int setsockopt(int sockfd, int level, int name, char *value, int *optlen)The sockfd must be an open socket. The level is the protocol level to use forthe function (IPPROTO_TCP for TCP/IP and SOL_SOCKET for socket level options),and the name of the option is as defined in the socket's man page. The *valuepointer points to a location where a value is stored for getsockopt() or when avalue is read for setsockopt(). The optlen parameter is a pointer to an integercontaining the length of the parameters in bytes; the value is set bygetsockopt() and must be set by the programmer when making a call viasetsockopt().The full man page with details of all the options is found in the man pagesetsockopt(2).Now back to the bug in Linux. When you open a socket, you must also call thesetsockopt() function with the following segment of code:#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endifThe #ifdef and #endif statements are necessary only if you want to port the codeover to systems other than Linux. Some UNIX systems might not support or requirethe SO_REUSEADDR flag.The connect() System CallThe connect() system call is used by clients to connect to a server in aconnection-oriented system. This connect() call should be made after the bind()call. It is defined like this:#include
                                          
                                           #include
                                           
                                            int connect(int sockfd, struct sockaddr *servsaddr, int addrlen)The parameters are the same as those for the bind call, with the exception thatthe servsaddr points to information about the server that the client isconnecting to. The accept call creates a new socket for the server to work withthe request. This way, the server can fork() off a new process and wait for moreconnections. On the server side of things, you would have code that looks likethat shown in Listing 59.1.Listing 59.1. The server side for a socket-oriented protocol.#include 
                                            
                                             #include 
                                             
                                              #include 
                                              
                                               #include 
                                               
                                                #define MY_PORT 6545main(int argc, char *argv[]){int sockfd, newfd;int cpid; /* child id */struct sockaddr_in servaddr;struct sockaddr_in clientInfo;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { myabort("Unable to create socket"); }#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endifbzero((char *)&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_family = htons(MY_PORT);/** The htonl (for a long integer) and htons (for short integer) convert* a host oriented byte order * into a network order.*/if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); }listen(sockfd,5);for (;;) { /* wait here */ newfd=accept(sockfd,(struct sockaddr *)&clientInfo, sizeof(struct sockaddr); if (newfd < 0) { myabort("Unable to accept on socket"); } if ((cpid = fork()) < 0) { myabort("Unable to fork on accept"); } else if (cpid == 0) { /* child */ close(sockfd); /* no need for original */ do_your_thing(newfd); exit(0); } close(newfd); /* in the parent */}}In the case of connection-oriented protocols, the server performs the followingfunctions: Creates a socket with a call to the socket() function Binds itself to an address with the bind() function call Listens for connections with the listen() function call Accepts any incoming requests with the accept() function call Gets incoming messages with the read() function and replies with the write() callNow let's look at the client side of things, in Listing 59.2.Listing 59.2. The client side function.#include 
                                                
                                                 #include 
                                                 
                                                  #include 
                                                  
                                                   #include 
                                                   
                                                    #define MY_PORT 6545#define MY_HOST_ADDR "204.25.13.1"int getServerSocketId(){ int fd, len; struct sockaddr_in unix_addr; /* create a Unix domain stream socket */ if ( (fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { return(-1); }#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endif /* fill socket address structure w/our address */ memset(&unix_addr, 0, sizeof(unix_addr)); unix_addr.sin_family = AF_INET; /* convert internet address to binary value*/ unix_addr.sin_addr.s_addr = inet_addr(MY_HOST_ADDR); unix_addr.sin_family = htons(MY_PORT); if (bind(fd, (struct sockaddr *) &unix_addr, len) < 0) return(-2); memset(&unix_addr, 0, sizeof(unix_addr)); if (connect(fd, (struct sockaddr *) &unix_addr, len) < 0) return(-3); return(fd);}The client for connection-oriented communication also takes the following steps: Creates a socket with a call to the socket() function Attempts to connect to the server with a connect() call If a connection is made, requests data with the write() call, and reads incoming replies with the read() functionConnectionless Socket ProgrammingNow let's consider the case of a connectionless exchange of information. Theprinciple on the server side is different from the connection-oriented serverside in that the server calls recvfrom() rather than the listen and acceptcalls. Also, to reply to messages, the server uses the sendto() function call.See Listing 59.3 for the server side.Listing 59.3. The server side.#include 
                                                    
                                                     #include 
                                                     
                                                      #include 
                                                      
                                                       #include 
                                                       
                                                        #define MY_PORT 6545#define MAXM 4096char mesg[MAXM];main(int argc, char *argv[]){int sockfd, newfd;int cpid; /* child id */struct sockaddr_in servaddr;struct sockaddr_in clientInfo;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0) < 0) { myabort("Unable to create socket"); }#ifdef LINUXopt = 1; len = sizeof(opt);setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);#endifbzero((char *)&servaddr, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_family = htons(MY_PORT);/** The htonl (for a long integer) and htons (for short integer) convert* a host oriented byte order * into a network order.*/if (bind(sockfd,(struct sockaddr *)&servaddr,sizeof(struct sockaddr)) < 0) { myabort("Unable to bind socket"); }for (;;) { /* wait here */ n = recvfrom(sockfd, mesg, MAXM, 0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); doSomethingToIt(mesg); sendto(sockfd,mesg,n,0, (struct sockaddr *)&clientInfo, sizeof(struct sockaddr)); }}As you can see, the two function calls to process each message make this aneasier implementation than a connection-oriented one. You must, however, processeach message one at a time because messages from multiple clients can bemultiplexed together. In a connection-oriented scheme, the child process alwaysknows where each message originated.The client does not have to call the connect() system call either. Instead, theclient can call the sendto() function directly. The client side is identical tothe server side, with the exception that the sendto call is made before therecvfrom()call:#include 
                                                        
                                                         #include 
                                                         
                                                          int sendto((int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int type, /* of routing, leave 0 */ const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr */ NOTE: If you are a BSD user, use the sendto() call, not the sendmsg() call. The sendto() call is more efficient. Any errors are indicated by a return value of -1. Only local errors aredetected.The recvfrom() system call is defined as shown here:#include 
                                                          
                                                           #include 
                                                           
                                                            int recvfrom(int sockfd, const void *message__, /* the pointer to message */ int length, /* of message */ unsigned int flags, /* of routing, leave 0 */ const struct sockaddr * client, /* where to send it */ int length ); /* of sockaddr */If a message is too long to fit in the supplied buffer, the extra bytes arediscarded. The call might return immediately or wait forever, depending on thetype of the flag being set. You can even set timeout values. Check the man pagesfor recvfrom for more information.There you have it--the very basics of how to program applications to takeadvantage of the networking capabilities under Linux. We have not even scratchedthe surface of all the intricacies of programming for networks. A good startingpoint for more detailed information would be UNIX Network Programming, by W.Richard Stevens (Prentice Hall, 1990). This book, a classic, is used in mostuniversities and is by far the most detailed book to date.Record and File LockingWhen two processes want to share a file, the danger exists that one processmight affect the contents of the file, and thereby affect the other process. Forthis reason, most operating systems use a mutually exclusive principle: when oneprocess has a file open, no other process can touch it. This is called filelocking.This technique is simple to implement. What usually happens is that a "lockfile" is created with the same name as the original file but with the extension.lock, which tells other processes that the file is unavailable. This is howmany Linux spoolers, such as the print system and UUCP, implement file locking.It is a brute-force method, perhaps, but effective and easy to program.Unfortunately, this technique is not good when you must have several processesaccess the same information quickly, because the delays waiting for file openingand closing can grow to be appreciable. Also, if one process doesn't release thefile properly, other processes can hang there, waiting for access.For this reason, record locking is sometimes implemented. With record locking, asingle part of a larger file is locked to prevent two processes from changingits contents at the same time. Record locking enables many processes to accessthe same file at the same time, each updating different records within the file,if necessary. The programming necessary to implement record locking is morecomplex than that for file locking, of course.Normally, to implement record locking, you use a file offset, or the number ofcharacters from the beginning of the file. In most cases, a range of charactersis locked; the program has to note the start of the locking region and thelength of it, and then store that information where other processes can examineit.Writing either file-locking or record-locking code requires a good understandingof the operating system but is otherwise not difficult, especially becausethousands of programs are readily available from the Internet, in networkingprogramming books, and on BBSs to examine for sample code.Interprocess CommunicationsNetwork programming always involves two or more processes talking to each other(interprocess communications), so the way in which processes communicate isvitally important to network programmers. Network programming differs from theusual method of programming in a few important aspects. A traditional programcan talk to different modules (or even other applications on the same machine)through global variables and function calls. That doesn't work across networks.A key goal of network programming is to ensure that processes don't interferewith each other. Otherwise, systems can get bogged down or can lock up.Therefore, processes must have a clean and efficient method of communicating.UNIX is particularly strong in this regard, because many of the basic UNIXcapabilities, such as pipes and queues, are used effectively across networks.Writing code for interprocess communications is quite difficult compared tosingle application coding. If you want to write this type of routine, you shouldstudy sample programs from a network programming book or a BBS site to see howthis task is accomplished.SummaryFew people need to write network applications, so the details of the process arebest left to those who want them. Experience and lots of examples are the bestway to begin writing network code, and mastering the skills can take many years.
                                                           
                                                          
                                                         
                                                        
                                                       
                                                      
                                                     
                                                    
                                                   
                                                  
                                                 
                                                
                                               
                                              
                                             
                                            
                                           
                                          
                                         
                                        
                                       
                                      
                                     
                                    
                                   
                                  
                                 
                                
                               
                              
                             
                            
                           
                          
                         
                        
                       
                      
                     
                    
                   
                  
                 
                
               
              
             
            
           
          
         
        
       
      
     
    
   
        

繼續閱讀