[Socket]BSD socket简易入门
介紹
當(dāng)你進(jìn)入 UNIX 的神秘世界后,立刻會(huì)發(fā)現(xiàn)越來(lái)越多的東西難以理解。對(duì)于大多數(shù)人來(lái)說(shuō),BSD socket 的概念就是其中一個(gè)。這是一個(gè)很短的教程來(lái)解釋他們是什么、他們?nèi)绾喂ぷ鞑⒔o出一些簡(jiǎn)單的代碼來(lái)解釋如何使用他們。
類(lèi)比 (什么是 socket ?)
socket 是進(jìn)行程序間通訊(IPC)的 BSD 方法。這意味著 socket 用來(lái)讓一個(gè)進(jìn)程和其他的進(jìn)程互通信息,就象我們用電話來(lái)和其他的人交流一樣。
用電話來(lái)比喻是很恰當(dāng)?shù)?#xff0c;我們?cè)诤竺鎸⒁恢庇秒娫掃@個(gè)概念來(lái)描敘 socket 。
裝上你的新電話(怎樣偵聽(tīng)?)
一個(gè)人要能夠收到別人打給他的電話,首先他要裝上一門(mén)電話。同樣,你必須先建立 socket 以偵聽(tīng)線路。這個(gè)過(guò)程包含幾個(gè)步驟。首先,你要建立一個(gè)新的 socket,就象先裝上電話一樣。socket() 命令就完成這個(gè)工作。
因?yàn)?sockets 有幾種類(lèi)型,你要注明你要建立什么類(lèi)型的。你要做一個(gè)選擇是 socket 的地址格式。如同電話有音頻和脈沖兩種形式一樣,socket 有兩個(gè)最重要的選項(xiàng)是 AF_UNIX 和 IAF_INET。AF_UNIX 就象 UNIX 路徑名一樣識(shí)別 sockets。這種形式對(duì)于在同一臺(tái)機(jī)器上的 IPC 很有用。而 AF_INET 使用象 192.9.200.10 這樣被點(diǎn)號(hào)隔開(kāi)的四個(gè)十進(jìn)制數(shù)字的地址格式。除了機(jī)器地址以外,還可以利用端口 號(hào)來(lái)允許每臺(tái)機(jī)器上的多個(gè) AF_INET socket。我們這里將著重于 AF_INET 方式,因?yàn)樗苡杏貌V泛使用。
另外一個(gè)你必須提供的參數(shù)是 socket 的類(lèi)型。兩個(gè)重要的類(lèi)型是 SOCK_STREAM 和 SOCK_DGRAM。SOCK_STREAM 表明數(shù)據(jù)象字符流一樣通過(guò) socket 。而 SOCK_DGRAM 則表明數(shù)據(jù)將是數(shù)據(jù)報(bào)(datagrams)的形式。我們將講解 SOCK_STREAM sockets,他很常見(jiàn)并易于使用。
在建立 socket 后,我們就要提供 socket 偵聽(tīng)的地址了。就象你還要個(gè)電話號(hào)碼來(lái)接電話一樣。bind() 函數(shù)來(lái)處理這件事情。
SOCK_STREAM sockets 讓連接請(qǐng)求形成一個(gè)隊(duì)列。如果你忙于處理一個(gè)連接,別的連接請(qǐng)求將一直等待到該連接 處理完畢。listen() 函數(shù)用來(lái)設(shè)置最大不被拒絕的請(qǐng)求數(shù)(一般為5個(gè))。 一般最好不要使用listen() 函數(shù)。
下面的代碼說(shuō)明如何利用 socket()、 bind() 和 listen() 函數(shù)建立連接并可以接受數(shù)據(jù)。
/* 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 后,你要等待對(duì)該 socket 的調(diào)用了。accept() 函數(shù)為此目的而來(lái)。調(diào)用 accept() 如同在電話鈴響后提起電話一樣。Accept() 返回一個(gè)新的連接到調(diào)用方的 socket 。
下面的代碼演示使用是個(gè)演示。
/* 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); }和電話不同的是,在你處理先前的連接的時(shí)候,你還可以接受調(diào)用。為此,一般用 fork 來(lái)處理每個(gè)連接。下面的代碼演示如何使用 establish() 和 get_connection() 來(lái)處理多個(gè)連接。
#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::*/ }撥號(hào) (如何調(diào)用 socket)
現(xiàn)在你應(yīng)該知道如何建立 socket 來(lái)接受調(diào)用了。那么如何調(diào)用呢?和電話一樣,你要先有個(gè)電話。用socket() 函數(shù)來(lái)完成這件事情,就象建立偵聽(tīng)的 socket 一樣。
在給 socket 地址后,你可以用 connect() 函數(shù)來(lái)連接偵聽(tīng)的 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); }這個(gè)函數(shù)返回一個(gè)可以流過(guò)數(shù)據(jù)的 socket 。
談話(如何通過(guò) sockets 交談)
好了,你在要傳輸數(shù)據(jù)的雙方建立連接了,現(xiàn)在該傳輸數(shù)據(jù)了。read() 和 write() 函數(shù)來(lái)處理吧。除了在 socket 讀寫(xiě)和文件讀寫(xiě)中的一個(gè)區(qū)別外,和處理一般的文件一樣。區(qū)別是你一般不能得到你所要 的數(shù)目的數(shù)據(jù)。所以你要一直循環(huán)到你需要的數(shù)據(jù)的到來(lái)。一個(gè)簡(jiǎn)單的例子:將一定的數(shù)據(jù)讀到緩存。
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); }相同的函數(shù)也可以寫(xiě)數(shù)據(jù),留給我們的讀者吧。
掛起(結(jié)束)
和你通過(guò)電話和某人交談后一樣,你要在 socket 間關(guān)閉連接。一般 close() 函數(shù)用來(lái)關(guān)閉每邊的 socket 連接。如果一邊的已經(jīng)關(guān)閉,而另外一邊卻在向他寫(xiě)數(shù)據(jù),則返回一個(gè)錯(cuò)誤代碼。
世界語(yǔ)(交流的語(yǔ)言很重要)
現(xiàn)在你可以在機(jī)器間聯(lián)絡(luò)了,可是要小心你所說(shuō)的話。許多機(jī)器有自己的方言,如 ASCII 和 EBCDIC。更常見(jiàn)的問(wèn)題是字節(jié)順序問(wèn)題。除非你一直傳輸?shù)亩际俏谋?#xff0c;否則你一定要注意這個(gè)問(wèn)題。幸運(yùn)的是,人們找出了解決的辦法。
在很久以前,人們爭(zhēng)論哪種順序更“正確”。現(xiàn)在必要時(shí)有相應(yīng)的函數(shù)來(lái)轉(zhuǎn)換。 其中有htons()、ntohs()、htonl() 和 ntohl()。在傳輸一個(gè)整型數(shù)據(jù)前,先轉(zhuǎn)換一下。
i= htonl(i); write_data(s, &i, sizeof(i));在讀數(shù)據(jù)后,再變回來(lái)。
read_data(s, &i, sizeof(i)); i= ntohl(i);如果你一直堅(jiān)持這個(gè)習(xí)慣,你將比別人少出錯(cuò)的機(jī)會(huì)。
未來(lái)在你的掌握了(下一步?)
就用我們剛才討論的東西,你就可以寫(xiě)自己的通訊程序了。和對(duì)待所有的新生事 物一樣, 最好還是看看別人已經(jīng)做了些什么。這里有許多關(guān)于 BSD socket 的東西可以參考。
請(qǐng)注意,例子中沒(méi)有錯(cuò)誤檢查,這在“真實(shí)”的程序中是很重要的。你應(yīng)該對(duì)此充分重視。
轉(zhuǎn)載于:https://www.cnblogs.com/tracy-e/archive/2011/07/20/2112137.html
總結(jié)
以上是生活随笔為你收集整理的[Socket]BSD socket简易入门的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 使用Go实现Socket服务端和客户端通
- 下一篇: MATLAB---同态滤波