网络编程套接字(UDP)

目录

一、预备知识

1.端口号的认识

1.1为什么需要端口号?

[1.2 什么是端口号?](#1.2 什么是端口号?)

[1.3 端口号的分类](#1.3 端口号的分类)

[1.4 端口号与IP地址的组合](#1.4 端口号与IP地址的组合)

2.TCP(传输控制协议)

[2.1 核心特点](#2.1 核心特点)

[2.2 典型应用](#2.2 典型应用)

[3. UDP(用户数据报协议)](#3. UDP(用户数据报协议))

[3.1 核心特点](#3.1 核心特点)

[3.2 典型应用](#3.2 典型应用)

4.网络字节序

[4.1 什么是字节序?](#4.1 什么是字节序?)

[4.2 为什么需要网络字节序?](#4.2 为什么需要网络字节序?)

[4.3 字节序转换函数](#4.3 字节序转换函数)

5.socket编程接口

[5.1常用Socket API](#5.1常用Socket API)

[5.2 sockaddr 结构(⭐⭐⭐)](#5.2 sockaddr 结构(⭐⭐⭐))

[5.2.1 struct sockaddr](#5.2.1 struct sockaddr)

[5.2.2 sockaddr_in 与 sockaddr_un](#5.2.2 sockaddr_in 与 sockaddr_un)

[5.2.3 强制类型转换](#5.2.3 强制类型转换)

二、简单的UDP网络程序

[1. 服务端实现](#1. 服务端实现)

[1.1 制造通信设备:socket()](#1.1 制造通信设备:socket())

[1.1.1 文件系统的fd](#1.1.1 文件系统的fd)

[1.1.2 socket返回值的fd(⭐⭐⭐)](#1.1.2 socket返回值的fd(⭐⭐⭐))

[1.2 填充sockaddr_in local (⭐⭐⭐)](#1.2 填充sockaddr_in local (⭐⭐⭐))

[1.2.1 bzero](#1.2.1 bzero)

[1.2.2 确定通信频道:sin_family = AF_INET](#1.2.2 确定通信频道:sin_family = AF_INET)

[1.2.3 端口号的"入乡随俗":htons(port_)](#1.2.3 端口号的“入乡随俗”:htons(port_))

[1.2.4 IP 地址的双重转换:inet_addr()](#1.2.4 IP 地址的双重转换:inet_addr())

[1.2.5 INADDR_ANY](#1.2.5 INADDR_ANY)

[1.3 宣誓主权:bind() (⭐⭐⭐)](#1.3 宣誓主权:bind() (⭐⭐⭐))

[1.4 阻塞接收:recvfrom()](#1.4 阻塞接收:recvfrom())

[1.5 原路返回:sendto()](#1.5 原路返回:sendto())

[2. 客户端实现](#2. 客户端实现)

[2.1 制造通信设备:socket()](#2.1 制造通信设备:socket())

[2.2 准备服务端的地址 (填充 sockaddr_in server)](#2.2 准备服务端的地址 (填充 sockaddr_in server))

[2.3 不需要显式 bind()(⭐⭐⭐)](#2.3 不需要显式 bind()(⭐⭐⭐))

[2.4 主动出击:sendto()](#2.4 主动出击:sendto())

[2.5 阻塞等待响应:recvfrom()](#2.5 阻塞等待响应:recvfrom())

[3. 代码结果示例](#3. 代码结果示例)

[3.1 netstat命令或ss(Socket Statistics)命令](#3.1 netstat命令或ss(Socket Statistics)命令)

[3.2 lsof命令](#3.2 lsof命令)

[3.3 通信结果示例](#3.3 通信结果示例)

[4. 完整代码](#4. 完整代码)

[4.1 服务器端](#4.1 服务器端)

[4.1.1 UdpServer.hpp](#4.1.1 UdpServer.hpp)

[4.1.2 main.cpp](#4.1.2 main.cpp)

[4.2 客户端](#4.2 客户端)


一、预备知识

1.端口号的认识

1.1为什么需要端口号?

一台主机上可能同时运行着多个网络应用程序(例如浏览器、邮件客户端、Web服务器)。当数据包到达主机时,操作系统需要知道该把它交给哪个进程。IP地址 负责将数据包送到正确的主机,而端口号则负责将数据包送到主机上正确的进程。

1.2 什么是端口号?

端口号是一个16位 的无符号整数,取值范围是 0 ~ 65535。它用于标识一台主机上的特定进程或服务。

1.3 端口号的分类

  • 知名端口:0 ~ 1023,这些端口通常固定分配给一些常用的网络服务。例如:

    • 80:HTTP(网页服务)

    • 443:HTTPS

    • 21:FTP

    • 22:SSH

  • 注册端口(Registered Ports):1024 ~ 49151,用于用户自定义的应用程序,但需向IANA注册。

  • 动态/私有端口(Dynamic/Private Ports):49152 ~ 65535,一般作为客户端的临时端口,由操作系统随机分配。

1.4 端口号与IP地址的组合

一个完整的通信端点由 IP地址 + 端口号 组成,称为套接字地址(Socket Address)。例如 192.168.1.100:8080 表示IP为 192.168.1.100 的主机上的 8080 端口。

2.TCP(传输控制协议)

TCP是一种面向连接的、可靠的、基于字节流的传输层协议。

2.1 核心特点

  • 面向连接:通信前需要通过三次握手建立连接,通信结束后通过四次挥手释放连接。

  • 可靠传输:通过确认应答、超时重传、序号机制、校验和等保证数据无丢失、无重复、按序到达。

  • 基于字节流:TCP将应用层数据视为无结构的字节流,不保留消息边界。

  • 流量控制:通过滑动窗口机制,让发送方根据接收方的处理能力调整发送速度。

  • 拥塞控制:当网络出现拥塞时,TCP会自动降低发送速率,避免网络瘫痪。

  • 全双工通信:连接双方可以同时发送和接收数据。

2.2 典型应用

TCP适用于要求数据准确可靠的场景,如文件传输(FTP)、网页浏览(HTTP)、电子邮件(SMTP)等。

3. UDP(用户数据报协议)

UDP是一种无连接的、不可靠的、基于数据报的传输层协议。

3.1 核心特点

  • 无连接:发送数据前不需要建立连接,直接发送即可。

  • 不可靠:不保证数据一定能到达,也不保证顺序,可能丢失、重复或乱序。

  • 基于数据报:保留消息边界,应用层每次写操作对应一个UDP数据报,接收方必须按相同大小读取。

  • 开销小:头部仅8字节(TCP头部20字节),无复杂的拥塞控制和流量控制,传输效率高。

  • 支持广播和多播:UDP可以一对多发送数据。

3.2 典型应用

UDP适用于实时性要求高、允许少量丢包的场景,如视频直播、语音通话(VoIP)、DNS查询、网络游戏等。

4.网络字节序

4.1 什么是字节序?

字节序是指多字节数据在内存中的存放顺序。主要分为两种:

  • 大端字节序(Big-Endian):高位字节存放在低地址,低位字节存放在高地址。

  • 小端字节序(Little-Endian):低位字节存放在低地址,高位字节存放在高地址。

不同的主机可能采用不同的字节序(例如x86架构使用小端,ARM可配置,网络设备多采用大端)。

4.2 为什么需要网络字节序?

当数据在不同字节序的主机之间传输时,如果不统一规定,接收方会错误地解释数据。

因此,TCP/IP协议规定:网络传输必须使用大端字节序 ,称为网络字节序

4.3 字节序转换函数

在编写网络程序时,我们需要将主机字节序转换为网络字节序,或者反过来。这些函数通常由系统提供,能自动处理主机字节序与网络字节序之间的转换。常用的函数有(以C语言为例):

  • htons():host to network short(16位)

  • htonl():host to network long(32位)

  • ntohs():network to host short(16位)

  • ntohl():network to host long(32位)

5.socket编程接口

5.1常用Socket API

cpp 复制代码
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
// 绑定端口号 (TCP/UDP, 服务器)      
int bind(int socket, const struct sockaddr *address,
          socklen_t address_len);
// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);
// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
          socklen_t* address_len);
// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
          socklen_t addrlen);
  • socket():创建一个套接字(相当于买一部手机)。

  • bind():绑定 IP 和端口号(相当于给手机上卡,拥有了属于自己的号码)。

  • listen():监听连接请求(把手机开机,随时准备接听电话,主要用于 TCP)。

  • accept():接收连接(接通电话,准备开始交流)。

  • connect():发起连接(主动拨打别人的号码)。

  • recv() / send()read() / write():接收和发送数据。

5.2 sockaddr 结构(⭐⭐⭐)

5.2.1 struct sockaddr
cpp 复制代码
struct sockaddr {
    sa_family_t sa_family;  // 地址家族 (Address Family),例如 AF_INET, AF_INET6, AF_UNIX,占 2 字节
    char        sa_data[14]; // 具体的地址数据(IP + 端口),占 14 字节
};

无论是哪种协议,前 2 个字节必须是 sa_family,告诉操作系统你用的是什么协议。操作系统看到这个标识后,就知道该怎么解析后面的 14 个字节了。

5.2.2 sockaddr_insockaddr_un

虽然 API 要求传入 sockaddr,但在实际写代码时,直接操作那 14 字节的字符数组来拼凑 IP 和端口简直是反人类的。因此,针对不同的协议,系统提供了专门的、更易读的结构体:

(1)网络通信专用的 IPv4 地址结构:struct sockaddr_in

cpp 复制代码
struct sockaddr_in {
    sa_family_t    sin_family; // 必须是 AF_INET (2字节)
    in_port_t      sin_port;   // 16位端口号,必须是网络字节序 (2字节)
    struct in_addr sin_addr;   // 32位 IP 地址 (4字节)
    unsigned char  sin_zero[8]; // 填充字段,为了保证和 sockaddr 长度一致都是 16 字节
};

(2)本地进程间通信专用的结构:struct sockaddr_un

cpp 复制代码
struct sockaddr_un {
    sa_family_t sun_family;        // 地址族,固定为 AF_UNIX
    char        sun_path[108];     // 套接字文件路径
};
5.2.3 强制类型转换
cpp 复制代码
// 1. 定义并初始化特定的 IPv4 地址结构
struct sockaddr_in local_addr;
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(8080); // 别忘了转网络字节序
local_addr.sin_addr.s_addr = INADDR_ANY;

// 2. 调用底层 API 时,强转为通用的 sockaddr* 指针
bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr));

sockaddr 就像是一个没有任何内部隔断的标准集装箱,不管你是运苹果(IPv4)还是运香蕉(IPv6),只要装得进这个 16 字节的箱子,并在箱子外面贴好标签(sa_family ),海关(操作系统底层的 Socket API)就一视同仁地给你放行。而 sockaddr_in 就是为了方便我们往箱子里合理摆放苹果而专门设计的模具。

二、简单的UDP网络程序

1. 服务端实现

服务端的生命周期分为三大步:创建、绑定、死循环监听

1.1 制造通信设备:socket()

cpp 复制代码
sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
  • 参数 1 (AF_INET): 域/协议族。表示使用 IPv4 网络协议。

  • 参数 2 (SOCK_DGRAM): 套接字类型。DGRAM 即 Datagram(数据报),明确表示我们要使用的是无连接、不可靠的 UDP 协议

  • 参数 3 (0): 默认协议。填 0 表示让系统根据前两个参数自动推导出 UDP 协议。

  • 返回值 (sockfd_): 成功则返回一个非负整数,这就是网络文件描述符。后续所有的收发操作,全部基于这个数字。

1.1.1 文件系统的fd
1.1.2 socket返回值的fd(⭐⭐⭐)

(1) Linux 内核将这个 struct file 的底层读写指针,替换成了网络协议栈的函数。当我们向 fd 写入数据时,数据并没有像普通文件那样流向磁盘,而是流向了内核内存中的 "发送缓冲区"

(2) struct file 结构体的私有数据指针指向了一个特定的 struct socket,进而指向了 struct sock。你发送的数据包进入了内核内存中的 "发送缓冲区" 。到这里, sendto 代码就已经返回成功了。数据被放进了邮局的邮箱。

(3) 隐藏在缓冲区后面的网络协议栈(UDP/IP)会通过一个接力跑的过程,找到数据包该走的路由,给它套上各种报头,然后把数据交到网卡驱动程序的队列中。

(4) 网卡硬件驱动,通过 DMA(直接内存访问) 技术,不需要 CPU 参与,直接从内核内存中把数据"吸"到网卡的物理芯片里,转换成电信号从网线发射出去。

1.2 填充sockaddr_in local (⭐⭐⭐)

在创建完 Socket(买好手机)之后,我们必须给它绑定一个网络地址(上电话卡)。在 C++ 网络编程中,填充 sockaddr_in 结构体就是在这个"电话卡"上写明我们的 IP 和端口。

cpp 复制代码
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
//需要保证我的端口号是网络字节序列,因为该端口号是要给对方发送的
local.sin_port = htons(port_); 
//(1)string -> uint32_t (2)uint32_t必须是网络序列的 
local.sin_addr.s_addr = inet_addr(ip_.c_str());  
// local.sin_addr.s_addr = htonl(INADDR_ANY);
1.2.1 bzero

在 C/C++ 中,局部变量如果不初始化,内存里存放的都是随机的"垃圾值"。为了防止这些脏数据干扰底层的网络绑定,使用 bzero(或者 memset)将结构体内部的所有字节清零,确保绝对干净。

1.2.2 确定通信频道:sin_family = AF_INET

AF_INET 代表 IPv4 网络协议,告诉系统接下来要按照 IPv4 的规则来解析后面的地址。

1.2.3 端口号的"入乡随俗":htons(port_)

网络世界规定:所有在网络上传输的数据,必须是大端字节序(网络字节序)。 htons(Host to Network Short)函数就是用来将我们主机上的 16 位端口号,翻译成全网通用的网络字节序。

1.2.4 IP 地址的双重转换:inet_addr()

我们日常习惯的 IP 是类似于 "192.168.1.1" 的点分十进制字符串。但计算机只认 32 位的无符号整数 (uint32_t) 。**inet_addr()**这个函数一次性帮我们做了两件事:

  • 把人看的字符串 IP 转换成了底层的 32 位整型。

  • 顺手把这个 32 位整型转换成了网络字节序

1.2.5 INADDR_ANY

在真实的云服务器开发中,服务端极其不建议绑定具体的公网 IPINADDR_ANY 的本质就是 0(即 0.0.0.0),代表任意地址。它的意思是:"只要是发到我这台服务器 8080 端口的数据,不管是从哪个网卡进来的,我全盘接收!"

1.3 宣誓主权:bind() (⭐⭐⭐)

cpp 复制代码
bind(sockfd_, (const struct sockaddr *)&local, sizeof(local))

把刚才填充好的 sockaddr_in local 结构体,与 sockfd_ 绑定在一起。

  • 参数 1: 刚刚创建的网络文件描述符。

  • 参数 2: 强转为 struct sockaddr *。这是 C 语言时代的"多态",为了让 bind 函数能接收各种不同类型的协议地址。

  • 参数 3: 该结构体的大小。

  • 核心逻辑: 告诉操作系统,"这台机器上的这个端口(8080)和这个 IP,从现在起被我这个进程接管了,外面发来的数据请交给我!"

1.4 阻塞接收:recvfrom()

cpp 复制代码
ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 
                                0, (struct sockaddr*)&client, &len);

这是 UDP 接收数据的绝对核心,它有 6 个参数:

  • sockfd 从哪个套接字读数据。

  • buf 数据存放的缓冲区(我们定义的字符数组)。

  • len 期望读取的最大字节数, 故意减 1 是为了给 C 风格字符串的结尾 \0 留出空间。

  • flags: 填 0 表示如果网络没数据,程序就在这里死等(阻塞),直到有数据到来。

  • src_addr(输出型参数): 操作系统在把数据给我们的同时,会把发送方(客户端)的 IP 和端口提取出来,塞进这个结构体里。

  • addrlen(输入输出型参数) 传入时表示缓冲区大小,返回时表示实际填充的地址结构体大小。

1.5 原路返回:sendto()

cpp 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

处理完数据后,我们要把结果发回去。同样是 6 个参数:

  • sockfd **套接字文件描述符,**标识通过哪个网络端点发送数据。

  • buf 指向待发送数据的缓冲区

  • len 要发送的数据长度(以字节为单位)。

  • flags:发送选项的标志位

  • dest_addr:指向目标地址结构体的指针 。直接把刚才 recvfrom 截获的 client 结构体填进去,实现"从哪儿来,回哪儿去"。

  • addrlen(输入输出型参数) 目标地址结构体的大小

2. 客户端实现

客户端的生命周期相对简单:创建套接字、准备服务端地址、发送数据、接收响应。它不需要死等别人,想什么时候发就什么时候发。

2.1 制造通信设备:socket()

cpp 复制代码
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

同服务端一样,客户端也需要一个网络文件描述符作为通信的凭证。参数与服务端完全一致。

2.2 准备服务端的地址 (填充 sockaddr_in server)

客户端发送数据前,必须知道"发给谁"。因此需要提前将目标服务端的 IP 和端口填充到地址结构体中,这相当于提前写好快递的收件地址。

cpp 复制代码
struct sockaddr_in server;
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(serverport); // 转换为网络字节序
server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 字符串IP转32位整型并转网络序列
socklen_t len = sizeof(server);

2.3 不需要显式 bind()(⭐⭐⭐)

在客户端代码中,并没有像服务端那样调用 bind() 函数。客户端其实也需要绑定 IP 和端口,只不过不需要程序员在代码中显式调用

  • 服务端的端口必须明确: 服务端是被动接收请求的,它的端口必须固定且广为人知,因为大家都需要找它。

  • 客户端的端口无需固定: 客户端的端口是多少其实不重要,只要能保证在本机上的唯一性即可。如果程序员写死客户端端口(例如强行绑死 8888),万一该端口被电脑上的其他软件占用,客户端就会启动失败。

  • 操作系统的隐式绑定: 当客户端第一次调用 sendto 发送数据时,操作系统会在底层随机挑选一个空闲的高位端口,自动与该套接字进行绑定。

2.4 主动出击:sendto()

cpp 复制代码
sendto(sockfd, message.c_str(), message.size(), 
                    0, (struct sockaddr *)&server, len);

客户端像是一个带着明确目标地址的快递员,主动向外发送请求。

2.5 阻塞等待响应:recvfrom()

cpp 复制代码
struct sockaddr_in temp;
socklen_t len = sizeof(temp);
ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);

发送完毕后,客户端调用 recvfrom 陷入阻塞,静静等待服务端的处理结果顺着网线传回来。

3. 代码结果示例

3.1 netstat命令或ss(Socket Statistics)命令

bash 复制代码
netstat -nlup
ss -nlup
  • -n (numeric):以数字形式显示 IP 和端口(拒绝将 80 显示为 http,拒绝解析域名,速度更快)。

  • -l (listening):只显示正在"监听"状态的套接字(你的服务器跑起来后就是这个状态)。

  • -u (udp):只显示 UDP 协议的网络状况。(如果是查 TCP,就把 u 换成 t,即 -nltp)。

  • -p (program):显示是哪个进程(PID 和进程名)占用了这个端口。(看其他人的进程可能需要 sudo 权限)

3.2 lsof命令

bash 复制代码
lsof -i :8080

这条命令会直接列出占用该端口的进程名和 PID。拿到 PID 后,如果你确定那个进程没用了,就可以直接 kill -9 PID 把它干掉,然后重新启动你的 UdpServer

3.3 通信结果示例

4. 完整代码

4.1 服务器端

4.1.1 UdpServer.hpp
cpp 复制代码
#pragma once

#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include "Log.hpp"

using func_t = std::function<std::string(const std::string&)>;

Log lg;

enum{
    SOCKET_ERR=1,
    BIND_ERR
};

uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";  //任意地址绑定
const int size = 1024;

class UdpServer{
public:
    UdpServer(const uint16_t &port = defaultport, const std::string &ip = defaultip):sockfd_(0), port_(port), ip_(ip),isrunning_(false)
    {}
    void Init()
    {
        // 1. 创建udp socket
        sockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INET
        if(sockfd_ < 0)
        {
            lg(Fatal, "socket create error, sockfd: %d", sockfd_);
            exit(SOCKET_ERR);
        }
        lg(Info, "socket create success, sockfd: %d", sockfd_);
        // 2. bind socket
        struct sockaddr_in local;
        bzero(&local, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(port_); //保证端口号是网络字节序列
        local.sin_addr.s_addr = inet_addr(ip_.c_str()); 
        // local.sin_addr.s_addr = htonl(INADDR_ANY);

        if(bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
        {
            lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));
            exit(BIND_ERR);
        }
        lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));
    }
    void Run(func_t func)
    {
        isrunning_ = true;
        char inbuffer[size];
        while(isrunning_)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);
            if(n < 0)
            {
                lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));
                continue;
            }
            inbuffer[n] = 0;
            std::string info = inbuffer;
            std::string echo_string = func(info);
            
            sendto(sockfd_, echo_string.c_str(), echo_string.size(), 0, (const sockaddr*)&client, len);
        }
    }
    ~UdpServer()
    {
        if(sockfd_>0) close(sockfd_);
    }
private:
    int sockfd_;     // 网路文件描述符
    std::string ip_; // 任意地址bind 0
    uint16_t port_;  // 表明服务器进程的端口号
    bool isrunning_;
};
4.1.2 main.cpp
cpp 复制代码
#include "UdpServer.hpp"
#include <memory>
#include <cstdio>


void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " port[1024+]\n" << std::endl;
}

std::string Handler(const std::string &str)
{
    std::string res = "Server get a message: ";
    res += str;
    std::cout << res << std::endl;
    return res;
}


int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        Usage(argv[0]);
        exit(0);
    }

    uint16_t port = std::stoi(argv[1]);

    std::unique_ptr<UdpServer> svr(new UdpServer(port));

    svr->Init();
    svr->Run(Handler);

    return 0;
} 

4.2 客户端

cpp 复制代码
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

using namespace std;

void Usage(std::string proc)
{
    std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
              << std::endl;
}

// ./udpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    struct sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport); //?
    server.sin_addr.s_addr = inet_addr(serverip.c_str());
    socklen_t len = sizeof(server);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        cout << "socker error" << endl;
        return 1;
    }


    string message;
    char buffer[1024];
    while (true)
    {
        cout << "Please Enter@ ";
        getline(cin, message);


        sendto(sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&server, len);
        
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);

        ssize_t s = recvfrom(sockfd, buffer, 1023, 0, (struct sockaddr*)&temp, &len);
        if(s > 0)
        {
            buffer[s] = 0;
            cout << buffer << endl;
        }
    }

    close(sockfd);
    return 0;
}
相关推荐
原来是猿2 小时前
Linux - 基础IO【中】
linux·运维·服务器
主角1 72 小时前
Linux系统安全
linux·运维·系统安全
翼龙云_cloud2 小时前
阿里云代理商:阿里云百炼视频混剪实战
服务器·阿里云·云计算
网硕互联的小客服2 小时前
CentOS 7 实现自动备份数据到百度网盘的具体步骤与方法
运维·服务器·网络·安全·自动化
1candobetter2 小时前
服务器公网访问策略与穿透方案汇总
运维·服务器
fetasty2 小时前
Android手机改造Linux服务器
linux·服务器
那就回到过去2 小时前
软考网络工程师第一章计算机网络的发展分类
网络·计算机网络·网络工程师·软考
这波不该贪内存的2 小时前
UDP与TCP:发送接收流程差异详解
网络·tcp/ip·udp
未来之窗软件服务2 小时前
服务器运维(四十七)鸿蒙系统Mongoose服务器伪请求pseudo http —东方仙盟
java·运维·服务器·服务器运维·仙盟创梦ide·东方仙盟