UDP通讯也是用socket,但是接收和发送的函数与TCP不一样,由于UDP不存在握手这一步骤,所以在绑定地址之后,服务端不需要listen,客户端也不需要connect,服务端同样不需要accept。只要服务端绑定以后,就可以互相发消息了,由于没有握手过程,两端都不能确定对方是否收到消息,这也是UDP协议不如TCP协议可靠的地方。
UDP协议接收和发送数据不再用send和recv方法,这两个方法一般用于TCP通信,UDP通信使用sendto和recvfrom方法,声明如下:
/**
*@brief 将接收到的消息放入缓冲区buf中
*@param sockfd 套接字文件描述符
*@param buf 缓冲区指针
*@param len 缓冲区大小
*@param flags 通信标签,详见recv方法说明
*@param src_addr 可以填NULL,如果src_addr不是NULL,并且底层协议提供了消息的源地址,则该院地址将被放置在src_addr指向的缓冲区中
*@param addrlen 如果src_addr不为NULL,它应该初始化为与src_addr关联的缓冲区的大小。返回时,addrlen被更新为包含实际源地址的大小。如果提供的缓冲区太小,则返回的嫡长子将被截断,这种情况下,addrlen将返回一个大于调用时提供的值
*@return ssize_t 实际收到消息的大小,如果接受失败,返回-1
*/
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
/**
*@brief 向指定地址发送缓冲区中的数据(一般用于UDP模式)
*@param sockfd 套接字文件描述符
*@param buf 缓冲区指针
*@param len 缓冲区大小
*@param flags 通信标签,详见send方法说明
*@param dest_addr 目标地址,如果用于连接模式,该参数会被忽略
*@param addrlen 目标地址长度
*@return ssize_t 发送消息的大小,如果发送失败,返回-1
*/
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
------------------------udp_server.c------------------------
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#define handle_error(cmd, result) \
if (result < 0) \
{ \
perror(cmd); \
return -1; \
}
int main(int argc, char const *argv[])
{
int sockfd, temp_result, client_fd;
struct sockaddr_in server_addr, client_addr;
char *buf = malloc(1024);
memset(&server_addr, 0, sizeof(server_addr));
memset(&client_addr, 0, sizeof(client_addr));
// 声明IPV4通信协议
server_addr.sin_family = AF_INET;
// 我们需要绑定0.0.0.0地址,转换成网络字节序后完成设置
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 端口随便用一个,但是不要用特权端口
server_addr.sin_port = htons(6666);
// 创建server socket,注意通信类型
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
handle_error("socket", sockfd);
// 绑定地址
temp_result = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
handle_error("bind", temp_result);
do
{
// 清空缓冲区
memset(buf, 0, 1024);
// 尝试接受数据
socklen_t client_addr_len = sizeof(client_addr);
temp_result = recvfrom(sockfd, buf, 1024, 0, (struct sockaddr *)&client_addr, &client_addr_len);
handle_error("recvfrom", temp_result);
// 如果客户端发来的不是EOF
if (strncmp(buf, "EOF", 3) != 0)
{
printf("received msg from %s at port %d: %s", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buf);
strcpy(buf, "OK\n");
} else {
printf("received EOF from client, existing...\n");
}
// 收到数据后回复OK或EOF(客户端发送的是EOF则回复EOF,否则回复OK)
temp_result = sendto(sockfd, buf, 4, 0, (struct sockaddr *)&client_addr, client_addr_len);
handle_error("sendto", temp_result);
} while (strncmp(buf, "EOF", 3) != 0);
free(buf);
return 0;
}
------------------------udp_client.c------------------------
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define handle_error(cmd, result) \
if (result < 0) \
{ \
perror(cmd); \
return -1; \
}
int main(int argc, char const *argv[])
{
int sockfd, temp_result, client_fd;
struct sockaddr_in server_addr;
char *buf = malloc(1024);
memset(&server_addr, 0, sizeof(server_addr));
// 声明IPV4通信协议
server_addr.sin_family = AF_INET;
// 我们需要绑定0.0.0.0地址,转换成网络字节序后完成设置
server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
// 端口随便用一个,但是不要用特权端口
server_addr.sin_port = htons(6666);
// 创建server socket,注意通信类型
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
handle_error("socket", sockfd);
do
{
write(STDOUT_FILENO, "Type something you want to send: ", 34);
// 从标准输入读取数据
int buf_len = read(STDIN_FILENO, buf, 1023);
temp_result = sendto(sockfd, buf, buf_len, 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
handle_error("sendto", temp_result);
// 清空缓冲区
memset(buf, 0, 1024);
// 尝试接受数据
temp_result = recvfrom(sockfd, buf, 1024, 0, NULL, NULL);
handle_error("recvfrom", temp_result);
// 如果服务端发来的不是EOF
if (strncmp(buf, "EOF", 3) != 0)
{
printf("received msg from %s at port %d: %s", inet_ntoa(server_addr.sin_addr), ntohs(server_addr.sin_port), buf);
}
} while (strncmp(buf, "EOF", 3) != 0);
free(buf);
return 0;
}
Socket编程原本是为了网络服务的,后来逐渐发展成一种进程间通信的方式:Unix Domain Socket IPS。它允许在同一台主机上运行的进程之间进行高效的数据传输,无需经过网络协议栈,因此具有低延时和高性能的特点。通过文件系统中的特殊文件(通常是一个套接字文件),进程可以通过套接字(socket)来进行通信,实现双向的数据传输,Unix Domain Socket IPC 被广泛用于各种应用场景,如进程间通信、客户端-胡武器模型等,是Nuix/Linux系统中实现进程间通信的重要工具之一。
------------------------socket_ipc_test.c------------------------
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h> //标准定义:size_t等
#include <sys/socket.h> //socket核心工具箱
#include <sys/un.h> //Unix socket 专用工具
#include <sys/stat.h> //文件状态操作
#include <errno.h> //错误码处理
#include <string.h>
#include <unistd.h> //Unix 标准函数:read,write,unlink
#define SOCKET_PATH "unix_domain.socket"
#define SERVER_MODE 1
#define CLIENT_MODE 2
#define BUF_LEN 1024
static struct sockaddr_un socket_addr;
static char *buf;
void handle_error(char *err_msg)
{
perror(err_msg);
unlink(SOCKET_PATH); //删除路径
exit(-1); //立刻退出程序
}
void server_mode(int sockfd)
{
int client_fd, msg_len;
static struct sockaddr_un client_addr;
if (bind(sockfd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)) < 0) //绑定路径
{
handle_error("bind");
}
if (listen(sockfd, 128) < 0) //开始监听
{
handle_error("listen");
}
socklen_t client_addr_len = sizeof(client_addr);
if ((client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addr_len)) < 0)//等待连接
{
handle_error("accept");
}
write(STDOUT_FILENO, "Connected to client!\n", 21); //确认连接成功
do
{
memset(buf, 0, BUF_LEN);
msg_len = recv(client_fd, buf, BUF_LEN, 0);
printf("Received msg: %s", buf);
if (strncmp(buf, "EOF", 3) != 0)
{
strcpy(buf, "OK!\n\0");
}
send(client_fd, buf, strlen(buf), 0);
} while (strncmp(buf, "EOF", 3) != 0);
if (shutdown(client_fd, SHUT_RDWR) < 0)
{
handle_error("shutdown server");
}
unlink(SOCKET_PATH);
}
void client_mode(int sockfd)
{
int msg_len, header_len;
if (connect(sockfd, (struct sockaddr *)&socket_addr, sizeof(socket_addr)))
{
handle_error("connect");
}
write(STDOUT_FILENO, "Connected to server!\n", 21);
strcpy(buf, "Msg received: ");
// 计算buf中头的长度
header_len = strlen(buf);
do
{
msg_len = read(STDIN_FILENO, buf + header_len, BUF_LEN - header_len);
send(sockfd, buf + header_len, msg_len, 0);
msg_len = recv(sockfd, buf + header_len, BUF_LEN - header_len, 0);
write(STDOUT_FILENO, buf, msg_len + header_len);
} while (strncmp(buf + header_len, "EOF", 3) != 0);
}
int main(int argc, char const *argv[])
{
int fd = 0, mode = 0;
if (argc == 1 || strncmp(argv[1], "server", 6) == 0)
{
mode = SERVER_MODE;
}
else if (strncmp(argv[1], "client", 6) == 0)
{
mode = CLIENT_MODE;
}
else
{
perror("参数错误");
exit(-1);
}
// address初始化
memset(&socket_addr, 0, sizeof(struct sockaddr_un));
buf = malloc(BUF_LEN);
// 给address赋值
socket_addr.sun_family = AF_UNIX; //地址类型:Unix路径
strcpy(socket_addr.sun_path, SOCKET_PATH); //文件路径
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
{
handle_error("socket");
}
// 分服务端和客户端
switch (mode)
{
case SERVER_MODE:
server_mode(fd);
break;
case CLIENT_MODE:
client_mode(fd);
break;
}
if (shutdown(fd, SHUT_RDWR) < 0)
{
handle_error("shutdown");
}
free(buf);
exit(0);
}
struct sockaddr_un {
sa_family_t sun_family; // 地址类型 = AF_UNIX
char sun_path[108]; // 文件路径(最大108字节)
};
Unix Socket是用文件路径(例如:unix_domain.socket)
TCP/UDP 用的是IP+端口(例如:127.0.0.1:6666)
为什么必须删除socket文件呢,是因为Unix Socket会在文件系统创建特殊文件,程序异常退出时不会自动删除,导致下次启动失败
AF_UNIX :指定使用 Unix Domain Socket(IPC 专用)
SOCK_STREAM :流式传输(像 TCP,保证顺序和可靠)
SOCK_DGRAM(像 UDP,无连接)
sun_path:Unix socket 的核心!用文件路径代替 IP:端口
这里给一个决策:
是否需要跨机器通信:是的话用TCP/UDP,否的话再看是否需要高性能,是的话用法Unix Socket,否的话用管道/共享内存
IP协议叫做互联网协议,是我们网络通讯里面最基础的协议之一,他的目的是通过IP地址找到庞大的互联网上的具体的那一台终端,也就是一台电脑。
IP地址就是分配给每一个连接互联网的计算机的唯一标识符,在庞大的互联网上就是通过这个IP地址,找到具体的哪一台服务器
我们在使用IP地址去寻找目标的服务器的时候,中间的那个东西就叫路由器,同时路由也被我们当作是一种功能,就是帮你找路,就比如说我发一条消息要经过路由,我只知道我是发送给张三,并不知道张三在这个互联网上的具体位置,都是由路由器来根据我的一些知识信息,它帮助我找到具体的位置
IP是网络层的核心协议,负责把数据从源设备送到目标设备,它靠IP地址来标识设备,就像快递靠收件地址投递包裹。
IP地址按作用范围分为
私有地址:只在局域网用(比如192.168.x.x),不能上公网
共有地址:全球唯一,由ISP分配,能上互联网
按是否变化分为
静态IP:手动设,不变(服务器常用)。
动态IP:由DHCP自动分配,会变(手机、电脑常用)。
| 设备 | 工作层次 | 依据什么转发? | 作用 |
|---|---|---|---|
| 路由器 | 网络层(第3层) | IP地址 | 连接不同网络,选最优路径(比如你家Wi-Fi到互联网) |
| 交换机 | 数据链路层(第2层) | MAC地址 | 在局域网内高效转发帧,减少冲突(比如连你家5台电脑) |
MAC地址是网卡的硬件唯一标识(48位,如 00:1A:2B:3C:4D:5E)。
ARP协议:IP->MAC的翻译官
以太网帧靠MAC传输,但应用层用IP,所以必须靠ARP转换
DNS就是把域名查出IP
- 先查本地缓存(浏览器、系统);
- 没有就问 本地 DNS 服务器(比如运营商的);
- 本地 DNS 依次问:根服务器 → .com 顶级服务器 → baidu 的权威服务器;
- 拿到 IP,返回给你,你才能连上网站。
DNS是应用层协议,但依赖UDP(有时TCP)