UDP 网络编程
一、程序分析
1.1 程序结构
客户端 (02cli.c)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
typedef struct sockaddr *(SA); // 定义类型别名
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("sockfd");
return 1;
}
struct sockaddr_in ser; // 服务器地址
bzero(&ser, sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.14.128");
int i = 10;
while (i--)
{
char buf[512]="hello";
sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
bzero(buf,sizeof(buf));
recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
printf("recv:%s\n",buf);
sleep(1);
}
close(sockfd);
return 0;
}
服务器 (01server.c)
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
typedef struct sockaddr *(SA);
int main(int argc, char **argv)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (-1 == sockfd)
{
perror("socket");
return 1;
}
struct sockaddr_in ser, cli;
bzero(&ser, sizeof(ser));
bzero(&cli, sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr = inet_addr("192.168.14.128");
int ret = bind(sockfd, (SA)&ser, sizeof(ser));
if (-1 == ret)
{
perror("bind");
return 1;
}
int i = 10;
socklen_t len = sizeof(cli);
while (1)
{
char buf[512] = {0};
recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);
printf("recv:%s\n", buf);
time_t tm;
time(&tm);
struct tm *info = localtime(&tm);
sprintf(buf, "%s %d:%d:%d", buf, info->tm_hour, info->tm_min, info->tm_sec);
sendto(sockfd, buf, strlen(buf) + 1, 0, (SA)&cli, len);
}
close(sockfd);
return 0;
}
二、关键知识点解析
2.1 socket 创建
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
// AF_INET: IPv4 地址族
// SOCK_DGRAM: UDP 协议(无连接数据报)
// 0: 自动选择协议(UDP 协议对应 IPPROTO_UDP)
2.2 地址结构体
struct sockaddr_in {
sa_family_t sin_family; // 地址族,AF_INET
in_port_t sin_port; // 端口号
struct in_addr sin_addr; // IP 地址
unsigned char sin_zero[8]; // 填充字节
};
2.3 字节序转换
ser.sin_port = htons(50000); // 主机字节序转网络字节序(端口)
ser.sin_addr.s_addr = inet_addr("192.168.14.128"); // 字符串转网络字节序
2.4 绑定和发送
// 服务器绑定地址
bind(sockfd, (SA)&ser, sizeof(ser));
// 发送数据
sendto(sockfd, buf, strlen(buf), 0, (SA)&ser, sizeof(ser));
// 参数:socket描述符, 数据缓冲区, 数据长度, 标志, 目标地址, 地址长度
// 接收数据
recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);
// 参数:socket描述符, 缓冲区, 缓冲区大小, 标志, 来源地址, 地址长度指针
三、执行流程
3.1 服务器端流程
1. socket() 创建 UDP socket
2. bind() 绑定到 192.168.14.128:50000
3. while(1) 无限循环
├─ recvfrom() 接收客户端数据
├─ printf() 打印接收到的数据
├─ time() 获取当前时间
├─ sprintf() 在数据后附加时间
└─ sendto() 发送响应给客户端
3.2 客户端流程
1. socket() 创建 UDP socket
2. for(10次) 循环 10 次
├─ sendto() 发送 "hello" 到服务器
├─ recvfrom() 接收服务器响应
├─ printf() 打印接收的数据
└─ sleep(1) 等待 1 秒
3. close() 关闭 socket
四、代码特点
4.1 原始代码的特点
-
简洁直接:没有过多错误处理
-
固定配置:IP 和端口硬编码在代码中
-
简单通信:客户端发送固定字符串,服务器返回带时间戳的响应
-
类型别名 :使用
typedef struct sockaddr *(SA)简化代码
4.2 代码中的细节
// 客户端接收时忽略发送方信息
recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
// NULL 表示不关心发送方地址
// 服务器发送时包含字符串结束符
sendto(sockfd, buf, strlen(buf) + 1, 0, (SA)&cli, len);
// strlen(buf) + 1 包含 '\0' 结束符
// 客户端每次发送前都初始化为 "hello"
char buf[512]="hello"; // 每次循环重新初始化
// 服务器的时间处理
sprintf(buf, "%s %d:%d:%d", buf, ...);
// 注意:这里 buf 同时作为源和目标,可能产生未定义行为
五、运行结果示例
服务器输出:
recv:hello
recv:hello
recv:hello
...
(持续接收客户端消息)
客户端输出:
recv:hello 14:30:25
recv:hello 14:30:26
recv:hello 14:30:27
...
(接收到带时间戳的响应)
六、注意事项
-
IP地址 :代码中的
192.168.14.128需要根据实际情况修改 -
时间戳问题 :服务器的
sprintf(buf, "%s %d:%d:%d", buf, ...)可能存在逻辑问题 -
错误处理:实际应用中应增加更多的错误检查
-
端口号:50000 端口需要确保没有被占用
-
循环次数:客户端固定发送 10 次消息后退出