Linux C语言UDP Socket通信:服务器与客户端完整实现
本文通过完整的C语言代码示例,展示如何在Linux系统中构建基于UDP协议的Socket网络通信程序。内容包含服务器端循环接收并回显数据,以及客户端定时发送自定义结构体数据并处理响应的逻辑,附带编译运行所需的命令行参数说明。
目标与准备
要实现基于UDP(User Datagram Protocol)的网络通信,我们需要两个独立的程序:一个监听特定端口等待数据的服务端,另一个向该端口发送数据的客户端。由于UDP是无连接协议,服务端需要预先绑定地址以便接收来自任何客户端的数据,而客户端则只需知道服务端的IP和端口即可进行通信。 以下示例将演示如何使用 Linux Socket API (sys/socket.h) 创建套接字、绑定地址收发数据。
第1步:编写并编译服务端代码
目的:创建一个持续运行的进程,用于监听指定端口,接收数据包并在终端打印十六进制形式的内容。
入口:新建文件 udp_server.c,粘贴服务端源码。
动作:
- 引入头文件:包括标准IO、内存操作、Socket系统及网络字节序转换相关的库。
- 创建套接字:调用
socket(AF_INET, SOCK_DGRAM, 0)。 - 配置地址选项:启用
SO_REUSEADDR允许端口快速重用。 - 绑定地址:将套接字与输入的IP和端口绑定。
- 接收循环:使用
recvfrom阻塞等待数据,成功后使用sendto原路返回(回显)。
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, char **argv)
{
if (argc != 3) {
printf("Usage: %s <server_ip> <server_port>\n", argv[0]);
return -1;
}
printf("server ip: %s\n", argv[1]);
printf("server port: %s\n", argv[2]);
int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
if (socketFd == -1) {
perror("socket");
exit(-1);
}
// 设置地址可复用,防止重启时Address already in use
int option = 1;
setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
struct sockaddr_in localAddr;
memset(&localAddr, 0, sizeof(localAddr));
localAddr.sin_family = AF_INET;
localAddr.sin_addr.s_addr = inet_addr(argv[1]);
localAddr.sin_port = htons(atoi(argv[2]));
if (bind(socketFd, (struct sockaddr *)&localAddr, sizeof(localAddr)) == -1) {
perror("bind");
close(socketFd);
exit(-1);
}
char tempBuff[2048] = {0};
while(1) {
struct sockaddr_in fromAddr;
socklen_t fromLen = sizeof(fromAddr);
ssize_t result = recvfrom(socketFd, tempBuff, sizeof(tempBuff), 0, (struct sockaddr *)&fromAddr, &fromLen);
if (result > 0) {
printf("recv data %s success. len: %zd\n", inet_ntoa(fromAddr.sin_addr), result);
for (ssize_t i = 0; i < result; i++) {
printf("%02x ", (unsigned char)tempBuff[i]);
if ((i+1)%16 == 0) printf("\n");
}
printf("\n");
// 将收到的数据原路发回
sendto(socketFd, tempBuff, result, 0, (struct sockaddr*)&fromAddr, sizeof(fromAddr));
} else if (result == -1 && errno != EINTR) {
perror("recvfrom");
}
}
close(socketFd);
}
编译命令:执行 gcc udp_server.c -o server。启动时需传入IP和端口,例如:./server 127.0.0.1 8888。
第2步:编写并编译客户端代码
目的:模拟周期性上报场景,构造特定的消息头部发送至服务端,并处理可能收到的回应。
入口:新建文件 udp_client.c,粘贴客户端源码。
动作:
- 定义自定义消息结构体
NSS_HEADER,包含版本号、类型和交易号等字段。 - 同样绑定本地地址(虽非必须,但有助于明确发送源端口)。
- 建立与服务端的连接信息 (
svrAddr)。 - 进入循环,递增交易号并调用
sendto发送 Header 数据。 - 紧接着调用
recvfrom尝试接收服务端的回包。
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
typedef struct _NSS_HEADER
{
unsigned short ProtocolVersion;
unsigned short MsgType;
unsigned short TransactionNo;
unsigned short PacketLength;
}NSS_HEADER;
int main(int argc, char **argv)
{
if (argc != 5) {
printf("Usage: %s <client_ip> <client_port> <server_ip> <server_port>\n", argv[0]);
return -1;
}
int socketFd = socket(AF_INET, SOCK_DGRAM, 0);
if (socketFd == -1) {
perror("socket");
exit(-1);
}
int option = 1;
setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &option, sizeof(option));
struct sockaddr_in localAddr;
memset(&localAddr, 0, sizeof(localAddr));
localAddr.sin_family = AF_INET;
localAddr.sin_addr.s_addr = inet_addr(argv[1]);
localAddr.sin_port = htons(atoi(argv[2]));
if (bind(socketFd, (struct sockaddr *)&localAddr, sizeof(localAddr)) == -1) {
perror("bind");
close(socketFd);
exit(-1);
}
// 服务器地址信息
struct sockaddr_in svrAddr;
memset(&svrAddr, 0, sizeof(svrAddr));
svrAddr.sin_family = AF_INET;
svrAddr.sin_addr.s_addr = inet_addr(argv[3]);
svrAddr.sin_port = htons(atoi(argv[4]));
NSS_HEADER hdr = {0};
while(1) {
hdr.TransactionNo++;
// 发送数据
ssize_t result = sendto(socketFd, &hdr, sizeof(NSS_HEADER), 0, (struct sockaddr*)&svrAddr, sizeof(svrAddr));
if (result > 0) {
printf("Send success. Len: %zd\n", result);
} else {
perror("sendto");
}
// 接收响应
char tempBuff[2048] = {0};
struct sockaddr_in fromAddr;
socklen_t fromLen = sizeof(fromAddr);
result = recvfrom(socketFd, tempBuff, sizeof(tempBuff), 0, (struct sockaddr *)&fromAddr, &fromLen);
if (result > 0) {
printf("Recv data from %s: %zd bytes\n", inet_ntoa(fromAddr.sin_addr), result);
} else if (result == -1) {
// 如果没有收到数据,忽略错误以免刷屏
continue;
}
sleep(60); // 每分钟发送一次
}
close(socketFd);
}
编译命令:执行 gcc udp_client.c -o client。启动时需传入客户端IP、端口及服务端IP、端口,例如:./client 127.0.0.1 9999 127.0.0.1 8888。
注意事项与排错
在调试上述代码时,可能会遇到以下常见问题:
- Address already in use:如果服务端频繁重启,可能会因为TCP/UDP TIME_WAIT状态导致端口占用。代码中已加入
setsockopt(..., SO_REUSEADDR, ...)解决此问题。 - 权限不足:若绑定的端口号小于
1024(如 80, 443),需要在 Linux 中以 Root 用户或sudo方式运行程序。 - 字节序问题:在网络传输中,多字节整数需要进行大小端转换。本例中使用
htons(Host to Network Short) 转换端口号,确保跨平台兼容性。若在结构体中包含其他整型数值作为业务数据传输,建议也加上htonl/ntohl转换。
以上就是在Linux中使用C语言实现基于UDP协议的Socket通信的详细内容,更多关于Linux Socket编程实战的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













