本地套接字实现的是同一主机的不同进程间的通信,且建立的通信是双向的通信。socket本地通信与网络通信使用的是统一套接口,只是地址结构中的参数不同。
创建套接字需要使用 socket 系统调用,其原型如下:
int socket(int domain, int type, int protocol);
参数:
domain :指定协议族,对于本地套接字来说,其值须被置为 AF_UNIX 枚举值;
type :套接字类型,type 参数可被设置为 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字)
protocol 参数指定具体协议,protocol 字段应被设置为 0;
返回:生成的套接字描述符
套接字类型:
流式套接字(SOCK_STREAM)是一个有顺序的、可靠的双向字节流,相当于在本地进程之间建立起一条数据通道;
数据报式套接字(SOCK_DGRAM)相当于单纯的发送消息,在进程通信过程中,理论上可能会有信息丢失、复制或者不按先后次序到达的情况,但由于其在本地通信,不通过外界网络,这些情况出现的概率很小。
//创建本地socket
int server_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
1)绑定地址
通过绑定地址来指定服务器端的地址(struct sockaddr_un),本地socket的地址是本地文件的地址。
bind 绑定定函数:
int bind(int socket, const struct sockaddr *address, size_t address_len);
参数:
socket表示服务器端的套接字描述符
address 表示需要绑定的本地地址,是一个 struct sockaddr_un 类型的变量
address_len 表示该本地地址的字节长度
本地socket绑定,监听该地址的网络事件
struct sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, UNIX_DOMAIN,sizeof(server_addr.sun_path)-1);
bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
2)连接地址
注册需要连接的服务器地址信息到socket
struct sockaddr_un address;
address.sun_family = AF_UNIX;
strncpy(address.sun_path, UNIX_DOMAIN,sizeof(address.sun_path)-1);
connect(sockfd, (struct sockaddr *)&address, sizeof(address));
本地套接字的通信双方均需要具有本地地址,指定方式是使用 struct sockaddr_un 的变量sun_path。
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX ,2字节*/
char sun_path[UNIX_PATH_MAX]; /* 路径名 */
};
本地socket进程通信的关联文件命名方式有两种:
1)具体文件命名
2)抽象命名空间
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path,UNIX_DOMAIN,sizeof(server_addr.sun_path)-1);
server_len = sizeof(struct sockaddr_un);
缺点:
这个文件很容易被其他程序不经意中删除,这导致很奇怪的问题,而很难发现
这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。
后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0,代码如:
因第二种方式会对首字节置0,我们可以在命名字符串SERVER_NAME前添加一个占位符@,例如:
#define SERVER_NAME @"/tmp/socket_server"
server_addr.sun_family = AF_UNIX;
strcpy(server_addr.sun_path, SERVER_NAME);
server_addr.sun_path[0]=0;
server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);
offsetof函数在#include
正常情况下,sun_path指定了需要bind(或者send/connect等)的路径,若使用abstract_namespace,那么sun_path[0]必须为'\0'(零字符)。同时在使用这个地址时,必须在addresslen参数中指定正确的长度,这个长度是sizeof(sun_family) + 1 + strlen(path)。地址长度不包括path后面的'\0',Linux的系统调用也不会去寻找这个零字符。
当接收(accept,recvfrom等)数据时,Linux也不会在sun_path后面附上一个'\0',所以你必须根据返回的长度谨慎地处理字符串。
实质上,Linux在内存中维护了一个虚拟的"文件系统",这个文件在close之后会自动消失,并且文件系统中看不到bind的文件,用netstat -an却有记录。
若使用netstat查询端口情况,通过abstract_namespace的方式创建的unix域socket bind之后,路径前有一个@
~ 05:17:32 $ netstat -an
unix 2 [ ] DGRAM 7260 @/tmp/cli_path
unix 2 [ ] DGRAM 7259 @/tmp/svr_path
缺点:
移植性较差。
服务器:
#include
#include
#include
#include
#include
#include
//本地socket文件
#define UNIX_DOMAIN "/tmp/UNIX.domain"
//如果使用抽象文件方式,可以使用如 @"/tmp/socket_server"
int main()
{
//如果有本地socket文件,删除本地文件
unlink(UNIX_DOMAIN);
//创建本地socket
int server_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un server_addr;
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, UNIX_DOMAIN,sizeof(server_addr.sun_path)-1); //本地socket文件地址
//绑定socket到本地文件
bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
listen(server_sockfd, 5);
char ch;
int client_sockfd;
struct sockaddr_un client_addr;
socklen_t len = sizeof(client_addr);
char buffer[1024];
while(1)
{
printf("server waiting:\n");
//接收客户端连接
client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);
//读取数据
read(client_sockfd, buffer, sizeof(buffer));
printf("get char from client: %c\n", ch);
//发送数据
write(client_sockfd, buffer, sizeof(buffer));
close(client_sockfd);
}
return 0;
}
#include
#include
#include
#include
#include
#include
#define UNIX_DOMAIN "/tmp/UNIX.domain"
//或者本文件夹下的文件 "server_socket"
int main()
{
//创建本地socket
int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
struct sockaddr_un address;
address.sun_family = AF_UNIX;
strncpy(address.sun_path, UNIX_DOMAIN,sizeof(address.sun_path)-1);
//连接本地socket到本地文件(本物理服务器的)
int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));
if(result == -1)
{
perror("connect failed: ");
exit(1);
}
char buffer[1024];
//输入数据
fgets (buffer, sizeof(buffer), stdin)
//发送数据
write(sockfd, buffer,sizeof(buffer));
memset(buffer,0,sizeof(buffer));
//阻塞读取服务器返回数据
read(sockfd, buffer,sizeof(buffer));
printf("get char from server: %c\n", ch);
close(sockfd);
return 0;
}