本文介紹Linux系統(tǒng)網(wǎng)絡(luò)編程的內(nèi)容,如套接口的概念與使用、網(wǎng)絡(luò)編程的結(jié)構(gòu)等。
1 什么是套接口
簡(jiǎn)單地說,套接口就是一種使用UNIX系統(tǒng)中的文件描述符和系統(tǒng)進(jìn)程通信的一種方法。
因?yàn)樵赨NIX系統(tǒng)中,所有的I/O操作都是通過讀寫文件描述符而產(chǎn)生的。文件描述符就是
一個(gè)和打開的文件相關(guān)連的整數(shù)。但文件可以是一個(gè)網(wǎng)絡(luò)連接、一個(gè)FIFO、一個(gè)管道、一個(gè)
終端、一個(gè)真正存儲(chǔ)在磁盤上的文件或者UNIX系統(tǒng)中的任何其他的東西。所以,如果你希望
通過Internet和其他的程序進(jìn)行通信,你只有通過文件描述符。
使用系統(tǒng)調(diào)用socket(),你可以得到socket()描述符。然后你可以使用send() 和recv()調(diào)用而
與其他的程序通信。你也可以使用一般的文件操作來調(diào)用read() 和write()而與其他的程序進(jìn)行
通信,但send() 和recv()調(diào)用可以提供一種更好的數(shù)據(jù)通信的控制手段。下面我們討論Internet
套接口的使用方法。
2 兩種類型的Internet套接口
有兩種最常用的Internet 套接口,“數(shù)據(jù)流套接口”和“數(shù)據(jù)報(bào)套接口”,以后我們用
“SOCK_STREAM” 和“SOCK_DGRAM”分別代表上面兩種套接口。數(shù)據(jù)報(bào)套接口有時(shí)也
叫做“無連接的套接口”。
數(shù)據(jù)流套接口是可靠的雙向連接的通信數(shù)據(jù)流。如果你在套接口中以“ 1, 2”的順序放入
兩個(gè)數(shù)據(jù),它們?cè)诹硪欢艘矔?huì)以“1, 2”的順序到達(dá)。它們也可以被認(rèn)為是無錯(cuò)誤的傳輸。
經(jīng)常使用的telnet應(yīng)用程序就是使用數(shù)據(jù)流套接口的一個(gè)例子。使用HTTP的WWW瀏覽器
也使用數(shù)據(jù)流套接口來讀取網(wǎng)頁。事實(shí)上,如果你使用telnet 登錄到一個(gè)WWW站點(diǎn)的8 0端口,
然后鍵入“GET 網(wǎng)頁名”,你將可以得到這個(gè)HTML頁。數(shù)據(jù)流套接口使用TCP得到這種高質(zhì)
量的數(shù)據(jù)傳輸。數(shù)據(jù)報(bào)套接口使用UDP,所以數(shù)據(jù)報(bào)的順序是沒有保障的。數(shù)據(jù)報(bào)是按一種應(yīng)
答的方式進(jìn)行數(shù)據(jù)傳輸?shù)摹?br>3 網(wǎng)絡(luò)協(xié)議分層
由于網(wǎng)絡(luò)中的協(xié)議是分層的,所以上層的協(xié)議是依賴于下一層所提供的服務(wù)的。也就是說,
你可以在不同的物理網(wǎng)絡(luò)中使用同樣的套接口程序,因?yàn)橄聦拥膮f(xié)議對(duì)你來說是透明的。
UNIX系統(tǒng)中的網(wǎng)絡(luò)協(xié)議是這樣分層的:
• 應(yīng)用層( telnet、ftp等)。
• 主機(jī)到主機(jī)傳輸層( TCP、UDP )。
• Internet層( IP和路由)。
• 網(wǎng)絡(luò)訪問層(網(wǎng)絡(luò)、數(shù)據(jù)鏈路和物理層)。
4 數(shù)據(jù)結(jié)構(gòu)
下面我們要討論使用套接口編寫程序可能要用到的數(shù)據(jù)結(jié)構(gòu)。
首先是套接口描述符。一個(gè)套接口描述符只是一個(gè)整型的數(shù)值: i n t。
第一個(gè)數(shù)據(jù)結(jié)構(gòu)是struct sockaddr,這個(gè)數(shù)據(jù)結(jié)構(gòu)中保存著套接口的地址信息。
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包括一
個(gè)目的地址和一個(gè)端口地址。
你也可以使用另一個(gè)數(shù)據(jù)結(jié)構(gòu)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 */
} ;
這個(gè)數(shù)據(jù)結(jié)構(gòu)使得使用其中的各個(gè)元素更為方便。要注意的是sin_zero應(yīng)該使用bzero() 或
者memset()而設(shè)置為全0。另外,一個(gè)指向sockaddr_in數(shù)據(jù)結(jié)構(gòu)的指針可以投射到一個(gè)指向數(shù)
據(jù)結(jié)構(gòu)sockaddr的指針,反之亦然。
5 IP地址和如何使用IP地址
有一系列的程序可以使你處理I P地址。
首先,你可以使用inet_addr()程序把諸如“132.241.5.10”形式的IP地址轉(zhuǎn)化為無符號(hào)的整
型數(shù)。
ina.sin_addr.s_addr = inet_addr("132.241.5.10");
如果出錯(cuò),inet_addr()程序?qū)⒎祷? 1。
也可以調(diào)用inet_ntoa()把地址轉(zhuǎn)換成數(shù)字和句點(diǎn)的形式:
printf ("%s", inet_ntoa (ina.sin_addr));
這將會(huì)打印出I P地址。它返回的是一個(gè)指向字符串的指針。
5.1 socket()
我們使用系統(tǒng)調(diào)用socket()來獲得文件描述符:
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
第一個(gè)參數(shù)domain設(shè)置為“AF_INET”。第二個(gè)參數(shù)是套接口的類型:SOCK_STREAM 或
SOCK_DGRAM。第三個(gè)參數(shù)設(shè)置為0。
系統(tǒng)調(diào)用socket()只返回一個(gè)套接口描述符,如果出錯(cuò),則返回- 1。
5.2 bind()
一旦你有了一個(gè)套接口以后,下一步就是把套接口綁定到本地計(jì)算機(jī)的某一個(gè)端口上。但
如果你只想使用connect()則無此必要。
下面是系統(tǒng)調(diào)用bind()的使用方法:
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
第一個(gè)參數(shù)sockfd 是由socket()調(diào)用返回的套接口文件描述符。第二個(gè)參數(shù)my_addr 是指向
數(shù)據(jù)結(jié)構(gòu)sockaddr的指針。數(shù)據(jù)結(jié)構(gòu)sockaddr中包括了關(guān)于你的地址、端口和I P地址的信息。
第三個(gè)參數(shù)addrlen可以設(shè)置成sizeof(struct sockaddr)。
下面是一個(gè)例子:
#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));
如果出錯(cuò),bind() 也返回- 1。
如果你使用connect()系統(tǒng)調(diào)用,那么你不必知道你使用的端口號(hào)。當(dāng)你調(diào)用connect()時(shí),
它檢查套接口是否已經(jīng)綁定,如果沒有,它將會(huì)分配一個(gè)空閑的端口。
5.3 connect()
系統(tǒng)調(diào)用connect()的用法如下:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
第一個(gè)參數(shù)還是套接口文件描述符,它是由系統(tǒng)調(diào)用socket()返回的。第二個(gè)參數(shù)是
serv_addr是指向數(shù)據(jù)結(jié)構(gòu)sockaddr的指針,其中包括目的端口和I P地址。第三個(gè)參數(shù)可以使用
sizeof(struct sockaddr)而獲得。下面是一個(gè)例子:
#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));
同樣,如果出錯(cuò), c o n n e c t ( )將會(huì)返回- 1。
5.4 listen()
如果你希望不連接到遠(yuǎn)程的主機(jī),也就是說你希望等待一個(gè)進(jìn)入的連接請(qǐng)求,然后再處理
它們。這樣,你通過首先調(diào)用l i s t e n ( ),然后再調(diào)用a c c e p t ( )來實(shí)現(xiàn)。
系統(tǒng)調(diào)用l i s t e n ( )的形式如下:
int listen(int sockfd, int backlog);
第一個(gè)參數(shù)是系統(tǒng)調(diào)用s o c k e t ( )返回的套接口文件描述符。第二個(gè)參數(shù)是進(jìn)入隊(duì)列中允許
的連接的個(gè)數(shù)。進(jìn)入的連接請(qǐng)求在使用系統(tǒng)調(diào)用a c c e p t ( )應(yīng)答之前要在進(jìn)入隊(duì)列中等待。這個(gè)
值是隊(duì)列中最多可以擁有的請(qǐng)求的個(gè)數(shù)。大多數(shù)系統(tǒng)的缺省設(shè)置為2 0。你可以設(shè)置為5或者1 0。
當(dāng)出錯(cuò)時(shí),l i s t e n ( )將會(huì)返回- 1值。
當(dāng)然,在使用系統(tǒng)調(diào)用l i s t e n ( )之前,我們需要調(diào)用b i n d ( )綁定到需要的端口,否則系統(tǒng)內(nèi)
核將會(huì)讓我們監(jiān)聽一個(gè)隨機(jī)的端口。所以,如果你希望監(jiān)聽一個(gè)端口,下面是應(yīng)該使用的系統(tǒng)
調(diào)用的順序:
s o c k e t ( ) ;
b i n d ( ) ;
l i s t e n ( ) ;
/* accept() goes here */
5.5 accept()
系統(tǒng)調(diào)用a c c e p t ( )比較起來有點(diǎn)復(fù)雜。在遠(yuǎn)程的主機(jī)可能試圖使用c o n n e c t ( )連接你使用
l i s t e n ( )正在監(jiān)聽的端口。但此連接將會(huì)在隊(duì)列中等待,直到使用a c c e p t ( )處理它。調(diào)用a c c e p t ( )
之后,將會(huì)返回一個(gè)全新的套接口文件描述符來處理這個(gè)單個(gè)的連接。這樣,對(duì)于同一個(gè)連接
來說,你就有了兩個(gè)文件描述符。原先的一個(gè)文件描述符正在監(jiān)聽你指定的端口,新的文件描
述符可以用來調(diào)用s e n d ( )和r e c v ( )。
調(diào)用的例子如下:
#include <sys/socket.h>
int accept(int sockfd, void *addr, int *addrlen);
第一個(gè)參數(shù)是正在監(jiān)聽端口的套接口文件描述符。第二個(gè)參數(shù)a d d r是指向本地的數(shù)據(jù)結(jié)構(gòu)
s o c k a d d r _ i n的指針。調(diào)用c o n n e c t ( )中的信息將存儲(chǔ)在這里。通過它你可以了解哪個(gè)主機(jī)在哪個(gè)
端口呼叫你。第三個(gè)參數(shù)同樣可以使用sizeof(struct sockaddr_in)來獲得。
如果出錯(cuò),a c c e p t ( )也將返回- 1。下面是一個(gè)簡(jiǎn)單的例子:
#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);
下面,我們將可以使用新創(chuàng)建的套接口文件描述符n e w _ f d來調(diào)用s e n d ( )和r e c v ( )。
5.6 send() 和recv()
系統(tǒng)調(diào)用s e n d ( )的用法如下:
int send(int sockfd, const void *msg, int len, int flags);
第一個(gè)參數(shù)是你希望給發(fā)送數(shù)據(jù)的套接口文件描述符。它可以是你通過s o c k e t ( )系統(tǒng)調(diào)用
返回的,也可以是通過a c c e p t ( )系統(tǒng)調(diào)用得到的。第二個(gè)參數(shù)是指向你希望發(fā)送的數(shù)據(jù)的指針。
第三個(gè)參數(shù)是數(shù)據(jù)的字節(jié)長(zhǎng)度。第四個(gè)參數(shù)標(biāo)志設(shè)置為0。
下面是一個(gè)簡(jiǎn)單的例子:
char *msg = "Beej was here!";
int len, bytes_sent;
len = strlen(msg);
bytes_sent = send(sockfd, msg, len, 0);
系統(tǒng)調(diào)用s e n d ( )返回實(shí)際發(fā)送的字節(jié)數(shù),這可能比你實(shí)際想要發(fā)送的字節(jié)數(shù)少。如果返回
的字節(jié)數(shù)比要發(fā)送的字節(jié)數(shù)少,你在以后必須發(fā)送剩下的數(shù)據(jù)。當(dāng)s e n d ( )出錯(cuò)時(shí),將返回- 1。
系統(tǒng)調(diào)用r e c v ( )的使用方法和s e n d ( )類似:
int recv(int sockfd, void *buf, int len, unsigned int flags);
第一個(gè)參數(shù)是要讀取的套接口文件描述符。第二個(gè)參數(shù)是保存讀入信息的地址。第三個(gè)參
數(shù)是緩沖區(qū)的最大長(zhǎng)度。第四個(gè)參數(shù)設(shè)置為0。
系統(tǒng)調(diào)用r e c v ( )返回實(shí)際讀取到緩沖區(qū)的字節(jié)數(shù),如果出錯(cuò)則返回- 1。
這樣使用上面的系統(tǒng)調(diào)用,你可以通過數(shù)據(jù)流套接口來發(fā)送和接受信息。
5.7 sendto() 和recvfrom()
因?yàn)閿?shù)據(jù)報(bào)套接口并不連接到遠(yuǎn)程的主機(jī)上,所以在發(fā)送數(shù)據(jù)包之前,我們必須首先給出
目的地址,請(qǐng)看:
int sendto(int sockfd, const void *msg, int len, unsigned int flags,
const struct sockaddr *to, int tolen);
除了兩個(gè)參數(shù)以外,其他的參數(shù)和系統(tǒng)調(diào)用s e n d ( )時(shí)相同。參數(shù)t o是指向包含目的I P地址
和端口號(hào)的數(shù)據(jù)結(jié)構(gòu)s o c k a d d r的指針。參數(shù)t o l e n可以設(shè)置為sizeof(struct sockaddr)。
系統(tǒng)調(diào)用s e n d t o ( )返回實(shí)際發(fā)送的字節(jié)數(shù),如果出錯(cuò)則返回- 1。
系統(tǒng)調(diào)用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);
參數(shù)f r o m是指向本地計(jì)算機(jī)中包含源I P地址和端口號(hào)的數(shù)據(jù)結(jié)構(gòu)s o c k a d d r的指針。參數(shù)
f r o m l e n設(shè)置為sizeof(struct sockaddr)。
系統(tǒng)調(diào)用r e c v f r o m ( )返回接收到的字節(jié)數(shù),如果出錯(cuò)則返回- 1。
5.8 close() 和shutdown()
你可以使用c l o s e ( )調(diào)用關(guān)閉連接的套接口文件描述符:
c l o s e ( s o c k f d ) ;
這樣就不能再對(duì)此套接口做任何的讀寫操作了。
使用系統(tǒng)調(diào)用s h u t d o w n ( ),可有更多的控制權(quán)。它允許你在某一個(gè)方向切斷通信,或者切
斷雙方的通信:
int shutdown(int sockfd, int how);
第一個(gè)參數(shù)是你希望切斷通信的套接口文件描述符。第二個(gè)參數(shù)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()
這個(gè)系統(tǒng)的調(diào)用十分簡(jiǎn)單。它將告訴你是誰在連接的另一端:
#include <sys/socket.h>
int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);
第一個(gè)參數(shù)是連接的數(shù)據(jù)流套接口文件描述符。第二個(gè)參數(shù)是指向包含另一端的信息的數(shù)
據(jù)結(jié)構(gòu)s o c k a d d r的指針。第三個(gè)參數(shù)可以設(shè)置為sizeof(struct sockaddr)。
如果出錯(cuò),系統(tǒng)調(diào)用將返回- 1。
一旦你獲得了它們的地址,你可以使用inet_ntoa() 或者g e t h o s t b y a d d r ( )來得到更多的信息。
5.10 gethostname()
系統(tǒng)調(diào)用g e t h o s t n a m e ( )比系統(tǒng)調(diào)用g e t p e e r n a m e ( )還簡(jiǎn)單。它返回程序正在運(yùn)行的計(jì)算機(jī)的
名字。系統(tǒng)調(diào)用g e t h o s t b y n a m e ( )可以使用這個(gè)名字來決定你的機(jī)器的I P地址。
下面是一個(gè)例子:
#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”,即域名服務(wù)器。它可以把域名翻譯成相應(yīng)的I P地址。
你可以使用此I P地址調(diào)用b i n d ( )、c o n n e c t ( )、s e n d t o ( )或者用于其他的地方。
系統(tǒng)調(diào)用g e t h o s t b y n a m e ( )可以完成這個(gè)函數(shù):
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
它返回一個(gè)指向數(shù)據(jù)結(jié)構(gòu)h o s t e n t的指針,數(shù)據(jù)結(jié)構(gòu)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 —主機(jī)的正式名稱。
h_aliases —主機(jī)的別名。
h _ a d d r t y p e—將要返回的地址的類型,一般是A F _ I N E T。
h _ l e n g t h—地址的字節(jié)長(zhǎng)度。
h _ a d d r _ l i s t—主機(jī)的網(wǎng)絡(luò)地址。
h_addr —h _ a d d r _ l i s t中的第一個(gè)地址。
系統(tǒng)調(diào)用g e t h o s t b y n a m e ( )返回一個(gè)指向填充好的數(shù)據(jù)結(jié)構(gòu)h o s t e n t的指針。當(dāng)發(fā)生錯(cuò)誤時(shí),
則返回一個(gè)N U L L指針。下面是一個(gè)實(shí)際例子:
#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 ( )時(shí),你不能使用p e r r o r ( )來打印錯(cuò)誤信息。你應(yīng)該使用的是系統(tǒng)調(diào)用
h e r r o r ( )。
7 客戶機(jī)/服務(wù)器模式
在網(wǎng)絡(luò)上大部分的通信都是在客戶機(jī)/服務(wù)器模式下進(jìn)行的。例如t e l n e t。當(dāng)你使用t e l n e t連
接到遠(yuǎn)程主機(jī)的端口2 3時(shí),主機(jī)上的一個(gè)叫做t e l n e t d的程序就開始運(yùn)行。它處理所有進(jìn)入的
t e l n e t連接,為你設(shè)置登錄提示符等。
應(yīng)當(dāng)注意的是客戶機(jī)/服務(wù)器模式可以使用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。每當(dāng)你使用f t p時(shí),遠(yuǎn)程計(jì)算機(jī)都在運(yùn)
行一個(gè)f t p d為你服務(wù)。
一般情況下,一臺(tái)機(jī)器上只有一個(gè)服務(wù)器程序,它通過使用f o r k ( )來處理多個(gè)客戶端程序
的請(qǐng)求。最基本的處理方法是:服務(wù)器等待連接,使用a c c e p t ( )接受連接,調(diào)用f o r k ( )生成一個(gè)
子進(jìn)程處理連接。
8 簡(jiǎn)單的數(shù)據(jù)流服務(wù)器程序
此服務(wù)器程序所作的事情就是通過一個(gè)數(shù)據(jù)流連接發(fā)送字符串“ Hello, Wo r l d ! \ n”。你可以
在一個(gè)窗口上運(yùn)行此程序,然后在另一個(gè)窗口使用t e l n e t:
$ telnet remotehostname 3490
其中,r e m o t e h o s t n a m e是你運(yùn)行的機(jī)器名。下面是此程序的代碼:
#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 */
}
}
你也可以使用下面的客戶機(jī)程序從服務(wù)器上得到字符串。
9 簡(jiǎn)單的數(shù)據(jù)流客戶機(jī)程序
客戶機(jī)所做的是連接到你在命令行中指定的主機(jī)的3 4 9 0端口。它讀取服務(wù)器發(fā)送的字符
串。
下面是客戶機(jī)程序的代碼:
#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;
}
如果你在運(yùn)行服務(wù)器程序之前運(yùn)行客戶機(jī)程序,則將會(huì)得到一個(gè)“ Connection refused”的
信息。
10 數(shù)據(jù)報(bào)套接口
程序l i s t e n e r在機(jī)器中等待端口4 9 5 0到來的數(shù)據(jù)包。程序t a l k e r向指定的機(jī)器的4 9 5 0端口發(fā)
送數(shù)據(jù)包。
下面是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;
}
你可以在一臺(tái)機(jī)器上運(yùn)行l(wèi) i s t e n e r程序,在另一臺(tái)機(jī)器上運(yùn)行t a l k e r程序,然后觀察它們之
間的通信。
21.11 阻塞
當(dāng)使用上面的listener程序時(shí),此程序在等待直到一個(gè)數(shù)據(jù)包到來。這是因?yàn)樗{(diào)用了
recvform(),如果沒有數(shù)據(jù),recvform ( )就一直阻塞,直到有數(shù)據(jù)到來。
很多函數(shù)都有阻塞。系統(tǒng)調(diào)用accept ( )阻塞,所有的類似recv*()的函數(shù)也可以阻塞。它們
之所以可以阻塞是因?yàn)橄到y(tǒng)內(nèi)核允許它們阻塞。當(dāng)你第一次創(chuàng)建一個(gè)套接口文件描述符時(shí),系
統(tǒng)內(nèi)核將它設(shè)置為可以阻塞。如果你不希望套接口阻塞,你可以使用系統(tǒng)調(diào)用fcntl():
#include <unistd.h>
#include <fcntl.h>
.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
如果你設(shè)置為不阻塞,那么就得頻繁地詢問套接口以便檢查有無信息到來。如果你試圖讀
取一個(gè)沒有阻塞的套接口,同時(shí)它又沒有數(shù)據(jù),那么你將得到- 1。
詢問套接口以檢查有無信息得到來可能會(huì)占用太多的C P U時(shí)間。另一個(gè)可以使用的方法是
select()。
select()用于同步I / O多路復(fù)用。這個(gè)系統(tǒng)調(diào)用十分有用。考慮一下下面的情況:你是一個(gè)
服務(wù)器,你希望監(jiān)聽進(jìn)入的連接,同時(shí)還一直從已有的連接中讀取信息。
也許你認(rèn)為可以使用一個(gè)accept()調(diào)用和幾個(gè)recv()調(diào)用。但如果調(diào)用accept()阻塞了怎么
辦?如果在這時(shí)你希望調(diào)用recv()接受數(shù)據(jù)呢?
系統(tǒng)調(diào)用select()使得你可以同時(shí)監(jiān)視幾個(gè)套接口。它可以告訴你哪一個(gè)套接口已經(jīng)準(zhǔn)備好
了以供讀取,哪一個(gè)套接口已經(jīng)可以寫入。
下面是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);
此函數(shù)監(jiān)視幾個(gè)文件描述符,特別是r e a d f d s、w r i t e f d s和e x c e p t f d s。如果你希望檢查是否
可以從標(biāo)準(zhǔn)輸入中和一些其他的套接口文件描述符s o c k f d中讀取數(shù)據(jù),只需把文件描述符0和
s o c k f d添加到r e a d f d s中。參數(shù)n u m f d s應(yīng)該設(shè)置為最高的文件描述符的值加1。
當(dāng)s e l e c t ( )返回時(shí),r e a d f d s將會(huì)被修改以便反映你選擇的那一個(gè)文件描述符已經(jīng)準(zhǔn)備好了以
供讀取。你可以使用F D _ I S S E T ( )測(cè)試。
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)—檢測(cè)fd 是否在文件描述符集中。
數(shù)據(jù)結(jié)構(gòu)t i m e v a l包含下面的字段:
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
} ;
把t v _ s e c設(shè)置成需要等待的時(shí)間秒數(shù),t v _ u s e c設(shè)置成需要等待的微秒數(shù)。一秒中包括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系統(tǒng)高級(jí)編程-china pub
posted on 2009-11-15 21:40
chatler 閱讀(733)
評(píng)論(0) 編輯 收藏 引用 所屬分類:
Socket