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

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

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

相关推荐
大聪明-PLUS2 小时前
硬件断点:它们在 Linux 中的用途和工作原理
linux·嵌入式·arm·smarc
Starry_hello world2 小时前
Linux 线程(2)
linux
Promise4852 小时前
关于使用wsl实现linux移植(imux6ull)的网络问题
linux·服务器·网络
郝学胜-神的一滴2 小时前
Linux线程的共享资源与非共享资源详解
linux·服务器·开发语言·c++·程序人生·设计模式
郝学胜-神的一滴2 小时前
Linux进程与线程的区别:从内存三级映射角度深入解析
linux·服务器·c++·程序人生
雪花凌落的盛夏2 小时前
x86电脑安装steamOS
linux
不爱吃糖的程序媛2 小时前
OpenHarmony Linux 环境 SDK 使用说明(进阶--依赖库的解决方法)
linux·运维·harmonyos
gaize12132 小时前
服务器技术参数怎么写
服务器·网络·安全
Henry Zhu1232 小时前
VPP中ACL源码详解第三篇:ACL是如何以统一的接口来为其他插件服务(上)
运维·服务器·网络