《網絡編程》Unix 域套接字

概述

        Unix 域套接字是一種client和server在單主機上的 IPC 方法。Unix 域套接字不運行協議處理,不需要加入或刪除網絡報頭,無需驗證和,不產生順序號,無需發送確認報文,比因特網域套接字的效率更高。Unix 域套接字提供字節流(相似於 TCP)和數據報(相似於 UDP)兩種接口,UNIX域數據報服務是可靠的,既不會丟失消息也不會傳遞出錯。UNIX域套接字是套接字和管道之間的混合物。編程


Unix 域套接字編程


地址結構:數組

struct sockaddr_un{
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};

存放在 sun_path 數組中的路徑名必須以空字符結尾。如下是把一個路徑名綁定到 Unix 域套接字上實現的程序:

/* 建立一個Unix域套接字,並bind一個路徑名 */
#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>

extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
int main(int argc, char **argv)
{
    int sockfd, size;
    socklen_t len;
    struct sockaddr_un addr1, addr2;

    if(argc != 2)
        err_quit("usage: %s <pathname>", argv[0]);

    bzero(&addr1, sizeof(addr1));
    addr1.sun_family = AF_UNIX;

    strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path)-1);

    /* 建立一個Unix域套接字 */
    if( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");

    /* 若路徑名在文件系統已存在,則bind會出錯;因此先調用unlink刪除要綁定的路徑名,防止bind出錯 */
    unlink(argv[1]);

    /* 將路徑名bind綁定到該套接字上 */
    size = offsetof(struct sockaddr_un, sun_path) + strlen(addr1.sun_path);
    if(bind(sockfd, (struct sockaddr *)&addr1, size) < 0)
        err_sys("bind error");

    /* 顯示已綁定的路徑名 */
    len = sizeof(addr2);
    getsockname(sockfd, (struct sockaddr *)&addr2, &len);
    printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);

    exit(0);
}

$ ./main /tmp/sock
bound name = /tmp/sock, returned len = 12

/*當該路徑名存在,且不使用unlink函數時,會出現下面提示*/
$ ./main /tmp/sock
bind error: Address already in use


爲了建立一對非命名的,相互鏈接的 UNXI 域套接字,用戶可以使用socketopair函數。事實上現例如如下:網絡

#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
/* 返回值:若成功則返回0,出錯則返回-1 */
/* 說明
* 參數 domain 必須是 AF_LOCAL 或 AF_UNIX,protocol 必須爲 0,type 可以是 SOCK_STREAM 或 SOCK_DGRAM,新建立的兩個套接字描寫敘述符做爲sockfd[0]和sockfd[1]返回;

Unix 域套接字函數

與因特網域套接字相比,Unix 域套接字有下面的差異:dom

  1. 由 bind 建立的路徑名默認訪問權限應爲 0777,並按當前 umask 值進行改動;
  2. 路徑名必須是一個絕對路徑名,避免使用相對路徑名。因爲它的解析依賴於調用者的當前工做文件夾,若server綁定的是一個相對路徑名,則client和server必須在一樣的文件夾才幹正常工做;
  3. 在 connect 調用中指定的路徑名必須是一個當前綁定在某個已打開的 Unix 域套接字上的路徑名,而且套接字類型必須一致;
  4. 調用 connect 鏈接一個 Unix 域套接字涉及的權限測試等價於調用 open 以僅僅寫方式訪問對應的路徑名;
  5. Unix 域字節流套接字相似於 TCP 套接字:它們都爲進程提供一個無記錄邊界的字節流接口;
  6. 在 Unix 域字節流套接字中,若 connect 調用時發現監聽套接字的隊列已滿,則立刻返回 ECONNREFUSED 錯誤。而 TCP 套接字遇到這樣的狀況,TCP 監聽套接字忽略這些到達的 SYN 鏈接請求,TCP client則會重發數次 SYN 報文段;
  7. Unix 域數據報套接字相似於 UDP 套接字:它們都提供一個保留記錄邊界的不可靠數據報;
  8. 爲一個未綁定路徑名的 Unix 套接字發送數據時,不會本身主動給該套接字綁定一個路徑名。而 UDP 套接字在給一個未綁定的 UDP 套接字發送數據時,會本身主動爲其綁定一個暫時port;

Unix 域字節流編程

server程序:socket

#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/un.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <signal.h>
#include <errno.h>

#define QLEN 1024
typedef void Sigfunc(int);

extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
extern void str_echo(int);
static Sigfunc *MySignal(int signo, Sigfunc *func);
static Sigfunc *M_signal(int signo, Sigfunc *func);
static void sig_chld(int);

int main(int argc, char **argv)
{
    int sockfd, conndfd, size;
    socklen_t len;
    pid_t childpid;
    struct sockaddr_un cliaddr, servaddr;

    if(argc != 2)
        err_quit("usage: %s <pathname>", argv[0]);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_UNIX;

    strcpy(servaddr.sun_path, argv[1]);

    /* 建立一個Unix域套接字 */
    if( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");

    /* 若路徑名在文件系統已存在,則bind會出錯;因此先調用unlink刪除要綁定的路徑名,防止bind出錯 */
    unlink(argv[1]);

    /* 將路徑名bind綁定到該套接字上 */
    size = offsetof(struct sockaddr_un, sun_path) + strlen(servaddr.sun_path);
    if(bind(sockfd, (struct sockaddr *)&servaddr, size) < 0)
        err_sys("bind error");

    /* 監聽套接字 */
    if(listen(sockfd, QLEN) < 0)
    {
        close(sockfd);
        err_sys("listen error");
    }

    /* 信號處理 */
    MySignal(SIGCHLD, sig_chld);

    for( ; ;)
    {
        len = sizeof(cliaddr);
        if( (conndfd = accept(sockfd, (struct sockaddr *)&cliaddr, &len)) < 0)
        {
            if(errno == EINTR)
                continue;
            else
                err_sys("accept error");
        }
    }

    if( (childpid = fork()) == 0)
    {
        close(sockfd);
        str_echo(conndfd);
        exit(0);
    }
    close(conndfd);
}

void sig_chld(int signo)
{
    pid_t pid;
    int stat;
    while( (pid = waitpid(-1, &stat, WNOHANG)) > 0)
        printf("child %d terminated\n", pid);
    return;
}
static Sigfunc *MySignal(int signo, Sigfunc *func)
{
    Sigfunc *sigfunc;
    if( (sigfunc = M_signal(signo, func)) == SIG_ERR)
        err_sys("signal error");
    return (sigfunc);
}

static Sigfunc *M_signal(int signo, Sigfunc *func)
{
    struct sigaction act, oact;

    /* 設置信號處理函數 */
    act.sa_handler = func;
    /* 初始化信號集 */
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    if(signo == SIGALRM)
    {/* 如果SIGALRM信號,則系統不會本身主動從新啓動 */
#ifdef SA_INTERRUPT
        act.sa_flags |= SA_INTERRUPT;
#endif
    }
    else
    {/* 其他信號設置爲系統會本身主動從新啓動 */
#ifdef SA_RESTART
        act.sa_flags |= SA_RESTART;
#endif
    }
    /* 調用 sigaction 函數 */
    if(sigaction(signo, &act, &oact) < 0)
        return(SIG_ERR);
    return(oact.sa_handler);
}

client程序:

#include <sys/socket.h>
#include <sys/un.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>


extern void err_sys(const char *, ...);
extern void err_quit(const char *, ...);
extern void str_cli(FILE *, int);

int
main(int argc, char **argv)
{
	int					sockfd;
	struct sockaddr_un	servaddr;

    if(argc != 2)
        err_quit("usage: %s <pathname>", argv[0]);
	if( (sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
        err_sys("socket error");

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_UNIX;
	strcpy(servaddr.sun_path, argv[1]);

    int err;
	err = connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
	if( err < 0)
        err_sys("connect error");

	str_cli(stdin, sockfd);		/* do it all */

	exit(0);
}



參考資料:

《Unix 網絡編程》函數

相關文章
相關標籤/搜索