GNU 網絡程序

______________________________________________________________________________
|                                   版權聲明                 
|                                         
|  1、本文能夠轉載、修改及引用,但請保留本聲明和其後所付英文原文。             
|  2、任何狀況下,做者和譯者姓名不可刪除。                                     
|  3、任何狀況下,本文不可進入商業領域。                                       
|                                                                              
|                                                  胡淑瑜                      
|                                        (husuyu@linux.cqi.com.cn)             
|                                                                              
|                                                 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<sys/types.h>
   
   #include<sys/socket.h>
   
   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<sys/types.h>
   
   #include<sys/socket.h>
   
   int bind(int sockfd, struct sockaddr *saddr, int addrlen)
   
   第一個參數是一個套接字描述符.第二個參數是名字所用的一個結構.第三個參數是結構
的大小.
   如今你已經爲你的客戶機或服務器限定了一個地址,你能夠connect()服務器或是在服務
器上偵聽了.若是你的程序是一個服務器,那麼它把本身設置爲偵聽而後等待鏈接.讓咱們來
看看哪些函數能讓你試圖這樣作.
                                  listen()系統調用
               
   listen()系統調用被服務器所使用.下面有它的定義:
   
   #include<sys/types.h>
   
   #include<sys/socket.h>
   
   int listen(int sockfd, int backlog);
   
   sockfd是套接字描述符.backlog是在一時間內還沒有被決定是否拒絕的鏈接的號碼.通常
使用標準值5.如發生錯誤則返回值小於1.
   若是這個調用成功,你就已經能夠接受鏈接了.
   
                                  accept()系統調用
               
   accept()調用被服務器用於接受任何從客戶機connect()調用所引入的信息.必須明白的
是,若是沒有接受到鏈接這個函數將不返回任何值.它是象這樣定義的:
   #include<sys/types.h>
   
   #include<sys/socket.h>
   
   int accept(int sockfd, struct sockaddr *peeraddr, int addrlen)
   
   除peeraddr指向發出鏈接請求的客戶機的信息外,其它參數和bind()系統調用的相同.在
信息引入的基礎上,peeraddr所指向的結構的域被填上相應的值.
                      setsockopt()和getsockopt()系統調用 
   
   Linux所提供的socket庫含有一個錯誤(bug).此錯誤表現爲你不能爲一個套接字從新啓
用同一個端口號,即便在你正常關閉該套接字之後.例如,比方說,你編寫一個服務器在一個
套接字上等待的程序.服務器打開套接字並在其上偵聽是沒有問題的.不管如何,總有一些原
因(不論是正常仍是非正常的結束程序)使你的程序須要從新啓動.然而重啓動後你就不能把
它綁定在原來那個端口上了.從bind()系統調用返回的錯誤代碼老是報告說你試圖鏈接的端
口已經被別的進程所綁定.
   問題就是Linux內核在一個綁定套接字的進程結束後從不把端口標記爲未用.在大多數UN
IX系統中,端口能夠被一個進程重複使用,甚至能夠被其它進程使用.
   在Linux中繞開這個問題的辦法是,但套接字已經打開但還沒有有鏈接的時候用setsockopt
()系統調用在其上設定選項(options).setsockopt()調用設置選項而getsockopt()從給定
的套接字取得選項.
   這裏是這些調用的語法:
   
   #include<sys/types.h>
   
   #include<sys/socket.h>
   
   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)(T
CP/IP協議使用IPPROTO_TCP,套接字標準的選項實用SOL_SOCKET),選項的名稱(name)在套接
字說明書中(man page)有詳細說明.*value指向爲getsockopt()函數所獲取的值或setsocko
pt()函數所設置的值的地址.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描述纔是必須的.有些U
NIX系統可能不支持或不須要SO_REUSEADDR標誌.
                                  connect()系統調用
               
   connect()調用被在面向鏈接的系統中客戶機鏈接服務器時使用.connect()調用必須被
用在bind()調用以後.它是這樣定義的:
   #include<sys/types.h>
   
   #include<sys/socket.h>
   
   int connect(int sockfd, struct sockaddr *servs
   addr, int addrlen)   
   
   除servsaddr外全部的參數都和bind調用相同,servsaddr指向客戶機所鏈接的服務器的
信息.當接到請求後,accept調用爲服務器建立一個新的套接字.而後服務器就能夠fork()一
個新進程而後再去等待其它鏈接.在服務器端你能夠象程序清單59.1所顯示的那樣編寫代碼
                                  程序清單59.1
               
                            面向套接字協議的服務器端  
         
#include    <sys/types.h>
#include    <sys/socket.h>
#include <linux/in.h>
#include <linux/net.h>

#define MY_PORT 6545

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 LINUX
opt = 1; len = sizeof(opt);
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);
#endif

bzero((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    <sys/types.h>
#include    <sys/socket.h>
#include <linux/in.h>
#include <linux/net.h>

#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 LINUX
opt = 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    <sys/types.h>
#include    <sys/socket.h>
#include <linux/in.h>
#include <linux/net.h>

#define MY_PORT 6545
#define MAXM   4096
char 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 LINUX
opt = 1; len = sizeof(opt);
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);
#endif

bzero((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 <sys/types.h>
#include <sys/socket.h>

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(),由於send
to()性能更好.
   如出錯則返回-1,不過僅能檢查出本地錯誤.
   
   recvfrom()系統調用是這樣定義的:
   
#include <sys/types.h>
#include <sys/socket.h>

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. Ric
hard Stevens 的<<Unix 網絡程序設計>>(Prentice Hall, 1990).此書乃衆多大學所使用
的經典教材,內容極爲詳盡.
                                  記錄和文件鎖定
                                  
   當兩個進程共享一個文件時,這之中存在一件很是危險的事情.若是一個進程改變了文件
目錄那麼必然影響到另外一個進程.基於此理由,大多數操做系統採用一個互斥原則(mutually
exclusive 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 Programming
by Kamran Husain and Tim Parker
IN THIS CHAPTER
    Ports and Sockets
    Socket Programming
    Record and File Locking
    Interprocess Communications
This chapter looks at the basic concepts you need for network programming:
    Ports and sockets
    Record and file locking
    Interprocess communications
It is impossible to tell you how to program applications for a network in just a
few pages. Indeed, the best available reference to network programming takes
almost 800 pages in the first volume alone! If you really want to do network
programming, you need a lot of experience with compilers, TCP/IP, and network
operating 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 Tim
Parker (Sams Publishing).
Ports and Sockets
Network programming relies on the use of sockets to accept and transmit
information. Although there is a lot of mystique about sockets, the concept is
actually simple to understand.
Most applications that use the two primary network protocols, Transmission
Control Protocol (TCP) and User Datagram Protocol (UDP) have a port number that
identifies the application. A port number is used for each different application
the machine is handling, so it can keep track of those applications by numbers
rather than names. The port number makes it easier for the operating system to
know how many applications are using the system and which services are
available.
In theory, port numbers can be assigned on individual machines by the system
administrator, but some conventions have been adopted to allow better
communications. These conventions enable the port number to identify the type of
service that one system is requesting from another. For this reason, most
systems maintain a file of port numbers and their corresponding services.
Port numbers are assigned starting from the number 1. Normally, port numbers
above 255 are reserved for the private use of the local machine, but numbers
between 1 and 255 are used for processes requested by remote applications or for
networking services.
Each network communications circuit into and out of the host computer's TCP
application layer is uniquely identified by a combination of two numbers,
together called the socket. The socket is composed of the IP address of the
machine and the port number used by the TCP software.
Because at least two machines are involved in network communications, there will
be a socket on both the sending and the receiving machine. Because the IP
address of each machine is unique and the port numbers are unique to each
machine, socket numbers are also unique across the network. This setup enables
an application to talk to another application across the network based entirely
on the socket number.
The sending and receiving machines maintain a port table that lists all active
port numbers. The two machines involved have reversed entries for each session
between the two, a process called binding. In other words, if one machine has
the source port number 23 and the destination port number set at 25, the other
machine has its source port number set at 25 and the destination port number set
at 23.
Socket Programming
Linux supports BSD-style socket programming. Both connection-oriented and
connectionless types of sockets are supported. In connection-oriented
communication, the server and client establish a connection before any data is
exchanged. In connectionless communication, data is exchanged as part of a
message. In either case, the server always starts first, binds itself to a
socket, and listens to messages. How the server attempts to listen depends on
the 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 Call
The socket() system call creates a socket for the client or the server. The
socket function is defined as shown here:
#include<sys/types.h>
#include<sys/socket.h>
int socket(int family, int type, int protocol)
For Linux, you will have family = AF_UNIX. The type is either SOCK_STREAM for
reliable, though slower, communications or SOCK_DGRAM for faster, but less
reliable, communications. The protocol should be IPPROTO_TCP for SOCK_STREAM and
IPPROTO_UDP for SOCK_DGRAM.
The return value from this function is -1 if there was an error; otherwise, it's
a socket descriptor. You will use this socket descriptor to refer to this socket
in all subsequent calls in your program.
Sockets are created without a name. Clients use the name of the socket to read
or write to it. This is where the bind function comes in.
The bind() System Call
The bind() system call assigns a name to an unnamed socket. The bind function is
defined like this:
#include<sys/types.h>
#include<sys/socket.h>

int bind(int sockfd, struct sockaddr *saddr, int addrlen)
The first item is a socket descriptor. The second is a structure with the name
to 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 listen
and accept connections. Let's look at the function available for such an
endeavor.
The listen() System Call
The listen() system call is used by the server. It is defined in the following
way:
#include<sys/types.h>
#include<sys/socket.h>

int listen(int sockfd, int backlog);
The sockfd is the descriptor of the socket. The backlog is the number of
connections that are pending at one time before any are rejected. Use the
standard value of 5 for backlog. A returned value of less than 1 indicates an
error.
If this call is successful, you can accept connections.
The accept() System Call
The accept() system call is used by a server to accept any incoming messages
from clients' connect() calls. Be aware that this function does not return if no
connections are received. It is defined like this:
#include<sys/types.h>
#include<sys/socket.h>

int accept(int sockfd, struct sockaddr *peeraddr, int addrlen)
The parameters are the same as those for the bind call, with the exception that
the peeraddr points to information about the client that is making a connection
request. Based on the incoming message, the fields in the structure pointed at
by peeraddr are filled out.
The setsockopt() and getsockopt() System Calls
The socket libraries provided with Linux include a bug. The symptom of this bug
is that you cannot reuse a port number for a socket even if you closed the
socket properly. For example, say you write your own server that waits on a
socket. This server opens the socket and listens on it with no problems.
However, for some reason (a crash or normal termination), when the program is
restarted, you are not able to bind to the same port. The error codes from the
bind() call will always return an error indicating that the port you are
attempting to connect to is already bound to another process.
The problem is that the Linux kernel never marks the port as unused when the
process bound to a socket terminates. In most other UNIX systems, the port can
be 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() system
call to set the options on a socket when it is opened and before a connection is
made on it. The setsockopt() sets options and the getsockopt()call gets options
for a given socket.
Here is the syntax for these calls:
#include<sys/types.h>
#include<sys/socket.h>

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 for
the 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 *value
pointer points to a location where a value is stored for getsockopt() or when a
value is read for setsockopt(). The optlen parameter is a pointer to an integer
containing the length of the parameters in bytes; the value is set by
getsockopt() and must be set by the programmer when making a call via
setsockopt().
The full man page with details of all the options is found in the man page
setsockopt(2).
Now back to the bug in Linux. When you open a socket, you must also call the
setsockopt() function with the following segment of code:
#ifdef LINUX
opt = 1; len = sizeof(opt);
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);

#endif
The #ifdef and #endif statements are necessary only if you want to port the code
over to systems other than Linux. Some UNIX systems might not support or require
the SO_REUSEADDR flag.
The connect() System Call
The connect() system call is used by clients to connect to a server in a
connection-oriented system. This connect() call should be made after the bind()
call. It is defined like this:
#include<sys/types.h>
#include<sys/socket.h>

int connect(int sockfd, struct sockaddr *servsaddr, int addrlen)
The parameters are the same as those for the bind call, with the exception that
the servsaddr points to information about the server that the client is
connecting to. The accept call creates a new socket for the server to work with
the request. This way, the server can fork() off a new process and wait for more
connections. On the server side of things, you would have code that looks like
that shown in Listing 59.1.
Listing 59.1. The server side for a socket-oriented protocol.
#include    <sys/types.h>
#include    <sys/socket.h>
#include <linux/in.h>
#include <linux/net.h>

#define MY_PORT 6545

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 LINUX
opt = 1; len = sizeof(opt);
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);
#endif

bzero((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 following
functions:
    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()
    call
Now let's look at the client side of things, in Listing 59.2.
Listing 59.2. The client side function.
#include    <sys/types.h>
#include    <sys/socket.h>
#include <linux/in.h>
#include <linux/net.h>

#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 LINUX
opt = 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() function
Connectionless Socket Programming
Now let's consider the case of a connectionless exchange of information. The
principle on the server side is different from the connection-oriented server
side in that the server calls recvfrom() rather than the listen and accept
calls. 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    <sys/types.h>
#include    <sys/socket.h>
#include <linux/in.h>
#include <linux/net.h>

#define MY_PORT 6545
#define MAXM   4096
char 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 LINUX
opt = 1; len = sizeof(opt);
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,&len);
#endif

bzero((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 an
easier implementation than a connection-oriented one. You must, however, process
each message one at a time because messages from multiple clients can be
multiplexed together. In a connection-oriented scheme, the child process always
knows where each message originated.
The client does not have to call the connect() system call either. Instead, the
client can call the sendto() function directly. The client side is identical to
the server side, with the exception that the sendto call is made before the
recvfrom()call:
#include <sys/types.h>
#include <sys/socket.h>

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 are
detected.
The recvfrom() system call is defined as shown here:
#include <sys/types.h>
#include <sys/socket.h>

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 are
discarded. The call might return immediately or wait forever, depending on the
type of the flag being set. You can even set timeout values. Check the man pages
for recvfrom for more information.
There you have it--the very basics of how to program applications to take
advantage of the networking capabilities under Linux. We have not even scratched
the surface of all the intricacies of programming for networks. A good starting
point for more detailed information would be UNIX Network Programming, by W.
Richard Stevens (Prentice Hall, 1990). This book, a classic, is used in most
universities and is by far the most detailed book to date.
Record and File Locking
When two processes want to share a file, the danger exists that one process
might affect the contents of the file, and thereby affect the other process. For
this reason, most operating systems use a mutually exclusive principle: when one
process has a file open, no other process can touch it. This is called file
locking.
This technique is simple to implement. What usually happens is that a "lock
file" 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 how
many 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 processes
access the same information quickly, because the delays waiting for file opening
and closing can grow to be appreciable. Also, if one process doesn't release the
file properly, other processes can hang there, waiting for access.
For this reason, record locking is sometimes implemented. With record locking, a
single part of a larger file is locked to prevent two processes from changing
its contents at the same time. Record locking enables many processes to access
the same file at the same time, each updating different records within the file,
if necessary. The programming necessary to implement record locking is more
complex than that for file locking, of course.
Normally, to implement record locking, you use a file offset, or the number of
characters from the beginning of the file. In most cases, a range of characters
is locked; the program has to note the start of the locking region and the
length of it, and then store that information where other processes can examine
it.
Writing either file-locking or record-locking code requires a good understanding
of the operating system but is otherwise not difficult, especially because
thousands of programs are readily available from the Internet, in networking
programming books, and on BBSs to examine for sample code.
Interprocess Communications
Network programming always involves two or more processes talking to each other
(interprocess communications), so the way in which processes communicate is
vitally important to network programmers. Network programming differs from the
usual method of programming in a few important aspects. A traditional program
can 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 interfere
with 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 UNIX
capabilities, such as pipes and queues, are used effectively across networks.
Writing code for interprocess communications is quite difficult compared to
single application coding. If you want to write this type of routine, you should
study sample programs from a network programming book or a BBS site to see how
this task is accomplished.
Summary
Few people need to write network applications, so the details of the process are
best left to those who want them. Experience and lots of examples are the best
way to begin writing network code, and mastering the skills can take many years.
相關文章
相關標籤/搜索