Linux网络:socket编程UDP


前言

学习 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)告诉内核这是一个 IPv4socket

servaddr.sin_addr.s_addr = INADDR_ANY; // 任意 IP

INADDR_ANY = 0.0.0.0,表示服务端 绑定本机所有可用 IP 地址,如果服务器有多个网卡,客户端发送到任意 IP 都能被接收

servaddr.sin_port = htons(12345); // 端口

设置端口号为 12345htons = 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 socket
    if (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 socket
    if (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;
}

这里的sendtorecvfrom大家应该都懂,主要是这个客户端是如何找到服务端的重要

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值