《網路socket程式設計指南》6
阿新 • • 發佈:2019-01-10
資料包 Sockets
我不想講更多了,所以我給出程式碼 talker.c 和 listener.c。
listener 在機器上等待在埠 4590 來的資料包。talker 傳送資料包到 一定的機器,它包含使用者在命令列輸入的內容。
這裡就是 listener.c:
#include
#include
#include
#include
#include
#include
#include
#include
#define MYPORT 4950 /* the port users will be sending to */
#define MAXBUFLEN 100
main()
{
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_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */
bzero(&(my_addr.sin_zero),; /* 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.sin_addr));
printf("packet is %d bytes long/n",numbytes);
buf[numbytes] = '/0';
printf("packet contains /"%s/"/n",buf);
close(sockfd);
}
注意在我們的呼叫 socket(),我們最後使用了 SOCK_DGRAM。同時, 沒有必要去使用 listen() 或者 accept()。我們在使用無連線的資料報套接 字!
下面是 talker.c:
#include
#include
#include
#include
#include
#include
#include
#include
#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 */
herror("gethostbyname");
exit(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),; /* 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("sendto");
exit(1);
}
printf("sent %d bytes to
%s/n",numbytes,inet_ntoa(their_addr.sin_addr));
close(sockfd);
return 0;
}
這就是所有的了。在一臺機器上執行 listener,然後在另外一臺機器上 執行 talker。觀察它們的通訊!
除了一些我在上面提到的資料套接字連線的小細節外,對於資料套接 字,我還得說一些,當一個講話者呼叫connect()函式時並指定接受者的地 址時,從這點可以看出,講話者只能向connect()函式指定的地址傳送和接 受資訊。因此,你不需要使用sendto()和recvfrom(),你完全可以用send() 和recv()代替。
--------------------------------------------------------------------------------
阻塞
阻塞,你也許早就聽說了。"阻塞"是 "sleep" 的科技行話。你可能注意 到前面執行的 listener 程式,它在那裡不停地執行,等待資料包的到來。 實際在執行的是它呼叫 recvfrom(),然後沒有資料,因此 recvfrom() 說" 阻塞 (block)",直到資料的到來。
很多函式都利用阻塞。accept() 阻塞,所有的 recv*() 函式阻塞。它 們之所以能這樣做是因為它們被允許這樣做。當你第一次呼叫 socket() 建 立套接字描述符的時候,核心就將它設定為阻塞。如果你不想套接字阻塞, 你就要呼叫函式 fcntl():
#include
#include
.
.
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
.
.
通過設定套接字為非阻塞,你能夠有效地"詢問"套接字以獲得資訊。如 果你嘗試著從一個非阻塞的套接字讀資訊並且沒有任何資料,它不允許阻 塞--它將返回 -1 並將 errno 設定為 EWOULDBLOCK。
但是一般說來,這種詢問不是個好主意。如果你讓你的程式在忙等狀 態查詢套接字的資料,你將浪費大量的 CPU 時間。更好的解決之道是用 下一章講的 select() 去查詢是否有資料要讀進來。
--------------------------------------------------------------------------------
select()--多路同步 I/O
雖然這個函式有點奇怪,但是它很有用。假設這樣的情況:你是個服 務器,你一邊在不停地從連線上讀資料,一邊在偵聽連線上的資訊。 沒問題,你可能會說,不就是一個 accept() 和兩個 recv() 嗎? 這麼 容易嗎,朋友? 如果你在呼叫 accept() 的時候阻塞呢? 你怎麼能夠同時接 受 recv() 資料? “用非阻塞的套接字啊!” 不行!你不想耗盡所有的 CPU 吧? 那麼,該如何是好?
select() 讓你可以同時監視多個套接字。如果你想知道的話,那麼它就 會告訴你哪個套接字準備讀,哪個又準備寫,哪個套接字又發生了例外 (exception)。
閒話少說,下面是 select():
#include
#include
#include
int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set
*exceptfds, struct timeval *timeout);
這個函式監視一系列檔案描述符,特別是 readfds、writefds 和 exceptfds。如果你想知道你是否能夠從標準輸入和套接字描述符 sockfd 讀入資料,你只要將檔案描述符 0 和 sockfd 加入到集合 readfds 中。參 數 numfds 應該等於最高的檔案描述符的值加1。在這個例子中,你應該 設定該值為 sockfd+1。因為它一定大於標準輸入的檔案描述符 (0)。 當函式 select() 返回的時候,readfds 的值修改為反映你選擇的哪個 檔案描述符可以讀。你可以用下面講到的巨集 FD_ISSET() 來測試。 在我們繼續下去之前,讓我來講講如何對這些集合進行操作。每個集 合型別都是 fd_set。下面有一些巨集來對這個型別進行操作:
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是否在集合中
最後,是有點古怪的資料結構 struct timeval。有時你可不想永遠等待 別人傳送資料過來。也許什麼事情都沒有發生的時候你也想每隔96秒在終 端上列印字串 "Still Going..."。這個資料結構允許你設定一個時間,如果 時間到了,而 select() 還沒有找到一個準備好的檔案描述符,它將返回讓 你繼續處理。
資料結構 struct timeval 是這樣的:
struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};
只要將 tv_sec 設定為你要等待的秒數,將 tv_usec 設定為你要等待 的微秒數就可以了。是的,是微秒而不是毫秒。1,000微秒等於1毫秒,1,000 毫秒等於1秒。也就是說,1秒等於1,000,000微秒。為什麼用符號 "usec" 呢? 字母 "u" 很象希臘字母 Mu,而 Mu 表示 "微" 的意思。當然,函式 返回的時候 timeout 可能是剩餘的時間,之所以是可能,是因為它依賴於 你的 Unix 作業系統。
哈!我們現在有一個微秒級的定時器!別計算了,標準的 Unix 系統 的時間片是100毫秒,所以無論你如何設定你的資料結構 struct timeval, 你都要等待那麼長的時間。
還有一些有趣的事情:如果你設定資料結構 struct timeval 中的資料為 0,select() 將立即超時,這樣就可以有效地輪詢集合中的所有的檔案描述 符。如果你將引數 timeout 賦值為 NULL,那麼將永遠不會發生超時,即 一直等到第一個檔案描述符就緒。最後,如果你不是很關心等待多長時間, 那麼就把它賦為 NULL 吧。
下面的程式碼演示了在標準輸入上等待 2.5 秒:
#include
#include
#include
#define STDIN 0 /* file descriptor for standard input */
main()
{
struct timeval tv;
fd_set readfds;
tv.tv_sec = 2;
tv.tv_usec = 500000;
FD_ZERO(&readfds);
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");
}
如果你是在一個 line buffered 終端上,那麼你敲的鍵應該是回車 (RETURN),否則無論如何它都會超時。
現在,你可能回認為這就是在資料報套接字上等待資料的方式--你是對 的:它可能是。有些 Unix 系統可以按這種方式,而另外一些則不能。你 在嘗試以前可能要先看看本系統的 man page 了。
最後一件關於 select() 的事情:如果你有一個正在偵聽 (listen()) 的套 接字,你可以通過將該套接字的檔案描述符加入到 readfds 集合中來看是 否有新的連線。
這就是我關於函式select() 要講的所有的東西。