本文介紹Linux系統網絡編程的內容,如套接口的概念與使用、網絡編程的結構等。
1 什么是套接口
簡單地說,套接口就是一種使用UNIX系統中的文件描述符和系統進程通信的一種方法。
因為在UNIX系統中,所有的I/O操作都是通過讀寫文件描述符而產生的。文件描述符就是
一個和打開的文件相關連的整數。但文件可以是一個網絡連接、一個FIFO、一個管道、一個
終端、一個真正存儲在磁盤上的文件或者UNIX系統中的任何其他的東西。所以,如果你希望
通過Internet和其他的程序進行通信,你只有通過文件描述符。
使用系統調用socket(),你可以得到socket()描述符。然后你可以使用send() 和recv()調用而
與其他的程序通信。你也可以使用一般的文件操作來調用read() 和write()而與其他的程序進行
通信,但send() 和recv()調用可以提供一種更好的數據通信的控制手段。下面我們討論Internet
套接口的使用方法。
2 兩種類型的Internet套接口
有兩種最常用的Internet 套接口,“數據流套接口”和“數據報套接口”,以后我們用
“SOCK_STREAM” 和“SOCK_DGRAM”分別代表上面兩種套接口。數據報套接口有時也
叫做“無連接的套接口”。
數據流套接口是可靠的雙向連接的通信數據流。如果你在套接口中以“ 1, 2”的順序放入
兩個數據,它們在另一端也會以“1, 2”的順序到達。它們也可以被認為是無錯誤的傳輸。
經常使用的telnet應用程序就是使用數據流套接口的一個例子。使用HTTP的WWW瀏覽器
也使用數據流套接口來讀取網頁。事實上,如果你使用telnet 登錄到一個WWW站點的8 0端口,
然后鍵入“GET 網頁名”,你將可以得到這個HTML頁。數據流套接口使用TCP得到這種高質
量的數據傳輸。數據報套接口使用UDP,所以數據報的順序是沒有保障的。數據報是按一種應
答的方式進行數據傳輸的。
3 網絡協議分層
由于網絡中的協議是分層的,所以上層的協議是依賴于下一層所提供的服務的。也就是說,
你可以在不同的物理網絡中使用同樣的套接口程序,因為下層的協議對你來說是透明的。
UNIX系統中的網絡協議是這樣分層的:
• 應用層( telnet、ftp等)。
• 主機到主機傳輸層( TCP、UDP )。
• Internet層( IP和路由)。
• 網絡訪問層(網絡、數據鏈路和物理層)。
4 數據結構
下面我們要討論使用套接口編寫程序可能要用到的數據結構。
首先是套接口描述符。一個套接口描述符只是一個整型的數值: i n t。
第一個數據結構是struct sockaddr,這個數據結構中保存著套接口的地址信息。
struct sockaddr {
unsigned short sa_family; /* address family, AF_xxx */
char sa_data[14]; /* 14 bytes of protocol address */
} ;
sa_family 中可以是其他的很多值,但在這里我們把它賦值為“ A F _ I N E T”。s a _ d a t a包括一
個目的地址和一個端口地址。
你也可以使用另一個數據結構s o c k a d d r _ i n,如下所示:
struct sockaddr_in {
short int sin_family; /* Address family */
unsigned short int sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
unsigned char sin_zero[8]; /* Same size as struct sockaddr */
} ;
這個數據結構使得使用其中的各個元素更為方便。要注意的是sin_zero應該使用bzero() 或
者memset()而設置為全0。另外,一個指向sockaddr_in數據結構的指針可以投射到一個指向數
據結構sockaddr的指針,反之亦然。
5 IP地址和如何使用IP地址
有一系列的程序可以使你處理I P地址。
首先,你可以使用inet_addr()程序把諸如“132.241.5.10”形式的IP地址轉化為無符號的整
型數。
ina.sin_addr.s_addr = inet_addr("132.241.5.10");
如果出錯,inet_addr()程序將返回- 1。
也可以調用inet_ntoa()把地址轉換成數字和句點的形式:
printf ("%s", inet_ntoa (ina.sin_addr));
這將會打印出I P地址。它返回的是一個指向字符串的指針。
5.1 socket()
我們使用系統調用socket()來獲得文件描述符:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
第一個參數domain設置為“AF_INET”。第二個參數是套接口的類型:SOCK_STREAM 或
SOCK_DGRAM。第三個參數設置為0。
系統調用socket()只返回一個套接口描述符,如果出錯,則返回- 1。
5.2 bind()
一旦你有了一個套接口以后,下一步就是把套接口綁定到本地計算機的某一個端口上。但
如果你只想使用connect()則無此必要。
下面是系統調用bind()的使用方法:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
第一個參數sockfd 是由socket()調用返回的套接口文件描述符。第二個參數my_addr 是指向
數據結構sockaddr的指針。數據結構sockaddr中包括了關于你的地址、端口和I P地址的信息。
第三個參數addrlen可以設置成sizeof(struct sockaddr)。
下面是一個例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MYPORT 3490
m a i n ( )
{
int sockfd;
struct sockaddr_in my_addr;
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
m y _ a d d r.sin_family = AF_INET; /* host byte order */
m y _ a d d r.sin_port = htons(MYPORT); /* short, network byte order */
m y _ a d d r. s i n _ a d d r.s_addr = inet_addr("132.241.5.10");
b z e r o ( & ( m y _ a d d r.sin_zero), 8); /* zero the rest of the struct */
/* don't forget your error checking for bind(): */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
如果出錯,bind() 也返回- 1。
如果你使用connect()系統調用,那么你不必知道你使用的端口號。當你調用connect()時,
它檢查套接口是否已經綁定,如果沒有,它將會分配一個空閑的端口。
5.3 connect()
系統調用connect()的用法如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
第一個參數還是套接口文件描述符,它是由系統調用socket()返回的。第二個參數是
serv_addr是指向數據結構sockaddr的指針,其中包括目的端口和I P地址。第三個參數可以使用
sizeof(struct sockaddr)而獲得。下面是一個例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define DEST_IP "132.241.5.10"
#define DEST_PORT 23
main()
{
int sockfd;
struct sockaddr_in dest_addr; /* will hold the destination addr */
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
d e s t _ a d d r.sin_family = AF_INET; /* host byte order */
d e s t _ a d d r.sin_port = htons(DEST_PORT); /* short, network byte order */
d e s t _ a d d r. s i n _ a d d r.s_addr = inet_addr(DEST_IP);
b z e r o ( & ( d e s t _ a d d r.sin_zero), 8); /* zero the rest of the struct */
/* don't forget to error check the connect()! */
connect(sockfd, (struct sockaddr *)&dest_addr, sizeof(struct sockaddr));
同樣,如果出錯, c o n n e c t ( )將會返回- 1。
5.4 listen()
如果你希望不連接到遠程的主機,也就是說你希望等待一個進入的連接請求,然后再處理
它們。這樣,你通過首先調用l i s t e n ( ),然后再調用a c c e p t ( )來實現。
系統調用l i s t e n ( )的形式如下:
int listen(int sockfd, int backlog);
第一個參數是系統調用s o c k e t ( )返回的套接口文件描述符。第二個參數是進入隊列中允許
的連接的個數。進入的連接請求在使用系統調用a c c e p t ( )應答之前要在進入隊列中等待。這個
值是隊列中最多可以擁有的請求的個數。大多數系統的缺省設置為2 0。你可以設置為5或者1 0。
當出錯時,l i s t e n ( )將會返回- 1值。
當然,在使用系統調用l i s t e n ( )之前,我們需要調用b i n d ( )綁定到需要的端口,否則系統內
核將會讓我們監聽一個隨機的端口。所以,如果你希望監聽一個端口,下面是應該使用的系統
調用的順序:
s o c k e t ( ) ;
b i n d ( ) ;
l i s t e n ( ) ;
/* accept() goes here */
5.5 accept()
系統調用a c c e p t ( )比較起來有點復雜。在遠程的主機可能試圖使用c o n n e c t ( )連接你使用
l i s t e n ( )正在監聽的端口。但此連接將會在隊列中等待,直到使用a c c e p t ( )處理它。調用a c c e p t ( )
之后,將會返回一個全新的套接口文件描述符來處理這個單個的連接。這樣,對于同一個連接
來說,你就有了兩個文件描述符。原先的一個文件描述符正在監聽你指定的端口,新的文件描
述符可以用來調用s e n d ( )和r e c v ( )。
調用的例子如下:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
第一個參數是正在監聽端口的套接口文件描述符。第二個參數a d d r是指向本地的數據結構
s o c k a d d r _ i n的指針。調用c o n n e c t ( )中的信息將存儲在這里。通過它你可以了解哪個主機在哪個
端口呼叫你。第三個參數同樣可以使用sizeof(struct sockaddr_in)來獲得。
如果出錯,a c c e p t ( )也將返回- 1。下面是一個簡單的例子:
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#define MYPORT 3490 /* the port users will be connecting to */
#define BACKLOG 10 /* how many pending connections queue will hold */
m a i n ( )
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int sin_size;
sockfd = socket(AF_INET, SOCK_STREAM, 0); /* do some error checking! */
m y _ a d d r.sin_family = AF_INET; /* host byte order */
m y _ a d d r.sin_port = htons(MYPORT); /* short, network byte order */
m y _ a d d r. s i n _ a d d r.s_addr = INADDR_ANY; /* auto-fill with my IP */
b z e r o ( & ( m y _ a d d r.sin_zero), 8); /* zero the rest of the struct */
/* don't forget your error checking for these calls: */
bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr));
listen(sockfd, BACKLOG);
sin_size = sizeof(struct sockaddr_in);
new_fd = accept(sockfd, &their_addr, &sin_size);
下面,我們將可以使用新創建的套接口文件描述符n e w _ f d來調用s e n d ( )和r e c v ( )。
5.6 send() 和recv()
系統調用s e n d ( )的用法如下:
int send(int sockfd, const void *msg, int len, int flags);
第一個參數是你希望給發送數據的套接口文件描述符。它可以是你通過s o c k e t ( )系統調用
返回的,也可以是通過a c c e p t ( )系統調用得到的。第二個參數是指向你希望發送的數據的指針。
第三個參數是數據的字節長度。第四個參數標志設置為0。
下面是一個簡單的例子:
char *msg = "Beej was here!";
int len, bytes_sent;
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
系統調用s e n d ( )返回實際發送的字節數,這可能比你實際想要發送的字節數少。如果返回
的字節數比要發送的字節數少,你在以后必須發送剩下的數據。當s e n d ( )出錯時,將返回- 1。
系統調用r e c v ( )的使用方法和s e n d ( )類似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
第一個參數是要讀取的套接口文件描述符。第二個參數是保存讀入信息的地址。第三個參
數是緩沖區的最大長度。第四個參數設置為0。
系統調用r e c v ( )返回實際讀取到緩沖區的字節數,如果出錯則返回- 1。
這樣使用上面的系統調用,你可以通過數據流套接口來發送和接受信息。
5.7 sendto() 和recvfrom()
因為數據報套接口并不連接到遠程的主機上,所以在發送數據包之前,我們必須首先給出
目的地址,請看:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
除了兩個參數以外,其他的參數和系統調用s e n d ( )時相同。參數t o是指向包含目的I P地址
和端口號的數據結構s o c k a d d r的指針。參數t o l e n可以設置為sizeof(struct sockaddr)。
系統調用s e n d t o ( )返回實際發送的字節數,如果出錯則返回- 1。
系統調用r e c v f r o m ( )的使用方法也和r e c v ( )的十分近似:
int recvfrom(int sockfd, void *buf, int len, unsigned int flags
struct sockaddr *from, int *fromlen);
參數f r o m是指向本地計算機中包含源I P地址和端口號的數據結構s o c k a d d r的指針。參數
f r o m l e n設置為sizeof(struct sockaddr)。
系統調用r e c v f r o m ( )返回接收到的字節數,如果出錯則返回- 1。
5.8 close() 和shutdown()
你可以使用c l o s e ( )調用關閉連接的套接口文件描述符:
c l o s e ( s o c k f d ) ;
這樣就不能再對此套接口做任何的讀寫操作了。
使用系統調用s h u t d o w n ( ),可有更多的控制權。它允許你在某一個方向切斷通信,或者切
斷雙方的通信:
int shutdown(int sockfd, int how);
第一個參數是你希望切斷通信的套接口文件描述符。第二個參數h o w值如下:
0—Further receives are disallowed
1—Further sends are disallowed
2—Further sends and receives are disallowed (like close())
shutdown() 如果成功則返回0,如果失敗則返回- 1。
5.9 getpeername()
這個系統的調用十分簡單。它將告訴你是誰在連接的另一端:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
第一個參數是連接的數據流套接口文件描述符。第二個參數是指向包含另一端的信息的數
據結構s o c k a d d r的指針。第三個參數可以設置為sizeof(struct sockaddr)。
如果出錯,系統調用將返回- 1。
一旦你獲得了它們的地址,你可以使用inet_ntoa() 或者g e t h o s t b y a d d r ( )來得到更多的信息。
5.10 gethostname()
系統調用g e t h o s t n a m e ( )比系統調用g e t p e e r n a m e ( )還簡單。它返回程序正在運行的計算機的
名字。系統調用g e t h o s t b y n a m e ( )可以使用這個名字來決定你的機器的I P地址。
下面是一個例子:
#include <unistd.h>
int gethostname(char *hostname, size_t size);
如果成功,g e t h o s t n a m e將返回0。如果失敗,它將返回- 1。
6 DNS
DNS 代表“Domain Name Service”,即域名服務器。它可以把域名翻譯成相應的I P地址。
你可以使用此I P地址調用b i n d ( )、c o n n e c t ( )、s e n d t o ( )或者用于其他的地方。
系統調用g e t h o s t b y n a m e ( )可以完成這個函數:
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
它返回一個指向數據結構h o s t e n t的指針,數據結構h o s t e n t如下:
struct hostent {
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
} ;
#define h_addr h_addr_list[0]
h_name —主機的正式名稱。
h_aliases —主機的別名。
h _ a d d r t y p e—將要返回的地址的類型,一般是A F _ I N E T。
h _ l e n g t h—地址的字節長度。
h _ a d d r _ l i s t—主機的網絡地址。
h_addr —h _ a d d r _ l i s t中的第一個地址。
系統調用g e t h o s t b y n a m e ( )返回一個指向填充好的數據結構h o s t e n t的指針。當發生錯誤時,
則返回一個N U L L指針。下面是一個實際例子:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
int main(int argc, char *argv[])
{
struct hostent *h;
if (argc != 2) { /* error check the command line */
f p r i n t f ( s t d e r r,"usage: getip address\n");
e x i t ( 1 ) ;
}
if ((h=gethostbyname(argv[1])) == NULL) { /* get the host info */
h e r r o r ( " g e t h o s t b y n a m e " ) ;
e x i t ( 1 ) ;
}
printf("Host name : %s\n", h->h_name);
printf("IP Address : %s\n",inet_ntoa(*((struct in_addr *)h->h_addr)));
return 0;
}
在使用g e t h o s t b y n a m e ( )時,你不能使用p e r r o r ( )來打印錯誤信息。你應該使用的是系統調用
h e r r o r ( )。
7 客戶機/服務器模式
在網絡上大部分的通信都是在客戶機/服務器模式下進行的。例如t e l n e t。當你使用t e l n e t連
接到遠程主機的端口2 3時,主機上的一個叫做t e l n e t d的程序就開始運行。它處理所有進入的
t e l n e t連接,為你設置登錄提示符等。
應當注意的是客戶機/服務器模式可以使用S O C K _ S T R E A M、S O C K _ D G R A M或者任何其
他的方式。例如t e l n e t / t e l n e t d、f t p / f t p d和b o o t p / b o o t p d。每當你使用f t p時,遠程計算機都在運
行一個f t p d為你服務。
一般情況下,一臺機器上只有一個服務器程序,它通過使用f o r k ( )來處理多個客戶端程序
的請求。最基本的處理方法是:服務器等待連接,使用a c c e p t ( )接受連接,調用f o r k ( )生成一個
子進程處理連接。
8 簡單的數據流服務器程序
此服務器程序所作的事情就是通過一個數據流連接發送字符串“ Hello, Wo r l d ! \ n”。你可以
在一個窗口上運行此程序,然后在另一個窗口使用t e l n e t:
$ telnet remotehostname 3490
其中,r e m o t e h o s t n a m e是你運行的機器名。下面是此程序的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 3490 /* the port users will be connecting to */
#define BACKLOG 10 /* how many pending connections queue will hold */
m a i n ( )
{
int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */
struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int sin_size;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
p e r r o r ( " s o c k e t " ) ;
e x i t ( 1 ) ;
}
m y _ a d d r.sin_family = AF_INET; /* host byte order */
m y _ a d d r.sin_port = htons(MYPORT); /* short, network byte order */
m y _ a d d r. s i n _ a d d r.s_addr = INADDR_ANY; /* auto-fill with my IP */
b z e r o ( & ( m y _ a d d r.sin_zero), 8); /* zero the rest of the struct */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \
== -1) {
p e r r o r ( " b i n d " ) ;
e x i t ( 1 ) ;
}
if (listen(sockfd, BACKLOG) == -1) {
p e r r o r ( " l i s t e n " ) ;
e x i t ( 1 ) ;
}
while(1) { /* main accept() loop */
sin_size = sizeof(struct sockaddr_in);
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, \
&sin_size)) == -1) {
p e r r o r ( " a c c e p t " ) ;
c o n t i n u e ;
}
printf("server: got connection from %s\n", \
i n e t _ n t o a ( t h e i r _ a d d r. s i n _ a d d r ) ) ;
if (!fork()) { /* this is the child process */
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); /* parent doesn't need this */
while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
}
}
你也可以使用下面的客戶機程序從服務器上得到字符串。
9 簡單的數據流客戶機程序
客戶機所做的是連接到你在命令行中指定的主機的3 4 9 0端口。它讀取服務器發送的字符
串。
下面是客戶機程序的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define PORT 3490 /* the port client will be connecting to */
#define MAXDATASIZE 100 /* max number of bytes we can get at once */
int main(int argc, char *argv[])
{
int sockfd, numbytes;
char buf[MAXDATA S I Z E ] ;
struct hostent *he;
struct sockaddr_in their_addr; /* connector's address information */
if (argc != 2) {
f p r i n t f ( s t d e r r,"usage: client hostname\n");
e x i t ( 1 ) ;
}
if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
h e r r o r ( " g e t h o s t b y n a m e " ) ;
e x i t ( 1 ) ;
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
p e r r o r ( " s o c k e t " ) ;
e x i t ( 1 ) ;
}
t h e i r _ a d d r.sin_family = AF_INET; /* host byte order */
t h e i r _ a d d r.sin_port = htons(PORT); /* short, network byte order */
t h e i r _ a d d r.sin_addr = *((struct in_addr *)he->h_addr);
b z e r o ( & ( t h e i r _ a d d r.sin_zero), 8); /* zero the rest of the struct */
if (connect(sockfd, (struct sockaddr *)&their_addr, \
sizeof(struct sockaddr)) == -1) {
p e r r o r ( " c o n n e c t " ) ;
e x i t ( 1 ) ;
}
if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {
p e r r o r ( " r e c v " ) ;
e x i t ( 1 ) ;
}
buf[numbytes] = '\0';
printf("Received: %s",buf);
c l o s e ( s o c k f d ) ;
return 0;
}
如果你在運行服務器程序之前運行客戶機程序,則將會得到一個“ Connection refused”的
信息。
10 數據報套接口
程序l i s t e n e r在機器中等待端口4 9 5 0到來的數據包。程序t a l k e r向指定的機器的4 9 5 0端口發
送數據包。
下面是l i s t e n e r. c的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 4950 /* the port users will be sending to */
#define MAXBUFLEN 100
m a i n ( )
{
int sockfd;
struct sockaddr_in my_addr; /* my address information */
struct sockaddr_in their_addr; /* connector's address information */
int addr_len, numbytes;
char buf[MAXBUFLEN];
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket");
exit(1);
}
my_addr.sin_family = AF_INET; /* host byte order */
my_addr.sin_port = htons(MYPORT); /* short, network byte order */
my_a d d r.s i n_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero), 8); /* zero the rest of the struct */
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) \
== -1) {
perror("bind ") ;
exit(1);
}
addr_len = sizeof(struct sockaddr);
if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0, \
(struct sockaddr *)&their_addr, &addr_len)) == -1) {
perror( "recvfrom" );
exit(1) ;
}
printf("got packet from %s\n",inet_ntoa(their_addr. s i n _ a d d r ) ) ;
printf("packet is %d bytes long\n",numbytes);
buf[numbytes] = '\0';
printf("packet contains \"%s\"\n",buf);
close(sockfd);
}
下面是t a l k e r. c的代碼:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define MYPORT 4950 /* the port users will be sending to */
int main(int argc, char *argv[])
{
int sockfd;
struct sockaddr_in their_addr; /* connector's address information */
struct hostent *he;
int numbytes;
if (argc != 3) {
fprintf (stderr, "usage: talker hostname message\n");
exit(1);
}
if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */
h e r r o r ( " g e t h o s t b y n a m e " ) ;
e x i t ( 1 ) ;
}
if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {
perror("socket") ;
exit(1);
}
their_addr.sin_family = AF_INET; /* host byte order */
their_addr.sin_port = htons(MYPORT); /* short, network byte order */
their_addr.sin_addr = *((struct in_addr *)he->h_addr);
bzero(&(their_addr.sin_zero), 8); /* zero the rest of the struct */
if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \
(struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {
perror ("s e n d t o");
exit(1);
}
printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr. s i n _ a d d r ) ) ;
close(sockfd);
return 0;
}
你可以在一臺機器上運行l i s t e n e r程序,在另一臺機器上運行t a l k e r程序,然后觀察它們之
間的通信。
21.11 阻塞
當使用上面的listener程序時,此程序在等待直到一個數據包到來。這是因為它調用了
recvform(),如果沒有數據,recvform ( )就一直阻塞,直到有數據到來。
很多函數都有阻塞。系統調用accept ( )阻塞,所有的類似recv*()的函數也可以阻塞。它們
之所以可以阻塞是因為系統內核允許它們阻塞。當你第一次創建一個套接口文件描述符時,系
統內核將它設置為可以阻塞。如果你不希望套接口阻塞,你可以使用系統調用fcntl():
#include <unistd.h>
#include <fcntl.h>
.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
如果你設置為不阻塞,那么就得頻繁地詢問套接口以便檢查有無信息到來。如果你試圖讀
取一個沒有阻塞的套接口,同時它又沒有數據,那么你將得到- 1。
詢問套接口以檢查有無信息得到來可能會占用太多的C P U時間。另一個可以使用的方法是
select()。
select()用于同步I / O多路復用。這個系統調用十分有用。考慮一下下面的情況:你是一個
服務器,你希望監聽進入的連接,同時還一直從已有的連接中讀取信息。
也許你認為可以使用一個accept()調用和幾個recv()調用。但如果調用accept()阻塞了怎么
辦?如果在這時你希望調用recv()接受數據呢?
系統調用select()使得你可以同時監視幾個套接口。它可以告訴你哪一個套接口已經準備好
了以供讀取,哪一個套接口已經可以寫入。
下面是select()的用法:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int numfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
此函數監視幾個文件描述符,特別是r e a d f d s、w r i t e f d s和e x c e p t f d s。如果你希望檢查是否
可以從標準輸入中和一些其他的套接口文件描述符s o c k f d中讀取數據,只需把文件描述符0和
s o c k f d添加到r e a d f d s中。參數n u m f d s應該設置為最高的文件描述符的值加1。
當s e l e c t ( )返回時,r e a d f d s將會被修改以便反映你選擇的那一個文件描述符已經準備好了以
供讀取。你可以使用F D _ I S S E T ( )測試。
FD_ZERO(fd_set *set)—清除文件描述符集。
FD_SET(int fd, fd_set *set)—把fd 添加到文件描述符集中。
FD_CLR(int fd, fd_set *set)—把fd 從文件描述符中移走。
FD_ISSET(int fd, fd_set *set)—檢測fd 是否在文件描述符集中。
數據結構t i m e v a l包含下面的字段:
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
} ;
把t v _ s e c設置成需要等待的時間秒數,t v _ u s e c設置成需要等待的微秒數。一秒中包括1 000
0 0 0 μ s 。下面的程序等待2 . 5 s:
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define STDIN 0 /* file descriptor for standard input */
m a i n ( )
{
struct timeval tv;
fd_set readfds;
t v.tv_sec = 2;
t v.tv_usec = 500000;
F D _ Z E R O ( & r e a d f d s ) ;
FD_SET(STDIN, &readfds);
/* don't care about writefds and exceptfds: */
select(STDIN+1, &readfds, NULL, NULL, &tv);
if (FD_ISSET(STDIN, &readfds))
printf("A key was pressed!\n");
else
printf( " Timed out.\n");
摘自:
Linux系統高級編程-china pub
posted on 2009-11-15 21:40
chatler 閱讀(728)
評論(0) 編輯 收藏 引用 所屬分類:
Socket