在這一部分,我們將會看到創建套接口與創建管道一樣的容易。雖然有一些我們將會了解到的函數參數。為了能創建成功,這些參數必須提供合適的值。
socketpair函數概要如下:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
sys/types.h文件需要用來定義一些C宏常量。sys/socket.h文件必須包含進來定義socketpair函數原型。
socketpair函數需要四個參數。他們是:
套接口的域
套接口類型
使用的協議
指向存儲文件描述符的指針
domain參數直到第2單我們才會解釋。對于socketpair函數而言,只需提供C宏AF_LOCAL。
類型參數聲明了我們希望創建哪種類型的套接口。socketpair函數的選擇如下:
SOCK_STREAM
SOCK_DGRAM
套接口類型的選擇我們將會在第4章談到。在這一章中,我們只需要簡單的使用SOCK_STREAM套接口類型。
對于socketpair函數,protocol參數必須提供為0。
參數sv[2]是接收代表兩個套接口的整數數組。每一個文件描述符代表一個套接口,并且與另一個并沒有區別。
如果函數成功,將會返回0值。否則將會返回-1表明創建失敗,并且errno來表明特定的錯誤號。
使用socketpair的例子
為了演示如何使用socketpair函數,我們用下面的例子來進行演示。
1:? /* Listing 1.1:
2:?? *
3:?? * Example of socketpair(2) function:
4:?? */
5:? #include <stdio.h>
6:? #include <stdlib.h>
7:? #include <unistd.h>
8:? #include <errno.h>
9:? #include <string.h>
10: #include <sys/types.h>
11: #include <sys/socket.h>
12:
13: int
14: main(int argc,char **argv) {
15:???? int z;??????????????????? /* Status return code */
16:???? int s[2];???????????????? /* Pair of sockets */
17:
18:???? /*
19:????? * Create a pair of local sockets:
20:????? */
21:???? z = socketpair(AF_LOCAL,SOCK_STREAM,0,s);
22:
23:???? if ( z == -1 ) {
24:???????? fprintf(stderr,
25:???????????? "%s: socketpair(AF_LOCAL,SOCK_STREAM,0)\n",
26:???????????? strerror(errno));
27:??????? return 1;??????????? /* Failed */
28:?? }
29:
30:?? /*
31:???? * Report the socket file descriptors returned:
32:???? */
33:?? printf("s[0] = %d;\n",s[0]);
34:?? printf("s[1] = %d;\n",s[1]);
35:
36:?? system("netstat --unix -p");
37:
38:?? return 0;
39: }
演示程序的描述如下:
1 在第16行聲明數組s[2]用來存儲用來引用兩個新創建的套接口的文件描述符。
2 在第21行調用socketpair函數。domain參數指定為AF_LOCAL,套接口類型參數指定為SOCK_STREAM,而協議指定為0。
3 23行的if語句用來測試socketpair函數是否成功。如果z的值為-1,就會向標準錯誤發送報告,并且在27行退出程序。
4 如果函數調用成功,控制語句就會轉到33,并且在34行向標準輸出報告返回的文件單元數。
5 36行使用system函數來調用netstat命令。命令選項--unix表明只報告Unix套接口,-p選項則是要報告進程信息。
使用提供的Makefile,我們可以用make命令來編譯這個程序:
$ make 01lst01
gcc -c -D_GNU_SOURCE -Wall 01LST01.c
gcc 01LST01.o -o 01lst01
為了執行這個演示程序,我們可以執行下面的命令:
$ ./01lst01
程序的執行結果如下:
1:? $ ./01lst01
2:? s[0] = 3;
3:? s[1] = 4;
4:? (Not all processes could be identified, non-owned process info
5:??? will not be shown, you would have to be root to see it all.)
6:? Active UNIX domain sockets (w/o servers)
7:? Proto RefCnt Flags????? Type????? . . . I-Node PID/Program name? Path
8:? unix 1??????? []??????? STREAM?? . . . 406??? -???????????????? @00000019
9:? unix 1??????? []??????? STREAM?? . . . 490??? -???????????????? @0000001f
10: unix 1??????? []??????? STREAM?? . . . 518??? -???????????????? @00000020
11: unix 0??????? []??????? STREAM?? . . . 117??? -???????????????? @00000011
12: unix 1??????? []??????? STREAM?? . . . 789??? -???????????????? @00000030
13: unix 1??????? []??????? STREAM?? . . . 549??? -???????????????? @00000023
14: unix 1??????? []??????? STREAM?? . . .1032??? 662/01lst01
15: unix 1??????? []??????? STREAM?? . . .1031??? 662/01lst01
16: unix 1??????? []??????? STREAM?? . . . 793??? -???????????????? /dev/log
17: unix 1??????? []??????? STREAM?? . . . 582??? -???????????????? /dev/log
18: unix 1??????? []??????? STREAM?? . . . 574??? -???????????????? /dev/log
19: unix 1??????? []??????? STREAM?? . . . 572??? -???????????????? /dev/log
20: unix 1??????? []??????? STREAM?? . . . 408??? -???????????????? /dev/log
21: $
在我們上面的輸入顯示中,在第1行調用可執行程序01LST01。第2行和第3行顯示了我們在文件描述符3和4上打開套接口。接下來的4到20行是程序中netstat命令的輸出。
盡管這個程序并沒有使用創建的套接口來做任何事情,但是他確實演示了套接口的創建。并且他演示了套接口單元數的分配與打開的文件的方式一樣。
在套接口上執行I/O操作
我們在前面已經了解到套接口可以像任何打開的文件一樣向其中寫入或是從中讀取。在這一部分將我們將會親自演示這一功能。然而為了試都討論的完整,我們先來看一下read,write,close的函數概要:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
int close(int fd);
這些應是我們已經熟悉的Linux的輸入/輸入函數。通過回顧我們可以看到,read函數返從文件描述符fd中返回最大count字節的輸入,存放到buf緩沖區中。返回值代表實際讀取的字節數。如果返回0則代表文件結束。
write函數將我們指定的buf緩沖區中總計count的字節寫入文件描述符fd中。返回值代表實際寫入的字節數。通常這必須與指定的count參數相匹配。然而也會有一些情況,這個值要count小,但是我們沒有必要擔心這樣的情況。
最后,如果文件成功關閉close就會返回0。對于這些函數,如果返回-1則表明有錯誤發生,并且錯誤原因將會發送到外部變量errno中。為了可以訪問這個變量,我們需要在源文件中包含errno.h頭文件。
下面的例子是在套接口的兩個方向上執行讀取與寫入操作。
/*****************************************
?*
?* Listing 1.2
?*
?* Example performing I/O on s socket pair:
?*
?* ******************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int argc,char **argv)
{
?? ?int z;?? ??? ?/* Status return code */
?? ?int s[2];?? ?/* Pair of sockets */
?? ?char *cp;?? ?/* A work pointer */
?? ?char buf[80];?? ?/* work buffer */
?? ?/*
?? ? * Create a pair of local sockets:
?? ? */
?? ?z = socketpair(AF_LOCAL,SOCK_STREAM,0,s);
?? ?if(z == -1)
?? ?{
?? ??? ?fprintf(stderr,
?? ??? ??? ??? ?"%s:socketpair(AF_LOCAL,SOCK_STREAM,""0)\n",strerror(errno));
?? ??? ?return 1;?? ?/* Failed */
?? ?}
?? ?/*
?? ? * Write a message to socket s[1]:
?? ? */
?? ?z = write(s[1],cp="Hello?",6);
?? ?if(z<0)
?? ?{
?? ??? ?fprintf(stderr,"%s:wirte(%d,\"%s\",%d)\n",strerror(errno),s[1],cp,strlen(cp));
?? ??? ?return 2;?? ?/* Failed */
?? ?}
?? ?printf("Wrote message '%s' on s[1]\n",cp);
?? ?/*
?? ? * Read from socket s[0]:
?? ? */
?? ?z = read(s[0],buf,sizeof buf);
?? ?if(z < 0)
?? ?{
?? ??? ?fprintf(stderr,"%s:read(%d,buf,%d)\n",
?? ??? ??? ??? ?strerror(errno),s[0],sizeof buf);
?? ??? ?return 3;?? ?/* Failed */
?? ?}
?? ?/*
?? ? * Report received message:
?? ? */
?? ?buf[z] = 0;?? ?/* NUL terminate */
?? ?printf("Recevie message '%s' from socket s[0]\n",buf);
?? ?/*
?? ? * Send a reply back to s[1] from s[0]:
?? ? */
?? ?z = write(s[0],cp="Go away!",8);
?? ?if(z < 0)
?? ?{
?? ??? ?fprintf(stderr,"%s:write(%d,\"%s\",%d)\n",
?? ??? ??? ??? ?strerror(errno),s[0],cp,strlen(cp));
?? ??? ?return 4;?? ?/* Failed */
?? ?}
?? ?printf("Wrote message '%s' on s[0]\n",cp);
?? ?/*
?? ? * Read from socket s[1]:
?? ? */
?? ?z = read(s[1],buf,sizeof buf);
?? ?if(z < 0)
?? ?{
?? ??? ?fprintf(stderr,"%s:read(%d,buf,%d)\n",
?? ??? ??? ??? ?strerror(errno),s[1],sizeof buf);
?? ??? ?return 3;?? ?/* Failed */
?? ?}
?? ?/*
?? ? * Report message recevied by s[0]:
?? ? */
?? ?buf[z] = 0;?? ?/*NUL terminate */
?? ?printf("Received message '%s' from socket s[1]\n",
?? ??? ??? ?buf);
?? ?/*
?? ? * Close the sockets:
?? ? */
?? ?close(s[0]);
?? ?close(s[1]);
?? ?puts("Done");
?? ?return 0;
}
程序調用的步驟總結如下:
1 在第23行調用socketpair函數,如果成功返回,則將生成的套接口存放在數組s中。
2 在第25行測試函數是否成功,如果發生錯誤,將會報告錯誤。
3 在第36行一個由6個字符組成的消息"Hello?"寫入套接口s[1]。注意并沒有寫入空字節,因為在write函數的count參數中僅指定了6個字節。
4 第37到第42行檢測并報告可能發生的錯誤。
5 第44行聲明一個成功寫操作。
6 在第49行read調用試著從另一個套接口s[0]讀取消息。在這條語句中,可以讀取任何最大為buf[]數組尺寸的消息。
7 第50行到第55行檢測并服務在read語句中可能發生的錯誤。
8 第60行到第62行報告一條成功接收的消息。
9 第67行到第73行向套接口s[0]寫入一條回復消息"Go away!"。這就演示了不同于管道,信息在可以作為端點的套接口中雙向傳送。
10 第75行聲明一個成功的寫操作。
11 第80行到第86行應從通信線路的另一個端點套接口s[1]中讀取信息"Go away!"。
12 第91行到第93行報告成功接收的信息。
13 這兩個套接口在第98行和第99行關閉。
14 在第102行程序退出。
當程序被調用時,輸出結果如下:
$ ./01lst02
Wrote message 'Hello?' on s[1]
Received message 'Hello?' from socket s[0]
Wrote message 'Go away!' on s[0]
Received message 'Go away!' from socket s[1]
Done.
$
如果我們跟蹤我們在前面所勾畫的步驟,我們就會發現信息是在套接口中雙向傳送的。而且我們演示了套接口用與文件相同的方式來關閉。
關閉套接口
在前面,我們看到如何來創建一對套接口,并且看到如何使用這些套接口來執行最基本的輸入與輸出操作。我們也可以看到套接口可以使用與通過調用close函數來關閉文件的方式來關閉。現在我們來了解一下關閉套接口所提供的函數。
當從通過pipe函數創建的管道中讀取時,當接收到一個文件結尾時,接收就會認為不會再有要接收的數據。當關閉管道的寫端時,文件結束的條件是通過寫進程發送的。
同樣的過程也可以用在套接口上。當另一個端點關閉時,接收端就會收到一個文件結束的標識。
當本地進程希望通知遠程端不再接收數據時就會出現問題。如果本地進程關閉了他的套接口,這是可以適用的。然而,如果他希望從遠程端接收一個確認信息,這是不可能的,因為現在他的套接口已經關閉了。這樣的情況需要一個半關閉套接口的方法。
shutdown函數
下面顯示了shutdown函數的概要:
#include <sys/socket.h>
int shutdown(int s, int how);
shutdown函數需要兩個參數。他們是:
套接口描述符s指定了要部分關閉的套接口。
參數how指定要如何關閉這個套接口中。
如果函數成功則返回0。如果調用失敗則會返回-1,錯誤原因將會發送到errno。
how的可能值如下:
值?? ?宏?? ??? ?描述
0?? ?SHUT_RD?? ??? ?在指定的套接口上不再允許讀操作。
1?? ?SHUT_WR?? ??? ?在指定的套接口上不再允許寫操作。
2?? ?SHUT_RDWR?? ?在指定的套接口上不再允許讀寫操作。
注意當how值指定為2時,這個函數的調用與close函數調用相同。
關閉向一個套接口的寫
下面的代碼演示了如何指定在本地的套接口上不再執行寫操作:
int z;
int s;? /* Socket */
z = shutdown(s, SHUT_WR);
if ( z == -1 )
??? perror("shutdown()");
關閉套接口的寫端解決了一系列難題。他們是:
清空包含任何要發送的數據的內核緩沖區。通過內核網絡軟件來緩沖數據來改進性能。
向遠程套接口發送文件結束標識。這就通知遠程讀進程在這個套接口上不會再向他發送數據。
保留半關閉套接口為讀打開。這就使得在套接口上發送了文件結束標識以后還可以接收確認信息。
丟棄在這個套接口上的打開引用計數。只有最后在這個套接口上的close函數將會發送一個文件結束標識。
處理復制的套接口
如果一個套接口文件描述符通過dup或者是dup2函數來調用進行復制,只有最后的close函數調用可以關閉這個套接口。這是因為另外復制的文件描述符仍處于使用狀態。如下面的代碼如演示的樣子:
int s;????? /* Existing socket */
int d;????? /* Duplicated socket */
d = dup(s); /* duplicate this socket */
close(s);?? /* nothing happens yet */
close(d);?? /* last close, so shutdown socket */
在這個例子中,第一個close函數調用不會有任何效果。先關閉其中的任何一個都是一樣的結果。關閉s或者d將會為同一個套接口保留一個文件描述符。只有通過close函數調用來關閉最后一個存在的文件描述符才會有效果。在這個例子中,關閉d文件描述符關閉了這個套接口。
shutdown函數避免了這種區別。重復這個例子代碼,通過使用shutdown函數解決了這個問題:
int s;??????? /* Existing socket */
int d;??????? /* Duplicated socket */
d = dup(s);?? /* duplicate this socket */
shutdown(s,SHUT_RDWR); /* immediate shutdown */
盡管套接口s也在文件單元d上打開,shutdown函數立刻使得套接口執行關閉操作。這個操作在打開的文件描述符s和d都是同樣的效果,因為他們指向同一個套接口。
這個問題出現的另一個方式就是執行了fork函數調用。任何優先級高于fork操作的套接口都會在子進程中被復制。
關閉從一個套接口讀
關閉套接口的讀取端將會使得待讀取的任何數據都會被忽略掉。如果從遠程套接口發送來更多的數據,也同樣會被忽略掉。然而任何試著從這個套接口進行讀取的進程都會返回一個錯誤。這通常用來強制協議或是調試代碼。
shutdown函數的錯誤代碼如下:
錯誤?? ??? ?描述
EBADF?? ??? ?指定的套接口不是一個可用的文件描述符
ENOTSOCK?? ?指定的文件描述符不是一個套接口
ENOTCONN?? ?指定的套接口并沒有連接
從這個表中我們可以看到,對于已連接的套接口應只調用shutdown函數,否則就會返回ENOTCONN錯誤代碼。
編寫一個客戶/服務器例子
現在我們所了解的套接口API的集合已經足夠讓我們開始一些有趣的嘗試了。在這一部分,我們會檢測,編譯并且測試一個簡單的通過套接口進行通信的客戶與服務器進程。
為了使得這個程序盡可能的小,將會啟動一個程序,然后復制為一個客戶進程與一個服務器進程。子進程將會是客戶端程序角色,而原始的父進程將會執行服務器的角色。下圖顯示了父進程與子進程的關系以及套接口的使用。

父進程是最初啟動的程序。他立刻通過調用socketpair函數來生成一對套接口,然后通過調用fork函數將自己復制為兩個進程。
服務器將會接收請求,執行請求,然后退出。類似的客戶端將會執行請求,報告服務器響應,然后退出。
請 求將會采用strftime函數的第三個參數的格式。這是一個用來格式化日期與時間字符串的格式字符串。服務器將會在接收到請求時得到當前的日期與時間。 服務器將會使用客戶端的請求字符串來將其格式化為最終的字符串,然后發送給客戶端。我們先來回顧一個strftime函數的概要:
#include <time.h>
size_t strftime(char *buf,
??? size_t max,
??? const char *format,
??? const struct tm *tm);
參數buf與max分別指定了輸出緩沖區以及最大長度。參數format是一個輸入字符串,可以允許我們來格式化日期與時間字符串。最后參數tm用來指定必須來創建輸出日期與時間字符串的日期與時間組件。
/*****************************************
?*
?* Listing 1.3
?*
?* Client/Server Example Using socketpair
?* and fork:
?*
?* ******************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <time.h>
/*
?* As of RedHat 6.0,these are still not defined:
?*/
#ifndef SHUT_WR
#define SHUT_RD 0
#define SHUT_WR 1
#define SHUT_RDWR 2
#endif
/*
?* Main program
?*/
int main(int argc,char **argv)
{
?? ?int z;?? ??? ?/* Status return code */
?? ?int s[2];?? ?/* Pair of sockets */
?? ?char *msgp;?? ?/* A message pointer */
?? ?int mlen;?? ?/* Message length */
?? ?char buf[80];?? ?/* work buffer */
?? ?pid_t chpid;?? ?/* Child PID */
?? ?/*
?? ? * Create a pair of local sockets:
?? ? */
?? ?z = socketpair(AF_LOCAL,SOCK_STREAM,0,s);
?? ?if(z == -1)
?? ?{
?? ??? ?fprintf(stderr,"%s:socketpair(2)\n",strerror(errno));
?? ??? ?exit(1);
?? ?}
?? ?/*
?? ? * Now fork() into two processes:
?? ? */
?? ?if((chpid = fork()) == (pid_t)-1)
?? ?{
?? ??? ?/*
?? ??? ? * Failed to fork into two processes:
?? ??? ? */
?? ??? ?fprintf(stderr,"%s:fork(2)\n",strerror(errno));
?? ??? ?exit(1);
?? ?}
?? ?else if(chpid == 0)
?? ?{
?? ??? ?/*
?? ??? ? * This is child process(client)
?? ??? ? */
?? ??? ?char rxbuf[80]; ?? ?/*Receive buffer*/
?? ??? ?printf ("Parent PID is %ld\n",(long)getppid());
?? ??? ?close(s[0]);?? ??? ?/* Server uses s[1] */
?? ??? ?s[0] = -1;?? ??? ?/*Forget this unit */
?? ??? ?/*
?? ??? ? * Form the message and its length:
?? ??? ? */
?? ??? ?msgp = "%A %d-%b-%Y %l:%M %p";
?? ??? ?mlen = strlen(msgp);
?? ??? ?printf("Child sending request '%s'\n",msgp);
?? ??? ?fflush(stdout);
?? ??? ?/*
?? ??? ? * Write a request to the server:
?? ??? ? */
?? ??? ?z = write(s[1],msgp,mlen);
?? ??? ?if(z<0)
?? ??? ?{
?? ??? ??? ?fprintf(stderr,"%s:write(2)\n",strerror(errno));
?? ??? ??? ?exit(1);
?? ??? ?}
?? ??? ?/*
?? ??? ? * Now indicate that we will not be writing
?? ??? ? * anything further to our socket,by shutting
?? ??? ? * down the write side of the socket:
?? ??? ? */
?? ??? ?if(shutdown(s[1],SHUT_WR) == -1)
?? ??? ?{
?? ??? ??? ?fprintf(stderr,"%s:shutdown(2)\n",strerror(errno));
?? ??? ??? ?exit(1);
?? ??? ?}
?? ??? ?/*
?? ??? ? * Recevie the reply from the server:
?? ??? ? */
?? ??? ?z = read(s[1],rxbuf,sizeof rxbuf);
?? ??? ?if(z<0)
?? ??? ?{
?? ??? ??? ?fprintf(stderr,"%s:read(2)\n",strerror(errno));
?? ??? ??? ?exit(1);
?? ??? ?}
?? ??? ?/*
?? ??? ? * Put a null byte at the end of what we
?? ??? ? * received from the server:
?? ??? ? */
?? ??? ?rxbuf[z]=0;
?? ??? ?/*
?? ??? ? * Report the result:
?? ??? ? */
?? ??? ?printf("Server returned '%s'\n",rxbuf);
?? ??? ?fflush(stdout);
?? ??? ?close(s[1]);?? ?/*Close our end now*/
?? ?}
?? ?else
?? ?{
?? ??? ?/*
?? ??? ? * This is parent process(server):
?? ??? ? */
?? ??? ?int status;?? ?/*Child termintation status*/
?? ??? ?char txbuf[80];?? ?/*Reply buffer*/
?? ??? ?time_t td;?? ?/*Current date&time*/
?? ??? ?printf("Child PID is %ld\n",(long)chpid);
?? ??? ?fflush(stdout);
?? ??? ?close(s[1]);?? ?/* Cient uses s[0] */
?? ??? ?s[1] = -1;?? ?/* Forget this desciptor */
?? ??? ?/*
?? ??? ? * Wait for a request from the client:
?? ??? ? */
?? ??? ?z = read(s[0],buf,sizeof buf);
?? ??? ?if(z<0)
?? ??? ?{
?? ??? ??? ?fprintf(stderr,"%s:read(2)\n",strerror(errno));
?? ??? ??? ?exit(1);
?? ??? ?}
?? ??? ?/*
?? ??? ? * Put a null byte at the end of the
?? ??? ? * message we recevied from the client:
?? ??? ? */
?? ??? ?buf[z] = 0;
?? ??? ?/*
?? ??? ? * Now perform the server function on
?? ??? ? * the received message
?? ??? ? */
?? ??? ?time(&td);?? ?/* Get current time */
?? ??? ?strftime(txbuf,sizeof txbuf,?? ?/* Buffer */
?? ??? ??? ??? ?buf,?? ??? ?/* Input fromate*/
?? ??? ??? ??? ?localtime(&td));/* Input time */
?? ??? ?/*
?? ??? ? * Send back the response to client:
?? ??? ? */
?? ??? ?z = write (s[0],txbuf,strlen(txbuf));
?? ??? ?if(z<0)
?? ??? ?{
?? ??? ??? ?fprintf(stderr,"%s:write(2)\n",strerror(errno));
?? ??? ??? ?exit(1);
?? ??? ?}
?? ??? ?/*
?? ??? ? * Close our end of the socket
?? ??? ? */
?? ??? ?close(s[0]);
?? ??? ?/*
?? ??? ? * Wait for the child process to exit:
?? ??? ? */
?? ??? ?waitpid(chpid,&status,0);
?? ?}
?? ?return 0;
}
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
