?? bsd socket 簡易入門手冊.txt
字號:
BSD Socket 簡易入門手冊
--------------------------------------------------------------------------------
第八軍團 時間:2004-1-17 21:42:54
類比 (什么是 socket ?)
裝上你的新電話(怎樣偵聽?)
撥號 (如何調用 socket)
談話(如何通過 sockets 交談)
掛起(結束)
世界語(交流的語言很重要)
未來在你的掌握了(下一步?)
介紹
當你進入 UNIX 的神秘世界后,立刻會發(fā)現越來越多的東西難以理解。對于大多數人來說,BSD socket 的概念就是其中一個。這是一個很短的教程來解釋他們是什么、他們如何工作并給出一些簡單的代碼來解釋如何使 用他們。
類比 (什么是 socket ?)
socket 是進行程序間通訊(IPC)的 BSD 方法。這意味著 socket 用來讓一個進程和其他的進程互通信息,就象我們 用電話來和其他的人交流一樣。 用電話來比喻是很恰當的,我們在后面將一直用電話這個概念來描敘 socket 。
裝上你的新電話(怎樣偵聽?)
一個人要能夠收到別人打給他的電話,首先他要裝上一門電話。同樣,你必須先建立 socket 以偵聽線路。這個過 程包含幾個步驟。首先,你要建立一個新的 socket,就象先裝上電話一樣。socket() 命令就完成這個工作。 因為 sockets 有幾種類型,你要注明你要建立什么類型的。你要做一個選擇是 socket 的地址格式。如同電話有音 頻和脈沖兩種形式一樣,socket 有兩個最重要的選項是 AF_UNIX 和 IAF_INET。AF_UNIX 就象 UNIX 路徑名一樣識別 sockets。這種形式對于在同 一臺機器上的 IPC 很有用。而 AF_INET 使用象 192.9.200.10 這樣被點號隔開的四個十進制數字的地址格式。除了機器地址以外,還 可以利用端口號來允許每臺機器上的多個 AF_INET socket。我們這里將著重于 AF_INET 方式,因為他很有用并廣泛使用。
另外一個你必須提供的參數是 socket 的類型。兩個重要的類型是 SOCK_STREAM 和 SOCK_DGRAM。 SOCK_STREAM 表明數據象字符流一樣通過 socket 。而 SOCK_DGRAM 則表明數據將是數據報(datagrams)的形式。我們將講解 SOCK_STREAM sockets,他很常見并易于使用。 在建立 socket 后,我們就要提供 socket 偵聽的地址了。就象你還要個電話號碼來接電話一樣。bind() 函數來處 理這件事情。 SOCK_STREAM sockets 讓連接請求形成一個隊列。如果你忙于處理一個連接,別的連接請求將一直等待到該連接處 理完畢。listen() 函數用來設置最大不被拒絕的請求數(一般為5個)。一般最好不要使用 listen() 函數。 下面的代碼說明如何利用 socket()、 bind() 和 listen() 函數建立連接并可以接受數據。
/* code to establish a socket; originally from bzs@bu-cs.bu.edu */
int establish(unsigned short portnum)
{
char myname[MAXHOSTNAME+1];
int s;
struct sockaddr_in sa;
struct hostent *hp;
memset(&sa, 0, sizeof(struct sockaddr_in)); /* clear our address */
gethostname(myname, MAXHOSTNAME); /* who are we? */
hp= gethostbyname(myname); /* get our address info */
if (hp == NULL) /* we don't exist !? */
return(-1);
sa.sin_family= hp->h_addrtype; /* this is our host address */
sa.sin_port= htons(portnum); /* this is our port number */
if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0) /* create socket */
return(-1);
if (bind(s,&sa,sizeof(struct sockaddr_in)) < 0)
{
close(s);
return(-1); /* bind address to socket */
}
listen(s, 3); /* max # of queued connects */
return(s);
}
在建立完 socket 后,你要等待對該 socket 的調用了。accept() 函數為此目的而來。調用 accept() 如同在電話鈴響后提起電話一樣。Accept() 返回一個新的連接到調用方的 socket 。 下面的代碼演示使用是個演示。
/* wait for a connection to occur on a socket created with establish() */
int get_connection(int s)
{
int t; /* socket of connection */
if ((t = accept(s,NULL,NULL)) < 0) /* accept connection if there is one */
return(-1);
return(t);
}
和電話不同的是,在你處理先前的連接的時候,你還可以接受調用。為此,一般用 fork 來處理每個連接。下面的 代碼演示如何使用 establish() 和 get_connection() 來處理多個連接。
#include <errno.h> /* obligatory includes */
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#define PORTNUM 50000 /* random port number, we need something */
void fireman(void);
void do_something(int);
main()
{
int s, t;
if ((s= establish(PORTNUM)) < 0)
{
/* plug in the phone */
perror("establish");
exit(1);
}
signal(SIGCHLD, fireman); /* this eliminates zombies */
for (;;)
{
/* loop for phone calls */
if ((t= get_connection(s)) < 0)
{
/* get a connection */
if (errno == EINTR) /* EINTR might happen on accept(), */
continue; /* try again */
perror("accept"); /* bad */
exit(1);
}
switch(fork())
{
/* try to handle connection */
case -1 : /* bad news. scream and die */ perror("fork");
close(s);
close(t);
exit(1);
case 0 : /* we're the child, do something */
close(s);
do_something(t);
exit(0);
default : /* we're the parent so look for */
close(t); /* another connection */
continue;
}
}
}
/* as children die we should get catch their returns or else we get * zombies, A Bad Thing. fireman() catches falling children. */
void fireman(void)
{
while (waitpid(-1, NULL, WNOHANG) > 0) ;
} /* this is the function that plays with the socket. it will be called * after getting a connection. */
void do_something(int s) { /* do your thing with the socket here : : */ }
撥號 (如何調用 socket)
現在你應該知道如何建立 socket 來接受調用了。那么如何調用呢?和電話一樣,你要先有個電話。用 socket() 函數來完成這件事情,就象建立偵聽的 socket 一樣。 在給 socket 地址后,你可以用 connect() 函數來連接偵聽的 socket 了。下面是一段代碼。
int call_socket(char *hostname, unsigned short portnum)
{
struct sockaddr_in sa;
struct hostent *hp;
int a, s;
if ((hp= gethostbyname(hostname)) == NULL)
{
/* do we know the host's */
errno= ECONNREFUSED; /* address? */
return(-1); /* no */ }
memset(&sa,0,sizeof(sa));
memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length); /* set address */
sa.sin_family= hp->h_addrtype;
sa.sin_port= htons((u_short)portnum);
if ((s= socket(hp->h_addrtype,SOCK_STREAM,0)) < 0) /* get socket */ return(-1);
if (connect(s,&sa,sizeof sa) < 0)
{
/* connect */
close(s);
return(-1);
}
return(s);
}
這個函數返回一個可以流過數據的 socket 。
談話(如何通過 sockets 交談) 好了,你在要傳輸數據的雙方建立連接了,現在該傳輸數據了。read() 和 write() 函數來處理吧。除了在 socket 讀寫和文件讀寫中的一個區(qū)別外,和處理一般的文件一樣。區(qū)別是你一般不能得到你所要的數目的數據。所以你要 一直循環(huán)到你需要的數據的到來。
一個簡單的例子:將一定的數據讀到緩存。
int read_data(int s, /* connected socket */ char *buf, /* pointer to the buffer */ int n /* number of characters (bytes) we want */ )
{
int bcount; /* counts bytes read */
int br; /* bytes read this pass */
bcount= 0;
br= 0;
while (bcount < n)
{
/* loop until full buffer */
if ((br= read(s,buf,n-bcount)) > 0)
{
bcount += br; /* increment byte counter */
buf += br; /* move buffer ptr for next read */ }
else if (br < 0) /* signal an error to the caller */
return(-1); }
return(bcount); }
相同的函數也可以寫數據,留給我們的讀者吧。
掛起(結束)
和你通過電話和某人交談后一樣,你要在 socket 間關閉連接。一般 close() 函數用來關閉每邊的 socket 連接。如果一邊的已經關閉,而另外一邊卻在向他寫數據,則返回一個錯誤代碼。
世界語(交流的語言很重要)
現在你可以在機器間聯(lián)絡了,可是要小心你所說的話。許多機器有自己的方言,如 ASCII 和 EBCDIC。更常見的問題是字節(jié)順序問題。除非你一直傳輸的都是文本,否則你一定要注意這個問題。幸運的是,人 們找出了解決的辦法。 在很久以前,人們爭論哪種順序更“正確”。現在必要時有相應的函數來轉換。其中有 htons()、ntohs()、 htonl() 和 ntohl()。在傳輸一個整型數據前,先轉換一下。
i= htonl(i);
write_data(s, &i, sizeof(i));
在讀數據后,再變回來。 read_data(s, &i, sizeof(i)); i= ntohl(i); 如果你一直堅持這個習慣,你將比別人少出錯的機會。
未來在你的掌握了(下一步?)
就用我們剛才討論的東西,你就可以寫自己的通訊程序了。和對待所有的新生事物一樣, 最好還是看看別人已經做 了些什么。這里有許多關于 BSD socket 的東西可以參考。 請注意,例子中沒有錯誤檢查,這在“真實”的程序中是很重要
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -