UNIX域套接字
socket通信流程
udp
服务端工作流程和示例
服务端工作流程
- 创建服务端的socket。
- 把服务端用于通信的地址绑定到本地socket套接字上。
- 与客户端通信,接收客户端发过来的报文后处理。
- 不断重复recvfrom()/sendto(),直到客户端断开连接。
- 关闭socket,释放资源。
服务端示例
/*
* function: 演示本地unix域套接字用于进程间通信(udp协议)
* 此进程为服务端
*
* 2020-12-05
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
int main(int argc, char *argv[])
{
int sockfd = 0;
// 第一步: 创建unix域udp协议socket套接字
if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
{
perror("socket faild");
exit(1);
}
unlink("udpserver.sock"); // 删除文件udpserver.sock,防止文件被占用
// 注意: 不要删除正在使用的socket
struct sockaddr_un serveraddr; // 服务端的地址
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, "udpserver.sock");
// 第二步: 把服务端用于通信的地址绑定到本地socket套接字上。
if (bind(sockfd, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) == -1)
{
perror("bind faild");
close(sockfd);
exit(1);
}
struct sockaddr_un clientaddr; // 用来存放客户端的地址
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t client_addr_len = sizeof(clientaddr); // struct sockaddr_un 的大小
char buf[BUFSIZ] = {0};
// 第三步: 与客户端通信,接收客户端发过来的报文后处理。
// 第四步: 不断重复recvfrom()/sendto(),直到客户端断开连接。
while (1)
{
// 注意: recvfrom最后一个参数表示clientaddr可接收多大的数据,应为clientaddr的大小,即sizeof(clientaddr), 而不是0.
size_t n = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&clientaddr, &client_addr_len);
if (n == -1)
{
perror("recvfrom faild");
unlink("udpserver.sock");
close(sockfd);
exit(1);
}
printf("receive: %s\n", buf);
n = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&clientaddr, client_addr_len);
if (n == -1)
{
perror("sendto faild");
unlink("udpserver.sock");
close(sockfd);
exit(1);
}
printf("send: %s\n", buf);
}
unlink("udpserver.sock"); // 删除文件udpserver.sock
// 第五步: 关闭socket,释放资源
close(sockfd);
return 0;
}
客户端工作流程和示例
客户端工作流程
- 创建客户端的socket。
- 把客户端用于通信的地址绑定到本地socket套接字上。
- 与服务端通信,将报文发送给服务端。
- 不断重复sendto()/recvfrom(),直到数据发送完毕。
- 关闭socket,释放资源。
客户端示例
/*
* function: 演示本地unix域套接字用于进程间通信(udp协议)
* 此进程为客户端
*
* 2020-12-05
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
int main(int argc, char *argv[])
{
int sockfd = 0;
// 第一步:创建unix域udp协议socket套接字
if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
{
perror("socket faild");
exit(1);
}
unlink("udpclient.sock"); // 删除文件udpclient.sock,防止文件被占用
// 注意: 不要删除正在使用的socket
struct sockaddr_un clientaddr; // 客户端的地址
clientaddr.sun_family = AF_UNIX;
strcpy(clientaddr.sun_path, "udpclient.sock");
// 第二步: 把客户端用于通信的地址绑定到本地socket套接字上
// 注意: 如果不进行绑定,服务端的recvfrom函数将不能收到客户端的地址
if (bind(sockfd, (struct sockaddr *)&clientaddr, (socklen_t)sizeof(clientaddr)) == -1)
{
perror("bind faild");
close(sockfd);
exit(1);
}
struct sockaddr_un serveraddr; // 服务端的地址
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, "udpserver.sock");
char buf[BUFSIZ] = {0};
char sendbuf[] = "hello world";
// 第三步: 与服务端通信,将报文发送给服务端
// 第四步: 不断重复sendto()/recvfrom(),直到数据发送完毕。
while (1)
{
// 发送报文
// 注意: sendbuf[], 内容为字符串, 因此才使用strlen(sendbuf), strlen()函数需要谨慎使用。
size_t n = sendto(sockfd, sendbuf, strlen(sendbuf), 0, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr));
if (n == -1)
{
perror("sendto faild");
unlink("udpclient.sock");
close(sockfd);
exit(1);
}
printf("send: %s\n", sendbuf);
// 接收报文
n = recvfrom(sockfd, buf, sizeof(buf), 0, 0, 0);
if (n == -1)
{
perror("sendto faild");
unlink("udpclient.sock");
close(sockfd);
exit(1);
}
printf("receive: %s\n", buf);
sleep(2);
}
unlink("udpclient.sock"); // 删除文件udpclient.sock
// 第五步: 关闭socket,释放资源
close(sockfd);
return 0;
}
tcp
服务端工作流程和示例
服务端工作流程
- 创建服务端的socket。
- 把服务端用于通信的地址和端口绑定到socket上。
- 把socket设置为监听模式。
- 接受客户端的连接。
- 与客户端通信,接收客户端发过来的报文后,回复处理结果。
- 不断的重复第5步,直到客户端断开连接。
- 关闭socket,释放资源。
服务端示例
/*
* function: 演示本地unix域套接字用于进程间通信(tcp协议)
* 此进程为服务端
*
* 2020-12-07
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
int main(int argc, char *argv[])
{
int serverFd = 0;
// 第一步: 创建unix域tcp协议socket套接字
if ((serverFd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
{
perror("socket faild");
exit(1);
}
unlink("tcpserver"); // 删除tcpserver,防止文件被占用
// 注意: 不要删除正在使用的socket
struct sockaddr_un serveraddr; // 服务端的地址
memset(&serveraddr, 0, sizeof(serveraddr));
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, "tcpserver");
// 第二步: 指定用于通信的地址
if (bind(serverFd, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) == -1)
{
perror("bind faild");
exit(1);
}
// 第三步: 把socket设置为监听
if (listen(serverFd, 5) == -1)
{
perror("listen faild");
unlink("tcpserver");
close(serverFd);
exit(1);
}
int clientFd = 0;
struct sockaddr_un clientaddr; // 客户端的地址信息
memset(&clientaddr, 0, sizeof(clientaddr));
socklen_t socklen = sizeof(clientaddr); // struct sockaddr_un 的大小
// 第四步: 接受客户端的连接
if ((clientFd = accept(serverFd, (struct sockaddr *)&clientaddr, &socklen)) == -1)
{
perror("accept faild");
unlink("tcpserver");
close(serverFd);
exit(1);
}
printf("客户端(%s)连接成功!!!\n", clientaddr.sun_path);
char buf[BUFSIZ] = {0};
// 第五步: 与客户端通信,接收客户端发过来的报文后,回复处理结果。
// 第六步: 不断的重复第五步,直到客户端断开连接。
while (1)
{
// 接收报文
size_t n = recv(clientFd, buf, sizeof(buf), 0);
if (n == -1)
{
perror("recv faild");
unlink("tcpserver");
close(serverFd); close(clientFd);
exit(1);
}
printf("receive: %s\n", buf);
// 发送报文
n = send(clientFd, buf, strlen(buf), 0);
if (n == -1)
{
perror("send faild");
unlink("tcpserver");
close(serverFd); close(clientFd);
exit(1);
}
printf("send: %s\n", buf);
}
unlink("tcpserver"); // 删除文件tcpserver
// 第七步: 关闭socket,释放资源
close(serverFd); close(clientFd);
return 0;
}
客户端工作流程和示例
客户端工作流程
- 创建客户端的socket。
- 向服务器发起连接请求。
- 与服务端通信,发送一个报文后等待回复,然后再发下一个报文。
- 不断的重复第3步,直到全部的数据被发送完。
- 第4步:关闭socket,释放资源。
客户端示例
/*
* function: 演示本地unix域套接字用于进程间通信(tcp协议)
* 此进程为客户端
*
* 2020-12-07
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h> // struct sockaddr_un
int main(int argc, char *argv[])
{
int sockfd = 0;
// 第一步: 创建unix域tcp协议的socket套接字
if ((sockfd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
{
perror("socket faild");
exit(1);
}
unlink("tcpclient"); // 删除tcpclient,防止文件被占用
// 注意: 不要删除正在使用的socket
#if 0
struct sockaddr_un clientaddr; // 客户端的地址
clientaddr.sun_family = AF_UNIX;
strcpy(clientaddr.sun_path, "tcpclient");
// 如果使用bind绑定客户端的通信地址后,服务端的accept()函数的第二个参数可以接收到客户端的地址,否则不能接收到。
if (bind(sockfd, (struct sockaddr *)&clientaddr, (socklen_t)sizeof(clientaddr)) == -1)
{
perror("bind faild");
close(sockfd);
exit(1);
}
#endif
struct sockaddr_un serveraddr; // 服务端的地址
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path, "tcpserver");
// 第二步: 向服务器发起连接请求
if (connect(sockfd, (struct sockaddr *)&serveraddr, (socklen_t)sizeof(serveraddr)) == -1)
{
perror("connect faild");
unlink("tcpclient");
close(sockfd);
exit(1);
}
printf("连接服务端成功!!!\n");
char sendBuf[] = "hello world";
char buf[BUFSIZ] = {0};
// 第三步: 与服务端通信,发送一个报文后等待回复, 然后发下一个报文。
// 第四步: 不断的重复第三步,直到全部的数据被发送完。
while (1)
{
// 发送报文
// 注意: sendBuf[], 内容为字符串, 因此才使用strlen(sendBuf), strlen()函数需要谨慎使用。
size_t n = send(sockfd, sendBuf, strlen(sendBuf), 0);
if (n == -1)
{
perror("send faild");
unlink("tcpclient");
close(sockfd);
exit(1);
}
printf("send: %s\n", sendBuf);
// 接收报文
n = recv(sockfd, buf, sizeof(buf), 0);
if (n == -1)
{
perror("recv faild");
unlink("tcpclient");
close(sockfd);
exit(1);
}
printf("recevie: %s\n", buf);
sleep(2);
}
unlink("tcpclient"); // 删除文件tcpclient
// 第五步: 关闭socket,释放资源
close(sockfd);
return 0;
}
客户端代码没有打开注释位置,即未使用bind绑定客户端的地址
客户端代码打开了注释位置,即使用bind绑定客户端的地址
注意事项
1. 关于客户端使用bind()函数
1)对于udp协议,如果客户端未使用bind() 函数,那么我们只能实现客户端向服务端发送信息,但服务端不能向客户端回复信息,因为服务端得不到客户端的地址。
2)对于tcp协议,如果客户端未使用bind() 函数,服务端与客户端可以正常通信,只是服务端同样得不到客户端本地socket的名字。
2. 关于recvfrom()函数
函数原型:
ssize_t recvfrom(int socket, void *restrict buffer, size_t length,
int flags, struct sockaddr *restrict address,
socklen_t *restrict address_len);
对于最有一个参数address_len,看着像一个传出参数,得到的是address的大小。
实际上,address_len表示的是address可以接收多大,为传入参数,应该传入sizeof(address_len).