Linux C语言UDP Socket通信:服务器与客户端完整实现

Ivy
Ivy
发布时间:2026-10-13 | 5人浏览过
原创

本文通过完整的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编程实战的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表