UNIX Domain Socket使用
一、Socket概述
Socket最初用在基於TCP/IP網路間程序通訊中,以客戶端/伺服器模式進行通訊。
實現非同步操作,共享資源集中處理,提高客戶端響應能力。
Tcp通訊基本流程:
伺服器端 客戶端
1.建立socket 1.建立socket
2.bind()
3.listen()
4.accecp()
----等待客戶端連線---- 2.connect()
5.讀資料(recv) 3.寫資料(send)
6.寫資料(send) 4.讀資料(recv)
7.關閉socket(closesocket()) 5.關閉socket(closesocket())
Unix domain socket 或者 IPC socket是一種終端,可以使同一臺作業系統上的兩個或多個程序進行資料通訊。與管道相比,Unix domain sockets 既可以使用位元組流,又可以使用資料佇列,而管道通訊則只能使用位元組流。Unix domain sockets的介面和Internet socket很像,但它不使用網路底層協議來通訊。Unix domain socket 的功能是POSIX作業系統裡的一種元件。
Unix domain sockets 使用系統檔案的地址來作為自己的身份。它可以被系統程序引用。所以兩個程序可以同時開啟一個Unix domain sockets來進行通訊。不過這種通訊方式是發生在系統核心裡而不會在網路裡傳播。
UNIX Domain Socket是在socket架構上發展起來的用於同一臺主機的程序間通訊(IPC),它不需要經過網路協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層資料從一個程序拷貝到另一個程序。UNIX Domain Socket有SOCK_DGRAM或SOCK_STREAM兩種工作模式,類似於UDP和TCP,但是面向訊息的UNIX Domain Socket也是可靠的,訊息既不會丟失也不會順序錯亂。UNIX Domain Socket可用於兩個沒有親緣關係的程序,是全雙工的,是目前使用最廣泛的IPC機制,比如X Window伺服器和GUI程式之間就是通過UNIX Domain Socket通訊的。
因為應用於IPC,所以UNIXDomain socket不需要IP和埠,取而代之的是檔案路徑來表示“網路地址”,這點體現在下面兩個方面:
1. 地址格式不同,UNIXDomain socket用結構體sockaddr_un表示,是一個socket型別的檔案在檔案系統中的路徑,這個socket檔案由bind()呼叫建立,如果呼叫bind()時該檔案已存在,則bind()錯誤返回。 2. UNIX Domain Socket客戶端一般要顯式呼叫bind函式,而不象網路socket一樣依賴系統自動分配的地址。客戶端bind的socket檔名可以包含客戶端的pid,這樣伺服器就可以區分不同的客戶端。三、相關API
int socket(int domain,
int type, int protocol)
domain:說明我們網路程式所在的主機採用的通訊協族(AF_UNIX和AF_INET等).
AF_UNIX只能夠用於單一的Unix系統程序間通訊,而AF_INET是針對 Internet的,因而可以允許在遠端主機之間通訊
type:我們網路程式所採用的通訊協議(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明我們用的是TCP協議,這樣會提供按順序的,可靠,雙向,面向連線的 位元流. SOCK_DGRAM 表明我們用的是UDP協議,這樣只會提供定長的,不可靠,無連線的通訊.
protocol:由於我們指定了type,所以這個地方我們一般只要用0來代替就可以了
socket為網路通訊做基本的準備.成功時返回檔案描述符,失敗時返回-1,看errno可知道出錯的詳細情況
int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen)
sockfd:是由socket呼叫返回的檔案描述符.
addrlen:是sockaddr結構的長度.
my_addr:是一個指向sockaddr的指標. 在中有 sockaddr的定義
struct sockaddr{
unisgned short as_family;
char sa_data[14];
};
int listen(int sockfd,int backlog)
sockfd:是bind後的檔案描述符.
backlog:設定請求排隊的最大長度.當有多個客戶端程式和服務端相連時, 使用這個表示可以介紹的排隊長度. listen函式將bind的檔案描述符變為監聽套接字.返回的情況和bind一樣.
int accept(int sockfd, struct sockaddr *addr,int *addrlen)
sockfd:是listen後的檔案描述符.
addr,addrlen是用來給客戶端的程式填寫的,伺服器端只要傳遞指標就可以了. bind,listen和accept是伺服器端用的函式,accept呼叫時,伺服器端的程式會一直阻塞到有一個客戶程式發出了連線. accept成功時返回最後的伺服器端的檔案描述符,這個時候伺服器端可以向該描述符寫資訊了. 失敗時返回-1
過程:有人從很遠的地方通過一個你在偵聽(listen())的埠連線(connect())到你的機器。它的連線將加入到等待接受(accept())的佇列中。你呼叫accept()告訴它你有空閒的連線。它將返回一個新的套接字檔案描述符!這樣你就有兩個套接字了,原來的一個還在偵聽你的那個埠,新的在準備傳送(send())和接收(recv())資料。這就是Linux Accept函式的過程!
Ps:Linux Accept函式注意事項,在系統呼叫send()和recv()中你應該使用新的套接字描述符new_fd。如果你只想讓一個連線進來,那麼你可以使用close()去關閉原來的檔案描述符sockfd來避免同一個埠更多的連線。
int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
sockfd:socket返回的檔案描述符.
serv_addr:儲存了伺服器端的連線資訊.其中sin_add是服務端的地址
addrlen:serv_addr的長度
connect函式是客戶端用來同服務端連線的.成功時返回0,sockfd是同服務端通訊的檔案描述符失敗時返回-1
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags)
recv 和send的前3個引數等同於read和write。
flags引數值為0或:
flags | 說明 | recv | send |
MSG_DONTROUTE | 繞過路由表查詢 | • | |
MSG_DONTWAIT | 僅本操作非阻塞 | • | • |
MSG_OOB | 傳送或接收帶外資料 | • | • |
MSG_PEEK | 窺看外來訊息 | • | |
MSG_WAITALL | 等待所有資料 | • |
1. send解析
sockfd:指定傳送端套接字描述符。
buff: 存放要傳送資料的緩衝區
nbytes: 實際要改善的資料的位元組數
flags: 一般設定為0
1) send先比較傳送資料的長度nbytes和套接字sockfd的傳送緩衝區的長度,如果nbytes > 套接字sockfd的傳送緩衝區的長度, 該函式返回SOCKET_ERROR;
2) 如果nbtyes <= 套接字sockfd的傳送緩衝區的長度,那麼send先檢查協議是否正在傳送sockfd的傳送緩衝區中的資料,如果是就等待協議把資料傳送完,如果協議還沒有開始傳送sockfd的傳送緩衝區中的資料或者sockfd的傳送緩衝區中沒有資料,那麼send就比較sockfd的傳送緩衝區的剩餘空間和nbytes
3) 如果 nbytes > 套接字sockfd的傳送緩衝區剩餘空間的長度,send就一起等待協議把套接字sockfd的傳送緩衝區中的資料傳送完
4) 如果 nbytes < 套接字sockfd的傳送緩衝區剩餘空間大小,send就僅僅把buf中的資料copy到剩餘空間裡(注意並不是send把套接字sockfd的傳送緩衝區中的資料傳到連線的另一端的,而是協議傳送的,send僅僅是把buf中的資料copy到套接字sockfd的傳送緩衝區的剩餘空間裡)。
5) 如果send函式copy成功,就返回實際copy的位元組數,如果send在copy資料時出現錯誤,那麼send就返回SOCKET_ERROR; 如果在等待協議傳送資料時網路斷開,send函式也返回SOCKET_ERROR。
6) send函式把buff中的資料成功copy到sockfd的改善緩衝區的剩餘空間後它就返回了,但是此時這些資料並不一定馬上被傳到連線的另一端。如果協議在後續的傳送過程中出現網路錯誤的話,那麼下一個socket函式就會返回SOCKET_ERROR。(每一個除send的socket函式在執行的最開始總要先等待套接字的傳送緩衝區中的資料被協議傳遞完畢才能繼續,如果在等待時出現網路錯誤那麼該socket函式就返回SOCKET_ERROR)
7) 在unix系統下,如果send在等待協議傳送資料時網路斷開,呼叫send的程序會接收到一個SIGPIPE訊號,程序對該訊號的處理是程序終止。
2.recv函式
sockfd: 接收端套接字描述符
buff: 用來存放recv函式接收到的資料的緩衝區
nbytes: 指明buff的長度
flags: 一般置為0
1) recv先等待s的傳送緩衝區的資料被協議傳送完畢,如果協議在傳送sock的傳送緩衝區中的資料時出現網路錯誤,那麼recv函式返回SOCKET_ERROR
2) 如果套接字sockfd的傳送緩衝區中沒有資料或者資料被協議成功傳送完畢後,recv先檢查套接字sockfd的接收緩衝區,如果sockfd的接收緩衝區中沒有資料或者協議正在接收資料,那麼recv就一起等待,直到把資料接收完畢。當協議把資料接收完畢,recv函式就把s的接收緩衝區中的資料copy到buff中(注意協議接收到的資料可能大於buff的長度,所以在這種情況下要呼叫幾次recv函式才能把sockfd的接收緩衝區中的資料copy完。recv函式僅僅是copy資料,真正的接收資料是協議來完成的)
3) recv函式返回其實際copy的位元組數,如果recv在copy時出錯,那麼它返回SOCKET_ERROR。如果recv函式在等待協議接收資料時網路中斷了,那麼它返回0。
4) 在unix系統下,如果recv函式在等待協議接收資料時網路斷開了,那麼呼叫 recv的程序會接收到一個SIGPIPE訊號,程序對該訊號的預設處理是程序終止。
ps:讀資料的時候需要考慮的是當recv()返回的大小如果等於請求的大小,那麼很有可能是緩衝區還有資料未讀完,也意味著該次事件還沒有處理完,所以還需要再次讀取:
[cpp] view plain copy print?- while(rs)
- {
- buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
- if(buflen < 0)
- {
- // 由於是非阻塞的模式,所以當errno為EAGAIN時,表示當前緩衝區已無資料可讀
- // 在這裡就當作是該次事件已處理
- if(errno == EAGAIN)
- break;
- else
- return;
- } else if(buflen == 0)
while(rs)
{
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
if(buflen < 0)
{
// 由於是非阻塞的模式,所以當errno為EAGAIN時,表示當前緩衝區已無資料可讀
// 在這裡就當作是該次事件已處理
if(errno == EAGAIN)
break;
else
return;
} else if(buflen == 0)
[cpp]
view plain
copy
print?
- {
- // 這裡表示對端的socket已正常關閉.
- }
- if(buflen != sizeof(buf))
- rs = 0;
- else
- rs = 1;// 需要再次讀取
{
// 這裡表示對端的socket已正常關閉.
}
if(buflen != sizeof(buf))
rs = 0;
else
rs = 1;// 需要再次讀取
}
四、example
服務端:
[cpp] view plain copy print?- #include <stdio.h>
- #include <sys/stat.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <errno.h>
- #include <stddef.h>
- #include <string.h>
- // the max connection number of the server
- #define MAX_CONNECTION_NUMBER 5
- /* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */
- int unix_socket_listen(const char *servername)
- {
- int fd;
- struct sockaddr_un un;
- if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
- {
- return(-1);
- }
- int len, rval;
- unlink(servername); /* in case it already exists */
- memset(&un, 0, sizeof(un));
- un.sun_family = AF_UNIX;
- strcpy(un.sun_path, servername);
- len = offsetof(struct sockaddr_un, sun_path) + strlen(servername);
- /* bind the name to the descriptor */
- if (bind(fd, (struct sockaddr *)&un, len) < 0)
- {
- rval = -2;
- }
- else
- {
- if (listen(fd, MAX_CONNECTION_NUMBER) < 0)
- {
- rval = -3;
- }
- else
- {
- return fd;
- }
- }
- int err;
- err = errno;
- close(fd);
- errno = err;
- return rval;
- }
- int unix_socket_accept(int listenfd, uid_t *uidptr)
- {
- int clifd, len, rval;
- time_t staletime;
- struct sockaddr_un un;
- struct stat statbuf;
- len = sizeof(un);
- if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
- {
- return(-1);
- }
- /* obtain the client's uid from its calling address */
- len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
- un.sun_path[len] = 0; /* null terminate */
- if (stat(un.sun_path, &statbuf) < 0)
- {
- rval = -2;
- }
- else
- {
- if (S_ISSOCK(statbuf.st_mode) )
- {
- if (uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */
- unlink(un.sun_path); /* we're done with pathname now */
- return clifd;
- }
- else
- {
- rval = -3; /* not a socket */
- }
- }
- int err;
- err = errno;
- close(clifd);
- errno = err;
- return(rval);
- }
- void unix_socket_close(int fd)
- {
- close(fd);
- }
- int main(void)
- {
- int listenfd,connfd;
- listenfd = unix_socket_listen("foo.sock");
- if(listenfd<0)
- {
- printf("Error[%d] when listening...\n",errno);
- return 0;
- }
- printf("Finished listening...\n",errno);
- uid_t uid;
- connfd = unix_socket_accept(listenfd, &uid);
- unix_socket_close(listenfd);
- if(connfd<0)
- {
- printf("Error[%d] when accepting...\n",errno);
- return 0;
- }
- printf("Begin to recv/send...\n");
- int i,n,size, rs;
- char rvbuf[2048];
- for(i=0;i<2;i++)
- {
- //===========接收==============
- do
- {
- size = recv(connfd, rvbuf, 804, 0);
- if(size>=0)
- {
- // rvbuf[size]='\0';
- printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]);
- }
- if(size==-1)
- {
- printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno));
- break;
- }
- if(size != sizeof(rvbuf))
- rs = 1;// 需要再次讀取
- else
- rs = 0;
- }while (rs);
- /*
- //===========傳送==============
- memset(rvbuf, 'c', 2048);
- size = send(connfd, rvbuf, 2048, 0);
- if(size>=0)
- {
- printf("Data[%d] Sended.\n",size);
- }
- if(size==-1)
- {
- printf("Error[%d] when Sending Data.\n",errno);
- break;
- }
- */
- sleep(30);
- }
- unix_socket_close(connfd);
- printf("Server exited.\n");
- }
#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
// the max connection number of the server
#define MAX_CONNECTION_NUMBER 5
/* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */
int unix_socket_listen(const char *servername)
{
int fd;
struct sockaddr_un un;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
{
return(-1);
}
int len, rval;
unlink(servername); /* in case it already exists */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, servername);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servername);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0)
{
rval = -2;
}
else
{
if (listen(fd, MAX_CONNECTION_NUMBER) < 0)
{
rval = -3;
}
else
{
return fd;
}
}
int err;
err = errno;
close(fd);
errno = err;
return rval;
}
int unix_socket_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)
{
return(-1);
}
/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
un.sun_path[len] = 0; /* null terminate */
if (stat(un.sun_path, &statbuf) < 0)
{
rval = -2;
}
else
{
if (S_ISSOCK(statbuf.st_mode) )
{
if (uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */
unlink(un.sun_path); /* we're done with pathname now */
return clifd;
}
else
{
rval = -3; /* not a socket */
}
}
int err;
err = errno;
close(clifd);
errno = err;
return(rval);
}
void unix_socket_close(int fd)
{
close(fd);
}
int main(void)
{
int listenfd,connfd;
listenfd = unix_socket_listen("foo.sock");
if(listenfd<0)
{
printf("Error[%d] when listening...\n",errno);
return 0;
}
printf("Finished listening...\n",errno);
uid_t uid;
connfd = unix_socket_accept(listenfd, &uid);
unix_socket_close(listenfd);
if(connfd<0)
{
printf("Error[%d] when accepting...\n",errno);
return 0;
}
printf("Begin to recv/send...\n");
int i,n,size, rs;
char rvbuf[2048];
for(i=0;i<2;i++)
{
//===========接收==============
do
{
size = recv(connfd, rvbuf, 804, 0);
if(size>=0)
{
// rvbuf[size]='\0';
printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]);
}
if(size==-1)
{
printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno));
break;
}
if(size != sizeof(rvbuf))
rs = 1;// 需要再次讀取
else
rs = 0;
}while (rs);
/*
//===========傳送==============
memset(rvbuf, 'c', 2048);
size = send(connfd, rvbuf, 2048, 0);
if(size>=0)
{
printf("Data[%d] Sended.\n",size);
}
if(size==-1)
{
printf("Error[%d] when Sending Data.\n",errno);
break;
}
*/
sleep(30);
}
unix_socket_close(connfd);
printf("Server exited.\n");
}
客戶端: [cpp] view plain copy print?
- #include <stdio.h>
- #include <stddef.h>
- #include <sys/stat.h>
- #include <sys/socket.h>
- #include <sys/un.h>
- #include <errno.h>
- #include <string.h>
- /* Create a client endpoint and connect to a server. Returns fd if all OK, <0 on error. */
- int unix_socket_conn(const char *servername)
- {
- int fd;
- if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) /* create a UNIX domain stream socket */
- {
- return(-1);
- }
- int len, rval;
- struct sockaddr_un un;
- memset(&un, 0, sizeof(un)); /* fill socket address structure with our address */
- un.sun_family = AF_UNIX;
- sprintf(un.sun_path, "scktmp%05d", getpid());
- len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
- unlink(un.sun_path); /* in case it already exists */
- if (bind(fd, (struct sockaddr *)&un, len) < 0)
- {
- rval= -2;
- }
- else
- {
- /* fill socket address structure with server's address */
- memset(&un, 0, sizeof(un));
- un.sun_family = AF_UNIX;
- strcpy(un.sun_path, servername);
- len = offsetof(struct sockaddr_un, sun_path) + strlen(servername);
- if (connect(fd, (struct sockaddr *)&un, len) < 0)
- {
- rval= -4;
- }
- else
- {
- return (fd);
- }
- }
- int err;
- err = errno;
- close(fd);
- errno = err;
- return rval;
- }
- void unix_socket_close(int fd)
- {
- close(fd);
- }
- int main(void)
- {
- srand((int)time(0));
- int connfd;
- connfd = unix_socket_conn("foo.sock");
- if(connfd<0)
- {
- printf("Error[%d] when connecting...",errno);
- return 0;
- }
- printf("Begin to recv/send...\n");
- int i,n,size;
- char rvbuf[4096];
- for(i=0;i<10;i++)
- {
- /*
- //=========接收=====================
- size = recv(connfd, rvbuf, 800, 0); //MSG_DONTWAIT
- if(size>=0)
- {
- printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]);
- }
- if(size==-1)
- {
- printf("Error[%d] when recieving Data.\n",errno);
- break;
- }
- if(size < 800) break;
- */
- //=========傳送======================
- memset(rvbuf,'a',2048);
- rvbuf[2047]='b';
- size = send(connfd, rvbuf, 2048, 0);
- if(size>=0)
- {
- printf("Data[%d] Sended:%c.\n",size,rvbuf[0]);
- }
- if(size==-1)
- {
- printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno));
- break;
- }
- sleep(1);
- }
- unix_socket_close(connfd);
- printf("Client exited.\n");
- }