歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> Unix基礎知識

BSD Socket簡易入門手冊

介紹

當你進入UNIX的神秘世界後,立刻會發現越來越多的東西難以理解。對於大多數人來說,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_INETsocket。我們這裡將著重於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 [email protected]
*/
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讀寫和文件讀寫中的一個區別外,和處理一般的文件一樣。區別是你一般不能得到你所要的數目的數據。所以你要一直循環到你需要的數據的到來。一個簡單的例子:將一定的數據讀到緩存。

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連接。如果一邊的已經關閉,而另外一邊卻在向他寫數據,則返回一個錯誤代碼。

世界語(交流的語言很重要)

現在你可以在機器間聯絡了,可是要小心你所說的話。許多機器有自己的方言,如ASCII和EBCDIC。更常見的問題是字節順序問題。除非你一直傳輸的都是文本,否則你一定要注意這個問題。幸運的是,人們找出了解決的辦法。

在很久以前,人們爭論哪種順序更“正確”。現在必要時有相應的函數來轉換。其中有htons()、ntohs()、htonl()和ntohl()。在傳輸一個整型數據前,先轉換一下。

i= htonl(i);

write_data(s, &i, sizeof(i));

在讀數據後,再變回來。

read_data(s, &i, sizeof(i));

i= ntohl(i);

如果你一直堅持這個習慣,你將比別人少出錯的機會。

未來在你的掌握了(下一步?)

就用我們剛才討論的東西,你就可以寫自己的通訊程序了。和對待所有的新生事物一樣, 最好還是看看別人已經做了些什麼。這裡有許多關於BSD socket

的東西可以參考。

請注意,例子中沒有錯誤檢查,這在“真實”的程序中是很重要.

Copyright © Linux教程網 All Rights Reserved