以下是对上述代码的详细解释:
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义 sockaddr 结构体类型别名 sa_t
typedef struct sockaddr sa_t;
// 定义 sockaddr_in 结构体类型别名 sin_t
typedef struct sockaddr_in sin_t;
int main(int argc, char** argv)
{
// 检查命令行参数是否足够
if (argc < 3)
{
// 输出使用说明,如果命令行参数不足,程序无法正常运行
fprintf(stderr, "Usage <%s servIP servPort>\n", argv[0]);
return -1;
}
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
{
// 如果套接字创建失败,输出错误信息并退出程序
perror("socket");
exit(-1);
}
// 2. 绑定服务器端地址信息到套接字上
sin_t server = {AF_INET};
// 将服务器端口号转换为网络字节序
server.sin_port = htons( atoi(argv[2]));
// 将服务器 IP 地址从点分十进制转换为网络字节序
server.sin_addr.s_addr = inet_addr(argv[1]);
int len = sizeof(sin_t);
if (-1 == bind(sockfd, (sa_t*)&server, len))
{
// 如果绑定失败,输出错误信息,关闭套接字并退出程序
perror("bind");
close(sockfd);
exit(-1);
}
while (1)
{
// 3. 接收客户端发来的网络数据
char buf[64] = {0};
sin_t peer = {0};
int n = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (sa_t*)&peer, &len);
// 在接收的数据末尾添加字符串结束符
buf[n] = '\0';
// 打印客户端的 IP 地址、端口号和发送的数据
printf("[%s:%d]发来数据:%s\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf);
// 4. 回复数据给客户端
const char* resp[] = {"继续努力", "百尺杆头", "百舸争流", "干的漂亮"};
int sz = sizeof resp / sizeof resp[0];
// 随机选择一个回复信息
int i = rand() % sz;
// 向客户端发送回复信息
sendto(sockfd, resp[i], strlen(resp[i]), 0, (sa_t*)&peer, len);
}
// 关闭套接字
close(sockfd);
return 0;
}
代码解释
-
头文件部分:
<unistd.h>
:提供了通用的文件、目录、进程等操作的接口,这里可能用于close
函数。<sys/types.h>
:包含了基本的系统数据类型。<sys/socket.h>
:包含了套接字相关的函数和结构体的声明,如socket
、bind
、recvfrom
、sendto
等。<netinet/in.h>
:包含了struct sockaddr_in
结构体,用于存储 IPv4 地址信息。<arpa/inet.h>
:包含了inet_addr
和inet_ntoa
等网络地址转换函数。<stdio.h>
:用于输入输出操作,如printf
和fprintf
。<stdlib.h>
:提供了一些标准的库函数,如exit
。<string.h>
:提供了字符串操作函数,如strlen
。
-
类型别名部分:
typedef struct sockaddr sa_t;
:将struct sockaddr
结构体类型定义为sa_t
,方便后续使用。typedef struct sockaddr_in sin_t;
:将struct sockaddr_in
结构体类型定义为sin_t
,方便后续使用。
-
主函数部分:
- 参数检查:
if (argc < 3)
:检查命令行参数数量,如果小于 3 个,输出使用说明并返回 -1。程序需要两个参数,即服务器的 IP 地址和端口号。
- 套接字创建:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
:创建一个 UDP 套接字。AF_INET
表示使用 IPv4 地址族,SOCK_DGRAM
表示使用数据报(UDP)协议,0
表示使用默认的协议。if (sockfd == -1)
:检查套接字创建是否失败,如果失败使用perror
输出错误信息并退出程序。
- 地址绑定:
sin_t server = {AF_INET};
:创建并初始化sin_t
结构体,将地址族设置为AF_INET
。server.sin_port = htons(atoi(argv[2]));
:将服务器端口号从主机字节序转换为网络字节序。server.sin_addr.s_addr = inet_addr(argv[1]);
:将服务器 IP 地址从点分十进制表示转换为网络字节序。int len = sizeof(sin_t);
:计算sin_t
结构体的长度。if (-1 == bind(sockfd, (sa_t*)&server, len))
:将服务器地址绑定到套接字上,如果绑定失败,输出错误信息,关闭套接字并退出程序。
- 数据接收和回复循环:
while (1)
:程序进入一个无限循环,持续处理客户端请求。char buf[64] = {0};
:创建一个 64 字节的接收缓冲区,并初始化为 0。sin_t peer = {0};
:创建一个sin_t
结构体来存储客户端的地址信息。int n = recvfrom(sockfd, buf, sizeof(buf)-1, 0, (sa_t*)&peer, &len);
:从套接字接收数据,存储在buf
中,sizeof(buf)-1
为接收数据的最大长度,0
表示无特殊标志,(sa_t*)&peer
存储发送者的地址,&len
存储地址长度。buf[n] = '\0';
:在接收的数据末尾添加字符串结束符。printf("[%s:%d]发来数据:%s\n", inet_ntoa(peer.sin_addr), ntohs(peer.sin_port), buf);
:打印客户端的 IP 地址、端口号和发送的数据。const char* resp[] = {"继续努力", "百尺杆头", "百舸争流", "干的漂亮"};
:定义一个回复信息数组。int sz = sizeof resp / sizeof resp[0];
:计算回复信息数组的元素个数。int i = rand() % sz;
:随机选择一个回复信息的索引。sendto(sockfd, resp[i], strlen(resp[i]), 0, (sa_t*)&peer, len);
:向客户端发送回复信息,包括选择的回复信息、信息长度、无特殊标志、客户端地址和地址长度。
- 参数检查:
-
套接字关闭:
close(sockfd);
:关闭套接字,释放资源。
注意事项
- 此代码实现了一个简单的 UDP 服务器,可接收客户端发送的数据并随机回复一条信息。
- 代码没有处理
recvfrom
或sendto
可能出现的错误情况,可使用perror
或其他方式处理错误。 - 代码使用了
rand()
函数但没有调用srand()
函数设置随机数种子,每次运行可能产生相同的随机数序列,可在main
函数开始处调用srand(time(NULL));
解决。 - 代码没有处理可能出现的缓冲区溢出问题,如客户端发送的数据超过 63 字节,会导致数据截断。
客户端完整代码
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct sockaddr sa_t;
typedef struct sockaddr_in sin_t;
int main(int argc,char** argv)
{
if(argc < 3)
{
fprintf(stderr,"Usage <%s servIP servPort>\n",argv[0]);
return -1;
}
/*1 创建数据报套接字*/
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd == -1)
{
perror("socket");
exit(-1);
}
/*2 绑定服务器端地址信息到套接字上*/
sin_t server = {AF_INET};
server.sin_port = htons( atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
int len = sizeof(sin_t);
if(-1 == bind(sockfd,(sa_t*)&server,len))
{
perror("bind");
close(sockfd);
exit(-1);
}
while(1)
{
/*3接收客户端发来的网络数据*/
char buf[64] = {0};
sin_t peer = {0};
int n = recvfrom(sockfd,buf,sizeof(buf)-1,0,(sa_t*)&peer,&len);
buf[n] = '\0';
printf("[%s:%d]发来数据:%s\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port),buf);
/*4.回复数据给客户端*/
const char* resp[] = {"继续努力","百尺杆头","百舸争流","干的漂亮"};
int sz = sizeof resp / sizeof resp[0];
int i = rand() % sz;
sendto(sockfd,resp[i],strlen(resp[i]),0,(sa_t*)&peer,len);
}
close(sockfd);
return 0;
}
客户端代码
以下是对这段代码的详细解释:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
typedef struct sockaddr sa_t;
typedef struct sockaddr_in sin_t;
int main(int argc, char** argv)
{
// 检查命令行参数是否足够
if (argc < 3)
{
// 输出使用说明,如果命令行参数不足,程序无法正常运行
fprintf(stderr, "Usage <%s servIP servPort>\n", argv[0]);
return -1;
}
// 定义缓冲区大小
#define BUFFER_SIZE 1024
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
{
// 如果套接字创建失败,输出错误信息并退出程序
perror("socket");
exit(-1);
}
// 声明和初始化 server_address
sin_t server_address = {0};
server_address.sin_family = AF_INET;
server_address.sin_port = htons(atoi(argv[2]));
if (inet_pton(AF_INET, argv[1], &server_address.sin_addr) <= 0)
{
// 如果 IP 地址转换失败,输出错误信息并退出程序
perror("inet_pton");
exit(EXIT_FAILURE);
}
// 声明 client_socket
int client_socket;
// 连接到服务器
client_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (client_socket == -1)
{
// 如果套接字创建失败,输出错误信息并退出程序
perror("socket");
exit(EXIT_FAILURE);
}
if (connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1)
{
// 如果连接失败,输出错误信息并退出程序
perror("connect");
exit(EXIT_FAILURE);
}
// 正确声明和初始化 len
socklen_t len = sizeof(sin_t);
while (1)
{
// 声明并初始化要发送的字符串
char *hello = "席甜千到此一游";
// 发送消息给服务端
send(client_socket, hello, strlen(hello), 0);
printf("Message sent to server: %s\n", hello);
// 声明接收数据的缓冲区
char buffer[BUFFER_SIZE];
// 接收服务端的信息
int valread = recv(client_socket, buffer, BUFFER_SIZE, 0);
if (valread == -1)
{
// 如果接收失败,输出错误信息并继续下一次循环
perror("recv");
continue;
}
buffer[valread] = '\0';
printf("Message received from server: %s\n", buffer);
// 错误:此处不应关闭 sockfd,因为后续可能还需要使用
// close(sockfd);
}
// 关闭客户端套接字
close(client_socket);
return 0;
}
代码解释
-
头文件部分:
<stdio.h>
:提供标准输入输出函数,如printf
和fprintf
。<stdlib.h>
:提供了一些标准的库函数,如exit
。<string.h>
:提供了字符串操作函数,如strlen
。<sys/socket.h>
:包含了套接字相关的函数和结构体的声明,如socket
、connect
、send
、recv
等。<arpa/inet.h>
:包含了inet_pton
等网络地址转换函数。<unistd.h>
:提供了close
等系统调用函数。
-
类型别名部分:
typedef struct sockaddr sa_t;
:将struct sockaddr
结构体类型定义为sa_t
,方便后续使用。typedef struct sockaddr_in sin_t;
:将struct sockaddr_in
结构体类型定义为sin_t
,方便后续使用。
-
主函数部分:
- 参数检查:
if (argc < 3)
:检查命令行参数数量,如果小于 3 个,输出使用说明并返回 -1。程序需要两个参数,即服务器的 IP 地址和端口号。
- 套接字创建:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
:创建一个 UDP 套接字。AF_INET
表示使用 IPv4 地址族,SOCK_DGRAM
表示使用数据报(UDP)协议,0
表示使用默认的协议。if (sockfd == -1)
:检查套接字创建是否失败,如果失败使用perror
输出错误信息并退出程序。
- 服务器地址初始化:
sin_t server_address = {0};
:创建并初始化sin_t
结构体,将地址族设置为AF_INET
。server_address.sin_port = htons(atoi(argv[2]));
:将服务器端口号从主机字节序转换为网络字节序。if (inet_pton(AF_INET, argv[1], &server_address.sin_addr) <= 0)
:将服务器 IP 地址从点分十进制表示转换为网络字节序,如果转换失败,输出错误信息并退出程序。
- 客户端套接字创建和连接:
int client_socket;
:声明一个客户端套接字变量。client_socket = socket(AF_INET, SOCK_DGRAM, 0);
:创建另一个 UDP 套接字作为客户端套接字。if (client_socket == -1)
:检查套接字创建是否失败,如果失败使用perror
输出错误信息并退出程序。if (connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1)
:尝试将客户端套接字连接到服务器地址,如果连接失败,输出错误信息并退出程序。
- 数据传输和接收循环:
socklen_t len = sizeof(sin_t);
:计算sin_t
结构体的长度。while (1)
:程序进入一个无限循环,持续发送和接收数据。char *hello = "席甜千到此一游";
:定义要发送的字符串。send(client_socket, hello, strlen(hello), 0);
:使用send
函数将字符串发送给服务器。printf("Message sent to server: %s\n", hello);
:打印发送的消息。char buffer[BUFFER_SIZE];
:创建一个接收缓冲区。int valread = recv(client_socket, buffer, BUFFER_SIZE, 0);
:接收服务器发送的数据。if (valread == -1)
:如果接收失败,输出错误信息并继续下一次循环。buffer[valread] = '\0';
:在接收的数据末尾添加字符串结束符。printf("Message received from server: %s\n", buffer);
:打印接收到的消息。
- 套接字关闭:
close(client_socket);
:关闭客户端套接字。
- 参数检查:
问题和优化点
- 代码中创建了两个 UDP 套接字
sockfd
和client_socket
,但sockfd
似乎没有被使用,可以考虑删除sockfd
的创建。 - 在
while
循环中错误地调用了close(sockfd);
,应该将其移除,因为sockfd
未被使用且可能导致后续操作失败。 - 代码没有处理
recv
函数返回 0 的情况,可能需要根据具体情况进行处理。 - 可以使用
#define
定义一些常量,如服务器端口号和 IP 地址,增强代码的可维护性。 - 可以添加更多的错误处理,如处理服务器不可达等情况。
在这里插入代码片
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
typedef struct sockaddr sa_t;
typedef struct sockaddr_in sin_t;
int main(int argc, char** argv)
{
if (argc < 3)
{
fprintf(stderr, "Usage <%s servIP servPort>\n", argv[0]);
return -1;
}
// 定义缓冲区大小
#define BUFFER_SIZE 1024
// 1. 创建数据报套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd == -1)
{
perror("socket");
exit(-1);
}
// 声明和初始化 server_address
sin_t server_address = {0};
server_address.sin_family = AF_INET;
server_address.sin_port = htons(atoi(argv[2]));
if (inet_pton(AF_INET, argv[1], &server_address.sin_addr) <= 0)
{
perror("inet_pton");
exit(EXIT_FAILURE);
}
// 声明 client_socket
int client_socket;
// 连接到服务器
client_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (client_socket == -1)
{
perror("socket");
exit(EXIT_FAILURE);
}
if (connect(client_socket, (struct sockaddr*)&server_address, sizeof(server_address)) == -1)
{
perror("connect");
exit(EXIT_FAILURE);
}
// 正确声明和初始化 len
socklen_t len = sizeof(sin_t);
while (1)
{
// 声明并初始化要发送的字符串
char *hello = "席甜千到此一游";
// 发送消息给服务端
send(client_socket, hello, strlen(hello), 0);
printf("Message sent to server: %s\n", hello);
// 声明接收数据的缓冲区
char buffer[BUFFER_SIZE];
// 接收服务端的信息
int valread = recv(client_socket, buffer, BUFFER_SIZE, 0);
if (valread == -1)
{
perror("recv");
continue;
}
buffer[valread] = '\0';
printf("Message received from server: %s\n", buffer);
close(sockfd);
}
/* close(sockfd); */
close(client_socket);
return 0;
}