一、介绍
本地套接字是IPC, 即本地进程间通信的一种实现方式。出来本地套接字以外,其他技术,如管道、共享信息队列等也是进程间通信的常用方法。但因为本地套接字开发便捷,接受度高,所以普遍适用于同一台主机上进程间通信的各种场景。
利用本地套接字可完成可靠字节流和数据报两种协议。
PS:
可通过netstat命令查看Linux系统内的本地套接字状况。
查看所使用的本地套接字描述符:
套接字是一种特殊类型的套接字,与TCP/UDP套接字不同。TCP/UDP即使在本地地址通信,也要走系统网络协议栈,而本地套接字,严格意义上来说提供了一种单主机跨进程间调用的手段,减少 协议栈实现的复杂度,效率比TCP/UDP套接字高很多。类似的IPC机制还有UNIX管道、共享内存和RPC调用等。
本地地址就是本地套接字专属的。
二、本地字节流套接字
服务端:
//
// Created by shengym on 2019-07-14.
//
#include "lib/common.h"
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamserver <local_path>");
}
int listenfd, connfd;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
error(1, errno, "socket create failed");
}
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, local_path);
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "bind failed");
}
if (listen(listenfd, LISTENQ) < 0) {
error(1, errno, "listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &clilen)) < 0) {
if (errno == EINTR)
error(1, errno, "accept failed");
else
error(1, errno, "accept failed");
}
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (read(connfd, buf, BUFFER_SIZE) == 0) {
printf("client quit");
break;
}
printf("Receive: %s", buf);
char send_line[MAXLINE];
bzero(send_line, MAXLINE);
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
if (write(connfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
}
close(listenfd);
close(connfd);
exit(0);
}
客户端:
//
// Created by shengym on 2019-07-15.
//
#include "lib/common.h"
int main(int argc, char **argv) {
if (argc != 2) {
error(1, 0, "usage: unixstreamclient <local_path>");
}
int sockfd;
struct sockaddr_un servaddr;
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
error(1, errno, "create socket failed");
}
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, argv[1]);
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
error(1, errno, "connect failed");
}
char send_line[MAXLINE];
bzero(send_line, MAXLINE);
char recv_line[MAXLINE];
while (fgets(send_line, MAXLINE, stdin) != NULL) {
int nbytes = sizeof(send_line);
if (write(sockfd, send_line, nbytes) != nbytes)
error(1, errno, "write error");
if (read(sockfd, recv_line, MAXLINE) == 0)
error(1, errno, "server terminated prematurely");
fputs(recv_line, stdout);
}
exit(0);
}
这里创建的套接字类型,注意是 AF_LOCAL,并且使用字节流格式。
关于本地文件路径,需要明确一点,它必须是“绝对路径”,这样的话,编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”,为了保持同样的目的,这个程序的启动路径就必须固定,这样一来,对程序的管理反而是一个很大的负担。
另外还要明确一点,这个本地文件,必须是一个“文件”,不能是一个“目录”。如果文件不存在,后面 bind 操作时会自动创建这个文件。
运行结果:
服务端:
客户端:
三:本地数据报套接字
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define BUFFER_SIZE 4096
#define MAX_LINE 4096
int main(int argc,char* argv[])
{
if(argc != 2)
{
perror("usage: unixdataserver <local path>");
return -1;
}
int socket_fd;
socket_fd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if(socket_fd < 0)
{
perror("socket create failed");
return -1;
}
struct sockaddr_un servaddr;
char *local_path = argv[1];
unlink(local_path);
bzero(&servaddr,sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path,local_path);
if(bind(socket_fd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0)
{
perror("bind faild\n");
return -1;
}
char buf[BUFFER_SIZE];
struct sockaddr_un client_addr;
socklen_t client_len = sizeof(client_addr);
while(1)
{
bzero(buf,sizeof(buf));
if(recvfrom(socket_fd, buf, BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &client_len) == 0)
{
printf("client quit\n");
break;
}
printf("Receive: %s \n",buf);
char send_line[MAX_LINE];
bzero(send_line,MAX_LINE);
sprintf(send_line,"Hi, %s",buf);
size_t nybtes = strlen(send_line);
printf("now sending :%s \n",send_line);
if(sendto(socket_fd,send_line,nybtes, 0, (struct sockaddr *)&client_addr, client_len) != nybtes)
{
perror("sendto error\n");
break;
}
}
close(socket_fd);
exit(0);
}
这里创建的套接字类型,注意是 AF_LOCAL,协议类型为 SOCK_DGRAM。
收发数据recvfrom、sendto等都与UDP网络程序一致。
客户端:
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#define MAXLINE 4096
int main(int argc, char *argv[])
{
if (argc != 2)
{
perror("usage: unixdataclient <local path>");
return -1;
}
int sockfd;
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0 );
struct sockaddr_un client_addr, server_addr;
bzero(&client_addr,sizeof(client_addr));
client_addr.sun_family = AF_LOCAL;
strcpy(client_addr.sun_path, tmpnam(NULL));
if(bind(sockfd, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0)
{
perror("bind error\n");
return -1;
}
bzero(&server_addr,sizeof(server_addr));
server_addr.sun_family = AF_LOCAL;
strcpy(server_addr.sun_path, argv[1]);
char send_line[MAXLINE];
bzero(send_line, sizeof(send_line));
char recv_line[MAXLINE];
while (fgets(send_line,MAXLINE,stdin) != NULL)
{
int i = strlen(send_line);
if(send_line[i - 1] == '\n')
{
send_line[i - 1] = 0;
}
size_t nbytes = strlen(send_line);
printf("now sending %s \n",send_line);
if(sendto(sockfd, send_line, nbytes, 0, (struct sockaddr *)&server_addr,sizeof(server_addr)) != nbytes)
{
perror("sendto error\n");
break;
}
int n = recvfrom(sockfd, recv_line, MAXLINE, 0, NULL, NULL);
recv_line[n] = 0;
fputs(recv_line, stdout);
fputs("\n",stdout);
}
exit(0);
}
执行结果:
【记】关于本地文件路径,需要明确一点,它必须是“绝对路径”,这样的话,编写好的程序可以在任何目录里被启动和管理。如果是“相对路径”,为了保持同样的目的,这个程序的启动路径就必须固定,这样一来,对程序的管理反而是一个很大的负担。
附:
Unix Domain Socket 使用
C语言tmpnam()函数
详解struct sockaddr
unlink