Linux網路程式設計筆記(修訂版)

maojunxu發表於2018-03-10

我的網路程式設計筆記, 因為最近又要做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

#include <netdb.h>

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傳輸資料

2.6.1.    read和write

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表示網路連線出現了問題(對方已經關閉了連線).

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

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

2.6.3.    recvfrom和sendto

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

2.6.4.    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_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伺服器接受一個客戶端的連線,然後處理,完成了這個客戶的所有請求後,斷開連線.

演算法如下:

socket(…);

bind(…);

listen(…);

while(1)

 {

accept(…);

while(1)

 {

read(…);

process(…);

write(…);

 }

close(…);

 }

 

 

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

 

3.2.      併發伺服器

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

 

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

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

演算法如下:

socket(…);

bind(…);

listen(…);

while(1)

 {

accept(…);

if(fork(..)==0)

 {

while(1)

 {

read(…);

process(…);

write(…);

 }

close(…);

exit(…);

 }

close(…);

 }

 

 

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];

while(1)

{

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

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);

while(1)

 {

設定監聽讀寫檔案描述符(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
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
a”,strerror(errno));

exit(1);

 }

listen(listen_fd,5);

while(1)

 {

accept_fd=accept(listen_fd,NULL,NULL);

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

continue;

else if(accept_fd<0)

 {

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

continue;

 }

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
a”,strerror(errno));

close(accept_fd);

 }

}

 

 

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.

4. Send and receive data.

5. Close the connection.

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_handlewindows平臺上的型別為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 : accept呼叫錯誤, 上面的socket呼叫應用SOCK_STREAM

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

      {

           int n;

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

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

                 write(n_handle,buf,n);

       // error 沒有檢查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, walter.fan@gmail.com

*/

#include “wf_base.h”

 

#ifndef BACKLOG

#define BACKLOG 50

#endif

 

#ifndef HOSTLEN

#define HOSTLEN 256

#endif

 

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)

{

    m_nSock=-1;

    m_nBacklog=BACKLOG;

    FD_ZERO(&m_fdSet);

    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

{

 

public:

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

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

   

    int Wait();

    int Open();

};

 

 

int TCPSocket::Open()

{

    int retval=0;

    //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));

    if(retval<0)

          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 )

            return –1;

        FD_SET(m_nSock,&m_fdSet);

    }

    else

    {

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

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

         if (retval!=0)

           return –1;

    }

    return m_nSock;

}

 

int TCPSocket::Wait()

{

    int retval=0;

    if(m_bServer)

    {

        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;

                     }  

                }

        }

                    

    }

    return retval;

}

 

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;

    char buf[100];

        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>>


相關文章