UNIX網絡程式設計筆記(11)—UNIX域套接字
1.簡介
Unix域協定并不是一個實際的協定族,而是在單個主機上執行客戶/伺服器通訊的一種方式,單個主機上執行通信,也就是所謂的進行間通信(IPC),是以Unix域套接字協定可以視作IPC方法之一。
Unix域提供兩類套接字:位元組流套接字(類似TCP)和資料報套接字(類似DUP)。
Unix域中用于辨別客戶和伺服器的協定位址是普通檔案系統中的路徑名(但需要和Unix域套接字關聯起來),否則無法讀寫這些檔案。回憶一蛤,IPv4的協定位址由一個32位位址和16位端口号構成,IPv6協定位址則由一個128位位址和16位端口号組成。
2.Unix域套接字位址結構
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sum_family;// AF_LOCAL或者AF_UNIX
char sun_path[];//字元串指代路徑(null終止)
};
sun_path
表示與套接字關聯的位址,以NULL結尾,如果未指定位址通則通過以空字元串作為路徑名訓示,也就是說
sun_path[0]
值為0,這個效果就好像Ipv4的
INADDR_ANY
和IPv6的
ADDR_ANY_INIT
。
3.socketpair函數
這個是UNIX域套接字特有的函數,它建立兩個素侯連接配接起來的套接字。
#include <sys/socket.h>
int socketpair(int family , int type , int protocol ,int sockfd[]);
//傳回:成功則為0,出錯則為-1
3.1參數說明
family: 為或者
AF_LOCAL
AF_UNIX
。
type:既可以是
也可以是
SOCK_STREAM
SOCK_DGRAM
。
protocol:必須為0。
sockfd[2]:新建立的兩個套接字别在sockfd[0]和sockfd[1]中傳回。
當設定type參數為
SOCK_STREAM
時,得到的結果就叫做流管道這與
pipe
建立的的普通UNIX管道類似了。差别在于流管道是全雙工的,也就是說,兩個描述符既可讀也可寫。回憶一蛤,用
pipe
建立的匿名管道,
pipefd[0]
用于讀操作,
pipefd[1]
用于寫操作。
4.UNIX域套接字程式設計
還是根據UNP1中的代碼寫的,主要工作就是把包裹函數拆開。
4.1 Unix域位元組流客戶/伺服器程式
跟TCP客戶服務程式類似,不過在
bind
步驟中,是将特定的
path
與套接字綁定。
代碼
伺服器程式
//unixdomainserv.c
#include <sys/un.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#define UNIXDOMAIN_PATH "/tmp/unix.str"
#define MAXLEN 1024
void sig_child(int);
void str_echo(int );
typedef void(*sig_handle)(int);//handle
int main(int argc ,char **argv)
{
int listenfd;
int connfd;
socklen_t clilen;
struct sockaddr_un cliaddr,servaddr;
pid_t childpid;
if((listenfd = socket(AF_LOCAL,SOCK_STREAM,))<)//socket
{
printf("socket error\r\n");
return -;
}
unlink(UNIXDOMAIN_PATH);//delete file
bzero(&servaddr,sizeof(servaddr));
// memset(&servaddr,0x00,sizeof(servaddr));
servaddr.sun_family=AF_LOCAL;
strcpy(servaddr.sun_path,UNIXDOMAIN_PATH);
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<)//bind
{
printf("bind error\r\n");
return -;
}
if(listen(listenfd,)<)//listen
{
printf("listen error\r\n");
return -;
}
sig_handle handle1 = sig_child;
signal(SIGCHLD,handle1);
while()
{
clilen = sizeof(cliaddr);
if((connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&clilen))<)
{
printf("accept error\r\n");
}
if((childpid= fork())==)//child process
{
close(listenfd);
str_echo(connfd);
return ;
}
close(connfd);//parent close connfd
}
return ;
}
void str_echo(int connfd)
{
ssize_t nread;
char readbuff[MAXLEN];
memset(readbuff,,sizeof(readbuff));
while((nread=read(connfd,readbuff,MAXLEN))>)
{
write(connfd,readbuff,strlen(readbuff));
memset(readbuff,,sizeof(readbuff));
}
}
void sig_child(int signo)
{
pid_t pid;
int stat;
#if 1
while((pid=waitpid(-,&stat,WNOHANG))>)
printf("waitpid:child terminated,pid=%d\r\n",pid);
#endif
return ;
}
用戶端程式
#include <sys/un.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#define UNIXDOMAIN_PATH "/tmp/unix.str"
#define MAXLEN 1024
void str_cli(FILE*,int);
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr;
if((sockfd = socket(AF_LOCAL,SOCK_STREAM,))<)//socket
{
printf("socket error\r\n");
return -;
}
bzero(&servaddr,sizeof(servaddr));
// memset(&servaddr,0x00,sizeof(servaddr));
servaddr.sun_family=AF_LOCAL;//or AF_UNIX
strcpy(servaddr.sun_path,UNIXDOMAIN_PATH);
if(connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<)//connect
{
printf("conncet error\r\n");
return -;
}
str_cli(stdin,sockfd);
return ;
}
void str_cli(FILE*fp,int sockfd)
{
int nread;
int nwrite;
char readbuff[MAXLEN];
while( fgets(readbuff,sizeof(readbuff),fp)!=NULL)
{
if( (nwrite= write(sockfd,readbuff,strlen(readbuff)))<)
{
printf("write error \r\n");
return ;
}
memset(readbuff,,sizeof(readbuff));
if(( nread= read(sockfd,readbuff,sizeof(readbuff)))<)
{
printf("read error \r\n");
return ;
}
fputs(readbuff,stdout);
}
}
4.2 Unix域資料報客戶/伺服器程式
與UDP程式類似,但是用戶端需要有一些改變:
當使用Unix域資料報協定時候,我們必須顯示的bind一個路徑名到我們到套接字,這樣伺服器才會有能回射應答的路徑名。在書中用了指派一個臨時路徑名,但是在編譯的時候說該函數已經不用了,取而代之的是
tmpnam
。
mkstemp
代碼
伺服器程式
#include <sys/un.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#define UNIXDOMAIN_PATH "/tmp/unix.dg"
#define MAXLEN 1024
#define MAXLINE 1024
void dg_echo(int,struct sockaddr *,socklen_t );
int main(int argc ,char **argv)
{
int sockfd;
struct sockaddr_un cliaddr,servaddr;
if((sockfd = socket(AF_LOCAL,SOCK_DGRAM,))<)//socket
{
printf("socket error\r\n");
return -;
}
unlink(UNIXDOMAIN_PATH);//delete file
bzero(&servaddr,sizeof(servaddr));
// memset(&servaddr,0x00,sizeof(servaddr));
servaddr.sun_family=AF_LOCAL;
strcpy(servaddr.sun_path,UNIXDOMAIN_PATH);
if(bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<)//bind
{
printf("bind error\r\n");
return -;
}
dg_echo(sockfd,(struct sockaddr *)&cliaddr,sizeof(cliaddr));
return ;
}
#if 1
void dg_echo(int sockfd ,struct sockaddr* pcliaddr,socklen_t clilen)
{
char buf[MAXLEN];
int n;
socklen_t len ;
while()
{
len = clilen;
if((n=recvfrom(sockfd,buf,MAXLEN,,pcliaddr,&len))<)
{
printf("recvfrom error\r\n");
return ;
}
sendto(sockfd,buf,n,,pcliaddr,len);
}
}
#endif
用戶端程式
#include <sys/un.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <stdio.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <stdlib.h>
#define UNIXDOMAIN_PATH "/tmp/unix.dg"
#define MAXLEN 1024
void dg_cli(FILE*,int,const struct sockaddr *,socklen_t);
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr;
struct sockaddr_un cliaddr;
if((sockfd = socket(AF_LOCAL,SOCK_DGRAM,))<)//socket
{
printf("socket error\r\n");
return -;
}
bzero(&cliaddr,sizeof(cliaddr));
// memset(&servaddr,0x00,sizeof(servaddr));
cliaddr.sun_family=AF_LOCAL;
char temp[]="template_XXXXXX";
int fd = mkstemp(temp);
strcpy(cliaddr.sun_path,temp);
unlink(temp);
close(fd);
if(bind(sockfd,(struct sockaddr*)&cliaddr,sizeof(cliaddr))<)//bind
{
printf("conncet error\r\n");
return -;
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sun_family=AF_LOCAL;
strcpy(servaddr.sun_path,UNIXDOMAIN_PATH);
dg_cli(stdin,sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
return ;
}
void dg_cli(FILE*fp,int sockfd,const struct sockaddr*pservaddr,socklen_t servlen)
{
int n;
char sendbuff[MAXLEN];
char recvbuff[MAXLEN+];
while(fgets(sendbuff,MAXLEN,fp)!=NULL)
{
//指定伺服器套接字結構直接sendto
sendto(sockfd,sendbuff,strlen(sendbuff),,pservaddr,servlen);
if((n=recvfrom(sockfd,recvbuff,MAXLEN,,NULL,NULL))<=)
{
printf("recvfrom error\r\n");
return ;
}
recvbuff[n]='\0';//防止越界
fputs(recvbuff,stdout);//輸出回射資料
}
}
4.3 附錄:Makefile
PROGS =unixdomainserv unixdomaincli unixdomainserv2 unixdomaincli2
CLEANFILES = core core.* *.core *.o temp.* *.out typescript* \
*.lc *.lh *.bsdi *.sparc *.uw
all :${PROGS}
CFLAGS+=-g
unixdomainserv:unixdomainserv.o
${CC} ${CFLAGS} -o [email protected] $^
unixdomaincli:unixdomaincli.o
${CC} ${CFLAGS} -o [email protected] $^
unixdomainserv2:unixdomainserv2.o
${CC} ${CFLAGS} -o [email protected] $^
unixdomaincli2:unixdomaincli2.o
${CC} ${CFLAGS} -o [email protected] $^
@rm *o
clean:
rm -f ${PROGS} ${CLEANFILES}
5.參考
1.《UNP1》