我的網絡編程筆記, 因爲最近又要做Linux下的網絡編程,故重新修訂, 其中一些內容參考了文末的鏈接及文章
http://www.cnblogs.com/linshui91/archive/2010/09/28/1837796.html
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/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) 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\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)); 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\n\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\n\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_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 #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>>