关键词总结:本地套接字
代码路径见Github 专栏代码
本地套接字概述
本地套接字是一种特殊类型的套接字,和 TCP/UDP 套接字不同。TCP/UDP 即使在本地地址通信,也要走系统网络协议栈,而本地套接字,严格意义上说提供了一种单主机跨进程间调用的手段,减少了协议栈实现的复杂度,效率比 TCP/UDP 套接字都要高许多。本地套接字是 IPC,也就是本地进程间通信的一种实现方式。
本地地址是本地套接字专属的
本地字节流套接字
本地字节流套接字服务器端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define MAXLINE 4096
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv){
if (argc != 2) {
perror("usage: unixstreamserver ");
}
int listenfd, connfd; // 监听套节字 连接套接字
socklen_t clilen; // 客户端
struct sockaddr_un cliaddr, servaddr;
// 本地套接字, 字节流
listenfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (listenfd < 0) {
perror("socket create failed");
}
// 创建了一个本地地址
char *local_path = argv[1];
unlink(local_path); // unlink 操作,以便把存在的文件删除掉
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL; // 数据类型中的 sun_family 需要填写为 AF_LOCAL
strcpy(servaddr.sun_path, local_path);
// bind 本地套接字
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
}
// listen
if (listen(listenfd, LISTENQ) < 0) {
perror("listen failed");
}
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd,(struct sockaddr *) &cliaddr, &clilen)) < 0) {
perror("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];
sprintf(send_line, "Hi, %s", buf);
int nbytes = sizeof(send_line);
// 从套接字中按照字节流的方式发送数据
if (write(connfd, send_line, nbytes) != nbytes)
perror( "write error");
}
close(listenfd);
close(connfd);
exit(0);
}
本地字节流套接字客户端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define MAXLINE 4096
int main(int argc, char **argv) {
if (argc != 2) {
perror("usage: unixstreamclient <local_path>");
}
int sockfd;
struct sockaddr_un servaddr;
// 创建了一个本地套接字
sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (sockfd < 0) {
perror( "create socket failed");
}
// 初始化目标服务器端的地址, TCP 编程中,使用的是服务器的 IP 地址和端口作为目标,在本地套接字中则使用文件路径作为目标标识
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, argv[1]); // sun_path 这个字段标识的是目标文件路径
// 发起对目标套接字的 connect 调用
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
perror("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)
perror("write error");
if (read(sockfd, recv_line, MAXLINE) == 0)
perror("server terminated prematurely");
fputs(recv_line, stdout);
}
exit(0);
}
实验
只启动客户端
由于没有启动服务器端,没有一个本地套接字在 /tmp/unixstream.sock 这个文件上监听,客户端直接报错,提示没有文件存在。
服务器端监听在无权限的文件路径上
比如 ./server.o /var/lib/unixstream.sock
我这里报的
使用root 用户启动该程序,并查看/var/lib/unixstream.sock 文件权限
root 用户启动该程序后服务器端阻塞运行中,文件大小为 0
使用 命令 sudo netstat -a -p -A unix 可以查看 server.o进程监听在 /var/lib/unixstream.sock 这个文件路径上。
服务器 - 客户端应答
让服务器和客户端都正常启动,并且客户端依次发送字符:
本地数据报套接字
本地数据报套接字服务器端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define MAXLINE 4096
#define LISTENQ 1024
#define BUFFER_SIZE 4096
int main(int argc, char **argv){
if (argc != 2) {
perror("usage: unixstreamserver ");
}
int socket_fd;
socklen_t clilen; // 客户端
struct sockaddr_un cliaddr, servaddr;
// 本地套接字, 数据报
socket_fd = socket(AF_LOCAL, SOCK_DGRAM, 0); // 不是 SOCK_STREAM
if (socket_fd < 0) {
perror("socket create failed");
}
// 创建了一个本地地址
char *local_path = argv[1];
unlink(local_path); // unlink 操作,以便把存在的文件删除掉
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL; // 数据类型中的 sun_family 需要填写为 AF_LOCAL
strcpy(servaddr.sun_path, local_path);
// bind 本地套接字
if (bind(socket_fd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
perror("bind failed");
}
clilen = sizeof(cliaddr);
char buf[BUFFER_SIZE];
while (1) {
bzero(buf, sizeof(buf));
if (recvfrom(socket_fd, buf, BUFFER_SIZE, 0, (struct sockaddr *) &cliaddr, &clilen) == 0) {
printf("client quit");
break;
}
printf("Receive: %s", buf);
char send_line[MAXLINE];
sprintf(send_line, "Hi, %s", buf);
size_t nbytes = strlen(send_line);
printf("now sending: %s \n", send_line);
if (sendto(socket_fd, send_line, nbytes, 0, (struct sockaddr *) &cliaddr, clilen) != nbytes)
perror( "write error");
}
close(socket_fd);
exit(0);
}
本地数据报套接字客户端
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#define MAXLINE 4096
int main(int argc, char **argv) {
if (argc != 2) {
perror("usage: unixdataclient <local_path>");
}
int sockfd;
struct sockaddr_un client_addr, server_addr;
sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);
if (sockfd < 0) {
perror( "create socket failed");
}
bzero(&client_addr, sizeof(client_addr)); /* bind an address for us */
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 failed");
}
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, MAXLINE);
char recv_line[MAXLINE];
// 从标准输入中读取的字符, fgets函数加上\0
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");
int n = recvfrom(sockfd, recv_line, MAXLINE, 0, NULL, NULL);
recv_line[n] = 0;
fputs(recv_line, stdout);
fputs("\n", stdout);
}
exit(0);
}
服务器端和客户端通过数据报应答的场景
客户端退出没有影响到服务器端,客户端重新启动后依旧正常应答
参考资料:
网络编程实战(极客时间)链接:
http://gk.link/a/10g9X
GitHub链接:
https://github.com/lichangke
CSDN首页:
https://me.csdn.net/leacock1991
欢迎大家来一起交流学习