Linux网络:socket编程UDP
文章目录
- 前言
- 一,socket
- 二,服务端socket
- 3-1 创建socket
- 3-2 绑定地址和端口
- 3-3 接收数据
- 3-4 回复数据
- 3-5关闭socket
- 3-6 完整代码
- 三,客户端socket
- 3-1 为什么客户端通常不需要手动定义 IP 和端口
前言
学习 socket
编程的意义在于:它让你掌握计算机之间通信的核心原理,能亲手实现聊天程序、文件传输、简易服务器等网络应用;同时这是理解 TCP/IP
协议、深入系统编程和进入后台开发、分布式系统的基础技能,也是面试和工程实践中必不可少的知识。
一,socket
socket
(套接字) 是操作系统提供的一种 通信机制,最常用于网络通信,在编程时,你可以把它当作 特殊的文件描述符(FD)
,既能读、也能写,文件用来在本地磁盘读写数据,而 socket
用来在不同主机之间交换数据。你可以把 socket
看作网络文件,只是读写的数据不是在磁盘上,而是发送到网络上的另一个进程。
二,服务端socket
主要作用:接收客户端请求并回复,服务器端使用socket
分为几个阶段:创建 socket -> 绑定地址和端口 -> 等待客户端发送数据 / 监听连接 -> 处理数据或回复 -> 关闭 socket
3-1 创建socket
socket函数原型
int socket(int domain, int type, int protocol);
domain
:用于选择网络协议协议类型一般有值:AF_INET
(IPv4 网络协议),AF_INET6
(IPv6 网络协议),AF_UNIX/AF_LOCAL
(本地进程间通信)type
:指定传输方式,主要有两种:SOCK_STREAM
面向连接,基于字节流(TCP),SOCK_DGRAM
无连接,基于报文(UDP)protocol
:通常指定具体协议:0
默认协议,IPPROTO_TCP
明确选择 TCP,IPPROTO_UDP
明确选择 UDP
在下面的代码示例中我们会选择:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
创建一个UDP
的套接字
3-2 绑定地址和端口
struct sockaddr_in servaddr;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP
servaddr.sin_port = htons(12345); // 端口
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
上面的struct sockaddr_in servaddr
创建一个 IPv4
地址结构体 sockaddr_in
,用来保存服务端的 IP
地址和端口信息结构体定义大致如下:
struct sockaddr_in {short sin_family; // 地址族 (AF_INET)unsigned short sin_port; // 端口号struct in_addr sin_addr; // IP 地址char sin_zero[8]; // 填充为与 struct sockaddr 同大小
};
解释上述代码:
servaddr.sin_family = AF_INET;
设置 地址族为 IPv4 (AF_INET)
告诉内核这是一个 IPv4
的 socket
servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP
INADDR_ANY = 0.0.0.0
,表示服务端 绑定本机所有可用 IP
地址,如果服务器有多个网卡,客户端发送到任意 IP
都能被接收
servaddr.sin_port = htons(12345); // 端口
设置端口号为 12345
,htons = host to network short
(主机字节序 → 网络字节序),网络通信使用 大端序,保证不同平台可以正确解析端口
bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
bind()
将 socket
文件描述符 与 IP
+ 端口
绑定
参数解释:
sockfd
:之前 socket()
返回的文件描述符
(struct sockaddr*)&servaddr
: 地址信息,强制类型转换为通用 sockaddr
sizeof(servaddr)
: 结构体大小
3-3 接收数据
char buffer[100];
struct sockaddr_in cliaddr {};
socklen_t len = sizeof(cliaddr);int n = recvfrom(sockfd, buffer, sizeof(buffer) - 1, 0, (struct sockaddr * ) & cliaddr, & len);
这段代码在一个 UDP
服务器程序中,从客户端接收数据,存储到 buffer
中,同时记录客户端的地址信息cliaddr
,并返回接收的字节数n
。
char buffer[100];
定义一个字符数组 buffer
,大小为 100
字节,用来存储从客户端接收的数据
struct sockaddr_in cliaddr {};
定义一个 sockaddr_in
结构体变量 cliaddr
,用于存储客户端的地址信息(如 IP
地址和端口号)。{}
初始化所有字段为 0
socklen_t len = sizeof(cliaddr);
定义一个 socklen_t
类型的变量 len
,初始化为 cliaddr
的大小(通常 16
字节,struct sockaddr_in
的大小)。
3-4 回复数据
const char* reply = "Hello Client";sendto(sockfd, reply, strlen(reply), 0, (struct sockaddr*)&cliaddr, len); // 回复
这段代码用与服务器回复客户端信息
其它的不再过多赘述
(struct sockaddr*)&cliaddr
:客户端地址结构体,告诉内核消息发送到哪里
3-5关闭socket
close(sockfd);
不关闭会造成资源泻漏
3-6 完整代码
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP socketif (sockfd < 0) { perror("socket"); return -1; }sockaddr_in servaddr{};servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = INADDR_ANY;servaddr.sin_port = htons(12345);bind(sockfd, (sockaddr*)&servaddr, sizeof(servaddr)); // 绑定端口char buffer[100];sockaddr_in cliaddr{};socklen_t len = sizeof(cliaddr);int n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (sockaddr*)&cliaddr, &len); // 接收消息buffer[n] = '\0';std::cout << "Server received: " << buffer << std::endl;const char* reply = "Hi Client";sendto(sockfd, reply, strlen(reply), 0, (sockaddr*)&cliaddr, len); // 回复客户端close(sockfd);return 0;
}
三,客户端socket
客户端socket
代码我直接展示出来吧,我们理解了服务端之后,客户端是非常好理解的
#include <iostream>
#include <arpa/inet.h>
#include <unistd.h>int main() {int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP socketif (sockfd < 0) { perror("socket"); return -1; }sockaddr_in servaddr{};servaddr.sin_family = AF_INET;servaddr.sin_port = htons(12345);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本机测试const char* msg = "Hello Server";sendto(sockfd, msg, strlen(msg), 0, (sockaddr*)&servaddr, sizeof(servaddr)); // 发送消息char buffer[100];socklen_t len = sizeof(servaddr);int n = recvfrom(sockfd, buffer, sizeof(buffer)-1, 0, (sockaddr*)&servaddr, &len); // 接收回复buffer[n] = '\0';std::cout << "Client received: " << buffer << std::endl;close(sockfd);return 0;
}
这里的sendto
和recvfrom
大家应该都懂,主要是这个客户端是如何找到服务端的重要
sockaddr_in servaddr{};servaddr.sin_family = AF_INET;servaddr.sin_port = htons(12345);servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本机测试
在这段代码里的IP地址和端口号是找到服务端的关键
-
IP 地址
(127.0.0.1):告诉客户端要把数据发送到哪个机器。127.0.0.1
表示本机,也就是服务端和客户端在同一台电脑。如果服务端在另一台电脑,你就要写它的真实IP
,例如192.168.1.100
。 -
端口号
(12345)
服务端在这个端口上监听数据。UDP/TCP
都是靠端口区分不同服务的,就像房子门牌号一样。所以客户端通过IP, port
就能找到服务端。
3-1 为什么客户端通常不需要手动定义 IP 和端口
客户端的职责是 主动找服务端,在调用 sendto()
时:目标地址(服务端的 IP+端口
) 由程序员指定,源地址(客户端的 IP+端口
) 不写时由内核自动分配
IP
:自动选择一块能到达服务端的本地网卡的 IP
端口
:自动分配一个 临时端口,通常在 49152–65535 之间
客户端什么时候需要绑定 IP+端口:如果客户端希望 使用固定端口(比如做 P2P
、游戏服务器通知端口),或者有多张网卡,必须指定 用哪张网卡的 IP
去通信,这种情况才会手动调用 bind()