SOCK_STREAM
SOCK_STREAM
int socket(int domain, int type, int protocol);
在參數表中,domain指定使用何種的地址類型,比較常用的有:
PF_INET, AF_INET: Ipv4網路協議;
PF_INET6, AF_INET6: Ipv6網路協議。
type參數的作用是設置通信的協議類型,可能的取值如下所示:
SOCK_STREAM:提供面向連接的穩定數據傳輸,即TCP協議。
SOCK_DGRAM:使用不連續不可靠的數據包連接。
SOCK_SEQPACKET:提供連續可靠的數據包連接。
SOCK_RAW:提供原始網路協議存取。
SOCK_RDM:提供可靠的數據包連接。
SOCK_PACKET:與網路驅動程序直接通信。
參數protocol用來指定socket所使用的傳輸協議編號。這一參數通常不具體設置,一般設置為0即可。
1、介紹
當你進入 UNIX 的神秘世界后,立刻會發現越來越多的東西難以理解。對於大多數人來說,BSD socket 的概念就是其中一個。這是一個很短的教程來解釋他們是什麼、他們如何工作並給出一些簡單的代碼來解釋如何使用他們。
2、類比 (什麼是 socket ?)
socket 是進行程序間通訊(IPC)的 BSD 方法。這意味著 socket 用來讓一個進程和其他的進程互通信息,就象我們用電話來和其他的人交流一樣。
用電話來比喻是很恰當的,我們在後面將一直用電話這個概念來描敘 socket 。
3、裝上你的新電話(怎樣偵聽?)
一個人要能夠收到別人打給他的電話,首先他要裝上一門電話。同樣,你必須先建立 socket 以偵聽線路。這個過程包含幾個步驟。首先,你要建立一個新的 socket,就象先裝上電話一樣。socket() 命令就完成這個工作。
因為 sockets 有幾種類型,你要註明你要建立什麼類型的。你要做一個選擇是 socket 的地址格式。如同電話有音頻和脈衝兩種形式一樣,socket 有兩個最重要的選項是 AF_UNIX 和 AF_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,他很常見並易於使用。
SOCK_STREAM sockets 讓連接請求形成一個隊列。如果你忙於處理一個連接,別的連接請求將一直等待到該連接處理完畢。listen() 函數用來設置最大不被拒絕的請求數(一般為5個)。一般最好不要使用 listen() 函數。
下面的代碼說明如何利用 socket()、 bind() 和 listen() 函數建立連接並可以接受數據。
{ char myname[MAXHOSTNAME+1];
structsockaddr_insa;
struct hostent *hp;
memset(&sa, 0, sizeof(struct sockaddr_in));
gethostname(myname, MAXHOSTNAME);
hp= gethostbyname(myname);
if (hp == NULL)
return(-1);
sa.sin_family= hp->h_addrtype;
sa.sin_port= htons(portnum);
if ((s= socket(AF_INET, SOCK_STREAM, 0)) < 0)
return(-1);
if (bind(s,&sa,sizeof(structsockaddr_in)) < 0) {
close(s);
return(-1);
}
listen(s, 3);
return(s);
}
在建立完 socket 后,你要等待對該 socket 的調用了。accept() 函數為此目的而來。調用 accept() 如同在電話鈴響後提起電話一樣。Accept() 返回一個新的連接到調用方的 socket 。
下面的代碼演示使用是個演示。
int get_connection(int s)
{ int t;
if ((t = accept(s,NULL,NULL)) < 0)
return(-1);
return(t);
}
和電話不同的是,在你處理先前的連接的時候,你還可以接受調用。為此,一般用 fork 來處理每個連接。下面的代碼演示如何使用 establish() 和 get_connection() 來處理多個連接。
#include
#include
#include
#include
#include
#include
#include
#include
#define PORTNUM 50000
void fireman(void);
void do_something(int);
main()
{ int s, t;
if ((s= establish(PORTNUM)) < 0) {
perror("establish");
exit(1);
}
signal(SIGCHLD, fireman);
for (;;) {
if ((t= get_connection(s)) < 0) {
if (errno == EINTR)
continue;
perror("accept");
exit(1);
}
switch(fork()) {
case -1 :
perror("fork");
close(s);
close(t);
exit(1);
case 0 :
close(s);
do_something(t);
exit(0);
default :
close(t);
continue;
}
}
}
void fireman(void)
{
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
void do_something(int s)
{
}
4、撥號 (如何調用 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) {
errno= ECONNREFUSED;
return(-1);
}
memset(&sa,0,sizeof(sa));
memcpy((char *)&sa.sin_addr,hp->h_addr,hp->h_length);
sa.sin_family= hp->h_addrtype;
sa.sin_port= htons((u_short)portnum);
if ((s= socket(hp->h_addrtype,SOCK_STREAM,0)) 0) {
bcount += br;
buf += br;
}
else if (br < 0)
return(-1);
}
return(bcount);
}
相同的函數也可以寫數據,留給我們的讀者吧。
5、談話
6、掛起(結束)
和你通過電話和某人交談后一樣,你要在 socket 間關閉連接。一般 close() 函數用來關閉每邊的 socket 連接。如果一邊的已經關閉,而另外一邊卻在向他寫數據,則返回一個錯誤代碼。
7、世界語(交流的語言很重要)
現在你可以在機器間聯絡了,可是要小心你所說的話。許多機器有自己的方言,如 ASCII 和 EBCDIC。更常見的問題是位元組順序問題。除非你一直傳輸的都是文本,否則你一定要注意這個問題。幸運的是,人們找出了解決的辦法。
i= htonl(i);
write_data(s, &i, sizeof(i));
在讀數據后,再變回來。
read_data(s, &i, sizeof(i));
i= ntohl(i);
如果你一直堅持這個習慣,你將比別人少出錯的機會。
8、未來在你的掌握了(下一步?)
就用我們剛才討論的東西,你就可以寫自己的通訊程序了。和對待所有的新生事物一樣,最好還是看看別人已經做了些什麼。這裡有許多關於 BSD socket 的東西可以參考。
請注意,例子中沒有錯誤檢查,這在“真實”的程序中是很重要的。你應該對此充分重視。