天天看點

Linux網絡程式設計筆記(修訂版)1.        基本概念2.        基本接口3.        最常用的伺服器模型.3.1.      循環伺服器:3.1.1.    循環伺服器之UDP伺服器3.1.2.    循環伺服器之TCP伺服器3.2.      并發伺服器3.2.1.    并發伺服器之TCP伺服器3.2.2.    并發伺服器之多路複用I/O3.2.3.    并發伺服器之UDP伺服器4.        資料流程5.        執行個體分析6.        參考連結及文章

我的網絡程式設計筆記, 因為最近又要做Linux下的網絡程式設計,故重新修訂, 其中一些内容參考了文末的連結及文章

1.   基本概念

2.   基本接口

2.1.   打開一個socket

2.2.   将socket綁定定指定的端口—bind

2.3.   偵聽socket—listen (伺服器端)

2.4.   等待接收請求—accept (伺服器端)

2.5.   連接配接到socket—connect

2.6.   利用socket傳輸資料

2.6.1.    read和write

2.6.2.    recv和send

2.6.3.    recvfrom和sendto

2.6.4.    recvmsg和sendmsg

2.7.   套接字的關閉close/shutdown

3.   最常用的伺服器模型.

3.1.   循環伺服器:

3.1.1.   循環伺服器之UDP伺服器

3.1.2.   循環伺服器之TCP伺服器

3.2.   并發伺服器

3.2.1.   并發伺服器之TCP伺服器

3.2.2.   并發伺服器之多路複用I/O

3.2.3.   并發伺服器之UDP伺服器

4.   執行個體分析

5.   參考連結及文章

1.        基本概念

說到網絡程式設計,不得不先提到OSI參考模型,其七層模型從下到上分别為

1.實體層(Physical Layer,PH)

2.資料鍊路層(Data Link Layer,DL)

3.網絡層(Network Layer,N)

4.運輸層(Transport Layer,T)

5.會話層(Session Layer,S)

6.表示層(Presentation Layer,P)

7.應用層(Application Layer,A)

現在最流行的網絡協定無疑就是TCP/IP(Transmission Control Protocol/Internet Protocol)協定.

注:

l        IP (Internet Protocol),網際協定;IP是TCP/IP的最底層,高層協定都要轉化為IP包,IP包含了源位址和目的位址,路由決策也發生在IP層;

l        ICMP (Internet Control Message Protocol),網際封包協定;它包括了資料包的錯誤、控制等相關資訊。比如ping指令就是利用ICMP來測試一個網絡的連接配接情況的工具;

l        TCP (Transmission Control Protocol),傳輸控制協定。TCP運作在IP之上,是基于資料流連接配接和面向的協定,應用程式把資料要經過TCP/IP的分割成若幹包,這樣資料就以位元組流發送和接收,到達目的地後,TCP/IP再按順序進行組裝。TCP/IP要保證機器與機器之間的連接配接的可靠性,還要有糾錯。TCP是否被選擇,取決于應用程式或服務;

l        UDP (User Datagram Protocol) ,使用者資料報協定 ,象TCP一樣運作在IP之上,是基于資料報或分組的協定,UDP/IP可以直接發送和接收資料封包,而不必做驗證,這一點與TCP/IP不同。TCP是否被選擇,取決于應用程式或服務;

2.        基本接口

以Unix/Linux平台為例,系統會建立許多網絡服務程式

$netstat -a

Proto Recv-Q Send-Q Local Address      Foreign Address          State  

tcp        0      0         *:1975                  :                      LISTEN

udp        0      0         *:1978                  :

tcp        0      0 MYServer:34320   192.168.1.2:1521       ESTABLISHED

以上可以看到有三個網絡連接配接,一個是TCP接連在1975端口偵聽,一個UDP連接配接在1978端口,另外一個TCP連接配接是連接配接到DB的

我們可以看出,用戶端程式需要通過”主機名:端口号”與伺服器建立連接配接.主機名其實就是IP位址.

上面的MYServer其實是192.168.1.3, 這樣的主機名與IP的對應關系由本機的host檔案或DNS伺服器解析提供.

$more /etc/hosts

# that require network functionality will fail.

127.0.0.1      localhost.localdomain   localhost

192.168.1.3   MYServer

$ more /etc/resolv.conf

nameserver 192.168.1.1

當然,我們在程式設計時無需查詢這些檔案或伺服器,系統提供了API:

gethostbyname/gethostbyaddr

#include <netdb.h>

extern int h_errno;

struct hostent *gethostbyname(const char *name);

#include <sys/socket.h>        /* for AF_INET */

struct hostent *gethostbyaddr(const char *addr, int len, int type);

它們會傳回一個指針,指向如下結構的對象

struct     hostent {

   char   h_name;        / official name */

   char   **h_aliases;    /* alias list */

   int    h_addrtype;     /* address type */

   int    h_length;       /* address length */

   char   **h_addr_list;  /* address list */

};

#define h_addr h_addr_list[0]

/* backward compatibility */

h_addr_list是一個與域名對應的IP位址的清單,勤快的程式員會依次嘗試連接配接清單中傳回的IP位址

懶惰的程式員隻會用h_addr,h_addr_list清單中的第一個IP位址

不同的應用程式使用不同的端口和協定,比如常用的ftp就用21端口和tcp協定

$more /etc/services

# service-name  port/protocol  [aliases ...]   [# comment]

ftp             21/tcp ftp             21/udp

          fsp fspd

ssh             22/tcp                          # SSH Remote Login Protocol

ssh             22/udp                          # SSH Remote Login Protocol

telnet          23/tcp

telnet          23/udp

# 24 - private mail system

smtp            25/tcp          mail

smtp            25/udp          mail

...

同樣,程式中是無需查詢這個檔案的,Unix提供了getservbyname

struct servent *getservbyname(const char *name, const char *proto);

傳回

struct servent {

             char    s_name;        / official service name */

             char    **s_aliases;    /* alias list */

             int     s_port;         /* port number */

             char    s_proto;       / protocol to use */

         }

知道主機名(IP)和端口号,我們就可以編寫在這台主機的運作的或是連接配接到它的網絡應用程式了

Unix/Linux系統中是通過提供套接字(socket)來進行網絡程式設計的.網絡程式通過socket和其它幾個函數的調用,會傳回一個通訊的檔案描述符,我們

可以将這個描述符看成普通的檔案的描述符來操作,可以通過向描述符讀寫操作實作網絡之間的資料交流.

2.1.      打開一個socket 

int socket(int domain,int type,int protocol)

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

type:我們網絡程式所采用的通訊協定(SOCK_STREAM,SOCK_DGRAM等)SOCK_STREAM表明我們用的是TCP協定,這樣會提供按順序的,可靠,雙向,面向連接配接的比特流.SOCK_DGRAM表明我們用的是UDP協定,這樣隻會提供定長的,不可靠,無連接配接的通信.

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

2.2.      将socket綁定定指定的端口—bind

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

sockfd:是由socket調用傳回的檔案描述符.

addrlen:是sockaddr結構的長度.

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

structsockaddr{

unisgnedshortas_family;

charsa_data[14];

不過由于系統的相容性,我們一般不用這個頭檔案,而使用另外一個結構(structsockaddr_in)來代替.在中有sockaddr_in的定義

structsockaddr_in{

unsignedshortsin_family;

unsignedshortintsin_port;

structin_addrsin_addr;

unsignedcharsin_zero[8];

}

我們主要使用Internet是以sin_family一般為AF_INET,sin_addr設定為INADDR_ANY表示可以和任何的主

機通信,sin_port是我們要監聽的端口号.sin_zero[8]是用來填充的.bind将本地的端口同socket傳回的檔案描述符捆綁在一

起.成功是傳回0,失敗的情況和socket一樣

2.3.      偵聽socket—listen (伺服器端)

int listen(int sockfd,int backlog)

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

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

2.4.      等待接收請求—accept (伺服器端)

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

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

addr,addrlen是用來給用戶端的程式填寫的,伺服器端隻要傳遞指針就可以了.bind,listen和accept是伺服器端用的函

數,accept調用時,伺服器端的程式會一直阻塞到有一個客戶程式發出了連接配接.accept成功時傳回最後的伺服器端的檔案描述符,這個時候伺服器

端可以向該描述符寫資訊了.失敗時傳回-1

2.5.      連接配接到socket—connect

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

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

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

addrlen:serv_addr的長度

connect函數是用戶端用來同服務端連接配接的.成功時傳回0,sockfd是同服務端通訊的檔案描述符失敗時傳回-1.

2.6.      利用socket傳輸資料

ssize_t read(int fd,void *buf,size_t nbyte)

read函數是負責從fd中讀取内容.當讀成功時,read傳回實際所讀的位元組數,

如果傳回的值是0表示已經讀到檔案的結束了,小于0表示出現了錯誤.

如果錯誤為EINTR說明讀是由中斷引起的,

如果是ECONNREST表示網絡連接配接出了問題. 和上面一樣,我們也寫一個自己的讀函數.

ssize_t write(int fd,const void *buf,size_t nbytes)

write函數将buf中的nbytes位元組内容寫入檔案描述符fd.

成功時傳回寫的位元組數.失敗時傳回-1. 并設定errno變量. 在網絡程式中,當我們向套接字檔案描述符寫時有倆種可能.

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

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

如果錯誤為EINTR表示在寫的時候出現了中斷錯誤.

如果為EPIPE表示網絡連接配接出現了問題(對方已經關閉了連接配接).

為了處理以上的情況,我們自己編寫一個寫函數來處理這幾種情況.

和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得到詳細解釋.

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裡面存儲了收資訊方的詳細資料.

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_name是結構的長度.當套接字是面向連接配接時,這兩個值應設為NULL. msg_iov和msg_iovlen指出接受和發送的緩沖區内容.msg_iov是一個結構指針,msg_iovlen指出這個結構數組的大小. msg_control和msg_controllen這兩個變量是用來接收和發送控制資料時的msg_flags指定接受和發送的操作選項.和recv,send的選項一樣

2.7.      套接字的關閉close/shutdown

關閉套接字有兩個函數close和shutdown.用close時和我們關閉檔案一樣.

int close(int sockfd);

int shutdown(int sockfd,int howto);

TCP連接配接是雙向的(是可讀寫的),當我們使用close時,會把讀寫通道都關閉,有時侯我們希望隻關閉一個方向,這個時候我們可以使用shutdown.針對不同的howto,系統回采取不同的關閉方式.

howto=0這個時候系統會關閉讀通道.但是可以繼續往接字描述符寫.

howto=1關閉寫通道,和上面相反,着時候就隻可以讀了.

howto=2關閉讀寫通道,和close一樣 在多程序程式裡面,如果有幾個子程序共享一個套接字時,如果我們使用shutdown, 那麼所有的子程序都不能夠操作了,這個時候我們隻能夠使用close來關閉子程序的套接字描述符.

3.        最常用的伺服器模型.

3.1.      循環伺服器:

循環伺服器在同一個時刻隻可以響應一個用戶端的請求

3.1.1.    循環伺服器之UDP伺服器

UDP循環伺服器的實作非常簡單:UDP伺服器每次從套接字上讀取一個用戶端的請求,處理, 然後将結果傳回給客戶機.

可以用下面的算法來實作.

socket(...);

bind(...);

while(1)

recvfrom(...);

process(...);

sendto(...);

因為UDP是非面向連接配接的,沒有一個用戶端可以老是占住服務端. 隻要處理過程不是死循環, 伺服器對于每一個客戶機的請求總是能夠滿足.

3.1.2.    循環伺服器之TCP伺服器

TCP循環伺服器的實作也不難:TCP伺服器接受一個用戶端的連接配接,然後處理,完成了這個客戶的所有請求後,斷開連接配接.

算法如下:

listen(...);

accept(...);

read(...);

write(...);

close(...);

TCP循環伺服器一次隻能處理一個用戶端的請求.隻有在這個客戶的所有請求都滿足後, 伺服器才可以繼續後面的請求.這樣如果有一個用戶端占住伺服器不放時,其它的客戶機都不能工作了.是以,TCP伺服器一般很少用循環伺服器模型的.

3.2.      并發伺服器

并發伺服器在同一個時刻可以響應多個用戶端的請求

3.2.1.    并發伺服器之TCP伺服器

為了彌補循環TCP伺服器的缺陷,人們又想出了并發伺服器的模型. 并發伺服器的思想是每一個客戶機的請求并不由伺服器直接處理,而是伺服器建立一個 子程序來處理.

if(fork(..)==0)

exit(...);

TCP并發伺服器可以解決TCP循環伺服器客戶機獨占伺服器的情況. 不過也同時帶來了一個不小的問題.為了響應客戶機的請求,伺服器要建立子程序來處理. 而建立子程序是一種非常消耗資源的操作.

3.2.2.    并發伺服器之多路複用I/O

為了解決建立子程序帶來的系統資源消耗,人們又想出了多路複用I/O模型.

首先介紹一個函數select

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *except fds,struct timeval *timeout)

void FD_SET(int fd,fd_set *fdset)

void FD_CLR(int fd,fd_set *fdset)

void FD_ZERO(fd_set *fdset)

int FD_ISSET(int fd,fd_set *fdset) 

一般的來說當我們在向檔案讀寫時,程序有可能在讀寫出阻塞,直到一定的條件滿足. 比如我們從一個套接字讀資料時,可能緩沖區裡面沒有資料可讀(通信的對方還沒有 發送資料過來),這個時候我們的讀調用就會等待(阻塞)直到有資料可讀.如果我們不希望阻塞,我們的一個選擇是用select系統調用. 隻要我們設定好select的各個參數,那麼當檔案可以讀寫的時候select回”通知”我們說可以讀寫了.

readfds所有要讀的檔案檔案描述符的集合

writefds所有要的寫檔案檔案描述符的集合

exceptfds其他的服要向我們通知的檔案描述符

timeout逾時設定.

nfds所有我們監控的檔案描述符中最大的那一個加1

在我們調用select時程序會一直阻塞直到以下的一種情況發生.

1)有檔案可以讀.

2)有檔案可以寫.

3)逾時所設定的時間到.

為了設定檔案描述符我們要使用幾個宏.

FD_SET将fd加入到fdset

FD_CLR将fd從fdset裡面清除

FD_ZERO從fdset中清除所有的檔案描述符

FD_ISSET判斷fd是否在fdset集合中

使用select的一個例子

int use_select(int *readfd,int n)

{

fd_set my_readfd;

int maxfd;

int i;

maxfd=readfd[0];

for(i=1;i<n;i++)

     if(readfd[i]>maxfd) 

           maxfd=readfd[i];

/* 将所有的檔案描述符加入 */

FD_ZERO(&my_readfd);

for(i=0;i FD_SET(readfd[i],*my_readfd);

/* 程序阻塞 */

select(maxfd+1,& my_readfd,NULL,NULL,NULL);

/* 有東西可以讀了 */

for(i=0;i if(FD_ISSET(readfd[i],&my_readfd))

/* 原來是我可以讀了 */

we_read(readfd[i]);

使用select後我們的伺服器程式就變成了.

初始化(socket,bind,listen);

設定監聽讀寫檔案描述符(FD_*);

調用select;

如果是傾聽套接字就緒,說明一個新的連接配接請求建立

建立連接配接(accept);

加入到監聽檔案描述符中去;

否則說明是一個已經連接配接過的描述符

進行操作(read或者write);

多路複用I/O可以解決資源限制的問題.着模型實際上是将UDP循環模型用在了TCP上面. 這也就帶來了一些問題.如由于伺服器依次處理客戶的請求,是以可能會導緻有的客戶 會等待很久.

3.2.3.    并發伺服器之UDP伺服器

人們把并發的概念用于UDP就得到了并發UDP伺服器模型. 并發UDP伺服器模型其實是簡單的.和并發的TCP伺服器模型一樣是建立一個子程序來處理的 算法和并發的TCP模型一樣.

除非伺服器在處理用戶端的請求所用的時間比較長以外,人們實際上很少用這種模型.

一個并發TCP伺服器執行個體

#include “...”

#define MY_PORT 8888

int main(int argc ,char **argv)

int listen_fd,accept_fd;

struct sockaddr_in client_addr;

int n;

if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)

printf(“Socket Error:%s\n\a”,strerror(errno));

exit(1);

bzero(&client_addr,sizeof(struct sockaddr_in));

client_addr.sin_family=AF_INET;

client_addr.sin_port=htons(MY_PORT);

client_addr.sin_addr.s_addr=htonl(INADDR_ANY);

n=1;

/* 如果伺服器終止後,伺服器可以第二次快速啟動而不用等待一段時間 */

setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));

if(bind(listen_fd,(struct sockaddr *)&client_addr,sizeof(client_addr))<0)

printf(“Bind Error:%s\n\a”,strerror(errno));

listen(listen_fd,5);

accept_fd=accept(listen_fd,NULL,NULL);

if((accept_fd<0)&&(errno==EINTR))

continue;

else if(accept_fd<0)

printf(“Accept Error:%s\n\a”,strerror(errno));

if((n=fork())==0)

/* 子程序處理用戶端的連接配接 */

char buffer[1024];

close(listen_fd);

n=read(accept_fd,buffer,1024);

write(accept_fd,buffer,n);

close(accept_fd);

exit(0);

else if(n<0)

printf(“Fork Error:%s\n\a”,strerror(errno));

4.        資料流程

Server Client
1. Establish a listening socket and wait for connections from clients.
2. Create a client socket and attempt to connect to server.
3. Accept the client's connection attempt.
4. Send and receive data.
5. Close the connection.

5.        執行個體分析

總的來說,利用socket進行網絡程式設計并不難,卻有點繁瑣,稍不留心,就會出錯,在C++網絡程式設計卷一中就舉過這樣一個例子

Error example of socket

#include <sys/types.h>

#include <sys/socket.h>

const int PORT_NUM=2007;

const int BUFSIZE=256;

int echo_server()

      struct sockaddr_in addr;

      int addr_len; //error 1 :未初始化addr_len

      char buf[BUFSIZE];

      int n_handle;

       //error 2: s_handle在windows平台上的類型為SOCKET,移植性不好

      int s_handle=socket(PF_UNIX,SOCK_DGRAM,0);

      if(s_handle==-1)      return -1;

      // error 3: 整個addr 結構要先清零 

       // error 4: PF_UNIX應對應 PF_INET

      addr.sin_family=AF_INET;

       // error 5: PORT_NUM應使用網絡位元組順序

      addr.sin_port=PORT_NUM;

      addr.sin_addr.addr=INSDDR_ANY;

      if(bind(s_handle,(struct sockaddr*) &addr,sizeof addr)==-1)

           return -1;

      // error 6: 未調用listen

       // error 7: 未加括号,導緻運算符優先級問題 

       // error 8: accept調用錯誤, 上面的socket調用應用SOCK_STREAM

      if(n_handle=accept(s_handle,(struct sockaddr*)&addr, &addr_len)!=-1)

      {

           int n;

            // error 9: read應該讀取n_handle,而不是s_handle

           while((n=read(s_handle,buf,sizeof(buf))>0)

                 write(n_handle,buf,n);

       // error 9: 沒有檢查write傳回值,有可能造成資料丢失

           close(n_handle);

      }

      return 0;    

所有凡是使用socket程式設計的程式中都想用一些相對簡單的類來封裝這些繁瑣的接口調用

我也曾經做過這樣的嘗試

/*

* Copyright  2005 JinWei Bird Studio All rights reserved

*

* Filename: wf_socket.h

* Description: Test program of socket lib

* Version:1.0

* Create date: 08/19/2005

* Author: Walter Fan, [email protected]

*/

#include "wf_base.h"

#ifndef BACKLOG

#define BACKLOG 50

#endif

#ifndef HOSTLEN

#define HOSTLEN 256

class Socket

protected:   

    int m_nPort;

    int m_nSock;

    int m_nBacklog;

    char* m_szHost;

    bool m_bServer;

    fd_set m_fdSet;

    int m_fdNum;

public:

    Socket(int port);

    Socket(char* host,int port);

    virtual ~Socket();

    virtual int Wait()=0;//encapsulate select and accept

    virtual int Open()=0;//encapsulate socket,listen or connect

    int Close();//encapsulate close socket handle

    int GetSocketID();

    int CloseFD(int fd);//encapsulate close file handle

Socket::Socket(char* host,int port)

:m_szHost(host),m_nPort(port),m_bServer(false),m_fdNum(0)

    m_nSock=-1;

    m_nBacklog=BACKLOG;

    FD_ZERO(&m_fdSet);

    msg_trace("Socket construct as Client...");

Socket::Socket(int port)

:m_szHost("127.0.0.1"),m_nPort(port),m_bServer(true),m_fdNum(0)

    msg_trace("Socket construct as Server...");

Socket::~Socket()

    Close();

    msg_trace("Socket destruct...");

int Socket::Close()//encapsulate close socket handle

    if (m_bServer)

    {

          for (int fd = 0; fd <= m_fdNum; fd++)

          {  

                if (FD_ISSET(fd, &m_fdSet))

                     close(fd);

          }

    }

    else

    {  

          close(m_nSock);

    return 0;

int Socket::GetSocketID()

    return m_nSock;

int Socket::CloseFD(int fd)//encapsulate close file handle

    int retval=0;

    retval=close(fd);

    if(retval<0)

          return retval;

    FD_CLR(fd, &m_fdSet);

    m_fdNum--;

    return retval;

//------------------------TCP --------------------//

class TCPSocket:public Socket

    TCPSocket(int port):Socket(port){};

    TCPSocket(char* host,int port):Socket(host,port){};

    int Wait();

    int Open();

int TCPSocket::Open()

    //int     sock_id;           // the socket

    struct  sockaddr_in   saddr;   // build our address here

    struct  hostent        *hp;   // this is part of our           

    m_nSock = socket(AF_INET, SOCK_STREAM, 0);  // get a socket

    if ( m_nSock == -1 )

        return -1;

    if (m_nSock > m_fdNum)

          m_fdNum = m_nSock;

    //---set socket option---//

    int socket_option_value = 1;

    retval=setsockopt(m_nSock, SOL_SOCKET, SO_REUSEADDR,

                &socket_option_value, sizeof(socket_option_value));

          return -1;

    //---build address and bind it to socket---//

    bzero((char *)&saddr, sizeof(saddr));   // clear out struct    

    gethostname(m_szHost, HOSTLEN);         // where am I ?        

    hp = gethostbyname(m_szHost);           // get info about host 

    if (hp == NULL)

        return -1;                                        // fill in host part   

    bcopy((char *)hp->h_addr, (char *)&saddr.sin_addr, hp->h_length);

    saddr.sin_port = htons(m_nPort);        // fill in socket port 

    saddr.sin_family = AF_INET ;            // fill in addr family 

    if(m_bServer)

        retval=bind(m_nSock, (struct sockaddr *)&saddr, sizeof(saddr));

        if (retval!= 0 )

            return -1;

        //---arrange for incoming calls---//

        retval=listen(m_nSock, m_nBacklog);

        if ( retval!= 0 )

        FD_SET(m_nSock,&m_fdSet);

           retval=connect(m_nSock,(struct sockaddr *)&saddr, sizeof(saddr));

           //msg_trace("connect return "<<retval);

         if (retval!=0)

           return -1;

int TCPSocket::Wait()

        fd_set fd_set_read;

        int fd,clientfd;

        struct sockaddr_un from;

        socklen_t from_len=sizeof(from);

        while(true)

        {

                //msg_trace("select begin...");

                retval=select(m_fdNum+1,&m_fdSet,NULL,NULL,NULL);

                //msg_trace("select return "<<retval);

                if(retval<0)

                     return -1;

                for(fd=0;fd<=m_fdNum;fd++)

                {

                     if(FD_ISSET(fd,&m_fdSet))

                     {

                           if(fd==m_nSock)

                           {

                                clientfd=accept(m_nSock,(struct sockaddr*)&from,&from_len);

                                //msg_trace("accept return "<<clientfd);

                                if(clientfd<0)

                                      return -1;

                                FD_SET(clientfd,&m_fdSet);

                                m_fdNum++;

                                continue;

                           }

                           else

                                return fd;

                     }  

                }

        }

int main(int argc, char *argv[])

    FILE* fp;

    time_t thetime;

    if(fork()==0)//client side

    int sock, ret=0;

    char buf[100];

        TCPSocket oSock("127.0.0.1",1975);

        while((sock =oSock.Open())==-1);

        ret=write(sock,"hi,walter",10);

        if(ret<0) err_quit("write error");

        ret=read(sock,buf,sizeof(buf));

        if(ret<0) err_quit("read error");

        msg_trace("Client get "<<buf);

    else//server side

    int fd, ret=0;

        TCPSocket oSock(1975);

        oSock.Open();

        fd = oSock.Wait();

    if(fd<0)    err_quit("wait failed");

        ret=read(fd,buf,sizeof(buf));

        if(ret<0) err_quit("read failed");

        msg_trace("Server get "<<buf);

        ret=write(fd,"Good bye",10);

        if(ret<0) err_quit("wait failed");

        oSock.CloseFD(fd);

     }

     return 0;

6.        參考連結及文章

http://www.unixprogram.com/socket/socket-faq.html http://www.linuxsir.org/main/?q=node/2 http://tangentsoft.net/wskfaq/ http://www.uwo.ca/its/doc/courses/notes/socket/ http://fanqiang.chinaunix.net/a4/b7/20010508/112359.html

<<Advanced UNIX Programming>>