LINUX本地套接字

服务端:

 
  1. //s_unix.c

  2. #include <stdio.h>

  3. #include <sys/types.h>

  4. #include <sys/socket.h>

  5. #include <sys/un.h>

  6. #define UNIX_DOMAIN "/tmp/UNIX.domain"

  7. int main(void)

  8. {

  9. socklen_t clt_addr_len;

  10. int listen_fd;

  11. int com_fd;

  12. int ret;

  13. int i;

  14. static char recv_buf[1024];

  15. int len;

  16. struct sockaddr_un clt_addr;

  17. struct sockaddr_un srv_addr;

  18. listen_fd=socket(PF_UNIX,SOCK_STREAM,0);

  19. if(listen_fd<0)

  20. {

  21. perror("cannot create communication socket");

  22. return 1;

  23. }

  24.  
  25. //set server addr_param

  26. srv_addr.sun_family=AF_UNIX;

  27. strncpy(srv_addr.sun_path,UNIX_DOMAIN,sizeof(srv_addr.sun_path)-1);

  28. unlink(UNIX_DOMAIN);

  29. //bind sockfd & addr

  30. ret=bind(listen_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));

  31. if(ret==-1)

  32. {

  33. perror("cannot bind server socket");

  34. close(listen_fd);

  35. unlink(UNIX_DOMAIN);

  36. return 1;

  37. }

  38. //listen sockfd

  39. ret=listen(listen_fd,1);

  40. if(ret==-1)

  41. {

  42. perror("cannot listen the client connect request");

  43. close(listen_fd);

  44. unlink(UNIX_DOMAIN);

  45. return 1;

  46. }

  47. //have connect request use accept

  48. len=sizeof(clt_addr);

  49. com_fd=accept(listen_fd,(struct sockaddr*)&clt_addr,&len);

  50. if(com_fd<0)

  51. {

  52. perror("cannot accept client connect request");

  53. close(listen_fd);

  54. unlink(UNIX_DOMAIN);

  55. return 1;

  56. }

  57. //read and printf sent client info

  58. printf("/n=====info=====/n");

  59. for(i=0;i<4;i++)

  60. {

  61. memset(recv_buf,0,1024);

  62. int num=read(com_fd,recv_buf,sizeof(recv_buf));

  63. printf("Message from client (%d)) :%s/n",num,recv_buf);

  64. }

  65. close(com_fd);

  66. close(listen_fd);

  67. unlink(UNIX_DOMAIN);

  68. return 0;

  69. }


客户端:

 

 

 
  1. //c_unix.c

  2. #include <stdio.h>

  3. #include <sys/types.h>

  4. #include <sys/socket.h>

  5. #include <sys/un.h>

  6. #define UNIX_DOMAIN "/tmp/UNIX.domain"

  7. int main(void)

  8. {

  9. int connect_fd;

  10. int ret;

  11. char snd_buf[1024];

  12. int i;

  13. static struct sockaddr_un srv_addr;

  14. //creat unix socket

  15. connect_fd=socket(PF_UNIX,SOCK_STREAM,0);

  16. if(connect_fd<0)

  17. {

  18. perror("cannot create communication socket");

  19. return 1;

  20. }

  21. srv_addr.sun_family=AF_UNIX;

  22. strcpy(srv_addr.sun_path,UNIX_DOMAIN);

  23. //connect server

  24. ret=connect(connect_fd,(struct sockaddr*)&srv_addr,sizeof(srv_addr));

  25. if(ret==-1)

  26. {

  27. perror("cannot connect to the server");

  28. close(connect_fd);

  29. return 1;

  30. }

  31. memset(snd_buf,0,1024);

  32. strcpy(snd_buf,"message from client");

  33. //send info server

  34. for(i=0;i<4;i++)

  35. write(connect_fd,snd_buf,sizeof(snd_buf));

  36. close(connect_fd);

  37. return 0;

  38. }

 

使用套接字除了可以实现网络间不同主机间的通信外,还可以实现同一主机的不同进程间的通信,且建立的通信是双向的通信。socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。

一、创建socket流程

(1)创建socket,类型为AF_LOCAL或AF_UNIX,表示用于进程通信:

创建套接字需要使用 socket 系统调用,其原型如下:


int socket(int domain, int type, int protocol);


其中,domain 参数指定协议族,对于本地套接字来说,其值须被置为 AF_UNIX 枚举值;type 参数指定套接字类型,protocol 参数指定具体协议;type 参数可被设置为 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字),protocol 字段应被设置为 0;其返回值为生成的套接字描述符。


对于本地套接字来说,流式套接字(SOCK_STREAM)是一个有顺序的、可靠的双向字节流,相当于在本地进程之间建立起一条数据通道;数据报式套接字(SOCK_DGRAM)相当于单纯的发送消息,在进程通信过程中,理论上可能会有信息丢失、复制或者不按先后次序到达的情况,但由于其在本地通信,不通过外界网络,这些情况出现的概率很小。

 

二、命名socket

SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_un 类型的变量。

struct sockaddr_un {
    sa_family_t     sun_family;     /* AF_UNIX */
    char    sun_path[UNIX_PATH_MAX];        /* 路径名 */
};

这里面有一个很关键的东西,socket进程通信命名方式有两种。一是普通的命名,socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。

另外一种命名方式是抽象命名空间,这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0,下面用代码说明:

 

第一种方式:

 
  1. //name the server socket

  2. server_addr.sun_family = AF_UNIX;

  3. strcpy(server_addr.sun_path,"/tmp/UNIX.domain");

  4. server_len = sizeof(struct sockaddr_un);

  5. client_len = server_len;




第二种方式:

 

 

#define SERVER_NAME @socket_server

 

 
  1. //name the socket

  2. server_addr.sun_family = AF_UNIX;

  3. strcpy(server_addr.sun_path, SERVER_NAME);

  4. server_addr.sun_path[0]=0;

  5. //server_len = sizeof(server_addr);

  6. server_len = strlen(SERVER_NAME) + offsetof(struct sockaddr_un, sun_path);




其中,offsetof函数在#include <stddef.h>头文件中定义。因第二种方式的首字节置0,我们可以在命名字符串SERVER_NAME前添加一个占位字符串,例如:

#define SERVER_NAME @socket_server  


前面的@符号就表示占位符,不算为实际名称。

 

提示:客户端连接服务器的时候,必须与服务端的命名方式相同,即如果服务端是普通命名方式,客户端的地址也必须是普通命名方式;如果服务端是抽象命名方式,客户端的地址也必须是抽象命名方式。

 

三、绑定

SOCK_STREAM 式本地套接字的通信双方均需要具有本地地址,其中服务器端的本地地址需要明确指定,指定方法是使用 struct sockaddr_un 类型的变量,将相应字段赋值,再将其绑定在创建的服务器套接字上,绑定要使用 bind 系统调用,其原形如下:


int bind(int socket, const struct sockaddr *address, size_t address_len);


其中 socket表示服务器端的套接字描述符,address 表示需要绑定的本地地址,是一个 struct sockaddr_un 类型的变量,address_len 表示该本地地址的字节长度。实现服务器端地址指定功能的代码如下(假设服务器端已经通过上文所述的 socket 系统调用创建了套接字,server_sockfd 为其套接字描述符):
struct sockaddr_un server_address;
server_address.sun_family = AF_UNIX;
strcpy(server_address.sun_path, "Server Socket");
bind(server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address));


客户端的本地地址不用显式指定,只需能连接到服务器端即可,因此,客户端的 struct sockaddr_un 类型变量需要根据服务器的设置情况来设置,代码如下(假设客户端已经通过上文所述的 socket 系统调用创建了套接字,client_sockfd 为其套接字描述符):
struct sockaddr_un client_address;
client_address.sun_family = AF_UNIX;
strcpy(client_address.sun_path, "Server Socket");

四、监听
服务器端套接字创建完毕并赋予本地地址值(名称,本例中为Server Socket)后,需要进行监听,等待客户端连接并处理请求,监听使用 listen 系统调用,接受客户端连接使用accept系统调用,它们的原形如下:
int listen(int socket, int backlog);
int accept(int socket, struct sockaddr *address, size_t *address_len);
其中 socket 表示服务器端的套接字描述符;backlog 表示排队连接队列的长度(若有多个客户端同时连接,则需要进行排队);address 表示当前连接客户端的本地地址,该参数为输出参数,是客户端传递过来的关于自身的信息;address_len 表示当前连接客户端本地地址的字节长度,这个参数既是输入参数,又是输出参数。实现监听、接受和处理的代码如下:
#define MAX_CONNECTION_NUMBER 10
int server_client_length, server_client_sockfd;
struct sockaddr_un server_client_address;
listen(server_sockfd, MAX_CONNECTION_NUMBER);
while(1)
{
    // ...... (some process code)
    server_client_length = sizeof(server_client_address);
    server_client_sockfd = accept(server_sockfd, (struct sockaddr*)&server_client_address, &server_client_length);
    // ...... (some process code)
}
这里使用死循环的原因是服务器是一个不断提供服务的实体,它需要不间断的进行监听、接受并处理连接,本例中,每个连接只能进行串行处理,即一个连接处理完后,才能进行后续连接的处理。如果想要多个连接并发处理,则需要创建线程,将每个连接交给相应的线程并发处理。
客户端套接字创建完毕并赋予本地地址值后,需要连接到服务器端进行通信,让服务器端为其提供处理服务。对于 SOCK_STREAM 类型的流式套接字,需要客户端与服务器之间进行连接方可使用。连接要使用 connect 系统调用,其原形为


int connect(int socket, const struct sockaddr *address, size_t address_len);


其中socket为客户端的套接字描述符,address表示当前客户端的本地地址,是一个 struct sockaddr_un 类型的变量,address_len 表示本地地址的字节长度。实现连接的代码如下:
connect(client_sockfd, (struct sockaddr*)&client_address, sizeof(client_address));
无论客户端还是服务器,都要和对方进行数据上的交互,这种交互也正是我们进程通信的主题。一个进程扮演客户端的角色,另外一个进程扮演服务器的角色,两个进程之间相互发送接收数据,这就是基于本地套接字的进程通信。发送和接收数据要使用 write 和 read 系统调用,它们的原形为:
int read(int socket, char *buffer, size_t len);
int write(int socket, char *buffer, size_t len);
其中 socket 为套接字描述符;len 为需要发送或需要接收的数据长度;对于 read 系统调用,buffer 是用来存放接收数据的缓冲区,即接收来的数据存入其中,是一个输出参数;对于 write 系统调用,buffer 用来存放需要发送出去的数据,即 buffer 内的数据被发送出去,是一个输入参数;返回值为已经发送或接收的数据长度。例如客户端要发送一个 "Hello" 字符串给服务器,则代码如下:
char buffer[10] = "Hello";
write(client_sockfd, buffer, strlen(buffer));
交互完成后,需要将连接断开以节省资源,使用close系统调用,其原形为:
int close(int socket);
不多说了,直接使用,大家一定都会,呵呵!
上面所述的每个系统调用都有 -1 返回值,在调用不成功时,它们均会返回 -1,这个特性可以使得我们用 if - else 或异常处理语句来处理错误,为我们提供了很大的方便。
SOCK_DGRAM 数据报式本地套接字的应用场合很少,因为流式套接字在本地的连接时间可以忽略,所以效率并没有提高,而且发送接收都需要携带对方的本地地址,因此很少甚至几乎不使用。
与本地套接字相对应的是网络套接字,可以用于在网络上传送数据,换言之,可实现不同机器上的进程通信过程。在 TCP/IP 协议中,IP 地址的首字节为 127 即代表本地,因此本地套接字通信可以使用 IP 地址为 127.x.x.x 的网络套接字来实现。

<think> 嗯,用户这次明确要求了解Linux系统下socket本地套接字的相关知识。从引用内容来看,用户已经掌握了一些基础概念,比如知道本地套接字是用于进程间通信的特殊文件类型,但需要更系统的技术说明。 用户可能是个开发者,正在实现本地进程通信功能,需要具体的使用指导。考虑到引用[3]详细提到了应用场景,说明用户关注实际应用价值。ta特意强调"不是网络通信",说明已经区分了网络套接字本地套接字的概念差异。 需要重点组织三部分内容:首先是本质特性(伪文件、缓冲区),这点引用[1]的说明很准确;然后是具体编程方法,引用[2]的API清单正好能用上;最后补充引用[3]提到的独特优势,特别是高效性和安全性。引用[4]的监控命令可以作为附加知识点。 注意到用户上次提问时系统要求生成相关问题,这次可以设计几个进阶方向:比如性能对比(与管道/FIFO)、安全配置(权限控制)、调试技巧等。特别要突出本地套接字区别于网络套接字的关键点——AF_UNIX地址族和文件路径绑定。 技术细节上需要强调:1) bind时用sockaddr_un结构体 2) 文件权限设置 3) 流式套接字(TCP风格)和数据报套接字(UDP风格)两种模式的选择。示例代码最好展示完整的服务端/客户端交互流程,这样用户能立刻上手实验。 </think> 以下是根据引用内容整理的Linux本地套接字(UNIX Domain Socket)核心技术详解: --- ### **一、本地套接字本质** 1. **特殊文件类型** Linux套接字封装为**伪文件**(内核缓冲区实现),通过文件描述符操作,统一了文件/套接字的接口规范[^1]。 2. **与管道的区别** | **特性** | 本地套接字 | 管道(Pipe) | |----------------|-----------------------|--------------------| | 通信范围 | 同一主机任意进程 | 父子进程/兄弟进程 | | 通信模式 | 支持字节流/数据报 | 仅字节流 | | 寻址方式 | 文件路径标识 | 无显式标识 | | 双向通信 | 支持 | 单向 | --- ### **二、核心使用流程** #### **1. 创建套接字** ```c int sockfd = socket(AF_UNIX, SOCK_STREAM, 0); // 字节流模式(TCP风格) // 或 SOCK_DGRAM 数据报模式(UDP风格) ``` **关键参数**: - `AF_UNIX`:指定本地套接字协议族 - `SOCK_STREAM`:可靠双向字节流(类似TCP) - `SOCK_DGRAM`:保留消息边界的数据报(类似UDP)[^2] #### **2. 绑定地址** ```c struct sockaddr_un addr; addr.sun_family = AF_UNIX; strncpy(addr.sun_path, "/tmp/mysocket.sock", sizeof(addr.sun_path)-1); bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); ``` **注意**: - `sun_path`需使用**绝对路径**,文件权限需开放(建议`chmod 777 /tmp/mysocket.sock`) - 绑定后生成socket文件,退出时需手动删除[^3] #### **3. 通信实现(以SOCK_STREAM为例)** **服务端**: ```c listen(sockfd, 5); // 设置监听队列长度 int client_fd = accept(sockfd, NULL, NULL); // 阻塞等待连接 read(client_fd, buffer, sizeof(buffer)); // 接收数据 ``` **客户端**: ```c connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)); // 连接服务端 write(sockfd, "Hello", 6); // 发送数据 ``` --- ### **三、技术优势分析** 1. **性能优势** - 无网络协议栈开销,传输速度比TCP/IP快**2倍以上** - 零拷贝技术支持,减少内核/用户空间数据复制[^3] 2. **安全性** - 通过文件系统权限控制访问(`chown/chmod`) - 数据不经过网络,避免窃听风险 3. **可靠性** - 内核保证数据有序到达(SOCK_STREAM模式) - 自动处理消息边界(SOCK_DGRAM模式) --- ### **四、典型应用场景** 1. **数据库连接** MySQL默认使用`/var/run/mysqld/mysqld.sock`进行本地高效通信 2. **容器间通信** Docker容器通过`/var/run/docker.sock`与守护进程交互 3. **进程监控工具** 如systemd使用`/run/systemd/private`管理服务进程 4. **X Window系统** 图形应用通过`/tmp/.X11-unix/X0`传递显示数据 --- ### **五、监控与管理** ```bash # 查看套接字统计 cat /proc/net/sockstat # 输出示例: # sockets: used 87 # UNIX: inuse 15 # 关注此项 # 列出所有打开的UNIX套接字 sudo lsof -U -c <进程名> ``` 输出字段含义: - `inuse`:当前使用中的套接字数量 - `tw`:TIME_WAIT状态套接字[^4] --- ### **六、注意事项** 1. **路径长度限制**:`sun_path`最大108字节(包括终止符) 2. **清理机制**:程序退出后需主动调用`unlink()`删除socket文件 3. **权限控制**:避免使用`/tmp`等公开目录,建议采用`/run/<app>/`专用目录 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

akai9898

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值