news 2026/7/21 12:45:41

Linux操作系统———UDP/IPC网络编程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux操作系统———UDP/IPC网络编程

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(intsockfd,void*buf,size_tlen,intflags,structsockaddr*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_tsendto(intsockfd,constvoid*buf,size_tlen,intflags,conststructsockaddr*dest_addr,socklen_taddrlen);

————————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

  1. 先查本地缓存(浏览器、系统);
  2. 没有就问本地 DNS 服务器(比如运营商的);
  3. 本地 DNS 依次问:根服务器 → .com 顶级服务器 → baidu 的权威服务器
  4. 拿到 IP,返回给你,你才能连上网站。

DNS是应用层协议,但依赖UDP(有时TCP)

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/21 16:32:58

39、程序性能调优工具全解析

程序性能调优工具全解析 在软件开发过程中,程序性能调优是一个至关重要的环节。本文将详细介绍几种常用的性能调优工具,包括它们的使用方法、适用场景以及优缺点。 1. 使用Valgrind检查指令效率 Valgrind是一款功能强大的工具集,可用于检查内存泄漏和内存损坏问题。这里我…

作者头像 李华
网站建设 2026/7/21 14:15:07

大模型的合规突围:从风险管控到专业赋能

面对大模型备案&#xff0c;不少企业仍陷入“想合规却不知如何落地”的困境&#xff1a;备案材料反复被退回、数据脱敏技术不达标、内容审核机制缺失……这些“合规痛点”不仅拖延了产品上线周期&#xff0c;更可能让企业错失市场先机。 ​一、​​大模型​技术的隐忧 2023 年…

作者头像 李华
网站建设 2026/7/20 15:21:13

43、调试技巧与GDB使用指南

调试技巧与GDB使用指南 在软件开发过程中,调试是一项至关重要的技能。本文将介绍调试过程中的一些注意事项,以及如何使用GNU调试器(GDB)进行有效的调试。 1. 信息查找与交流简洁性 如果所需信息分布在多行中,可能需要手动检查。同时,在调试过程中,应尽量保持交流简洁…

作者头像 李华
网站建设 2026/7/19 9:58:35

36、Ubuntu系统使用问题及安装方法全解析

Ubuntu系统使用问题及安装方法全解析 1. 常见问题及解决办法 1.1 程序频繁崩溃 当程序频繁崩溃时,可尝试在终端(Terminal)中运行该程序,以找出问题所在。具体操作步骤如下: 1. 获取运行该程序所需的命令名。 2. 将命令输入到终端,按回车键。 3. 正常使用程序,直至…

作者头像 李华
网站建设 2026/7/21 11:26:39

物流仓储智能调度与空间管理(Agent驱动的效率革命)

第一章&#xff1a;物流仓储 Agent 的空间利用在现代物流仓储系统中&#xff0c;Agent 技术的引入显著提升了空间利用率与作业效率。通过部署智能 Agent&#xff0c;仓库能够实现动态货位分配、路径优化与库存实时监控&#xff0c;从而最大化利用有限的空间资源。智能货位分配策…

作者头像 李华
网站建设 2026/7/19 18:45:52

什么是网络安全?渗透安全及渗透测试流程(超详细)!

什么是网络安全&#xff1f; 1、国际化标准组织&#xff08;ISO&#xff09;引用ISO-74982文献中对安全的定义&#xff1a;安全就是最大程度地减少数据和资源被攻击的可能性。 2、《计算机信息安全系统保护条例》中的第三条规范了包括计算机网络系统在内的计算机信息系统安全…

作者头像 李华