UDP made simple

本文详细描述了如何在C/Unix环境下编写一个简单的UDP客户端/服务器系统的步骤和代码解释,包括错误处理、socket API使用及注意事项。同时提供了客户端与服务器的实现代码,帮助开发者快速上手并理解UDP通信原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

UDP made simple

Abstract: This page describes how to write a simple UDP client/server system in a C/Unix environment. The code is explained step by step.

Motivation: I needed a page like this when working with a small test program for my master's thesis at Appius / Fält Communications. It is quite hard to remember all the socket API details off the top of your head, so I wanted a small reference page to get me started quickly without having to wade through tons of man pages. As I did not find a page I liked, I decided to write one. I hope it will be useful for others, too.

Caveats: The code is known to work under recent (fall 1999) versions of Linux. It should work on other Unices too, though some of the header files may be located in other directories on your system.

The server

     1  #include <arpa/inet.h>
     2  #include <netinet/in.h>
     3  #include <stdio.h>
     4  #include <sys/types.h>
     5  #include <sys/socket.h>
     6  #include <unistd.h>
     7
     8  #define BUFLEN 512
     9  #define NPACK 10
    10  #define PORT 9930
    11
    12  void diep(char *s)
    13  {
    14    perror(s);
    15    exit(1);
    16  }
    17
    18  int main(void)
    19  {
    20    struct sockaddr_in si_me, si_other;
    21    int s, i, slen=sizeof(si_other);
    22    char buf[BUFLEN];
    23
    24    if ((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1)
    25      diep("socket");
    26
    27    memset((char *) &si_me, 0, sizeof(si_me));
    28    si_me.sin_family = AF_INET;
    29    si_me.sin_port = htons(PORT);
    30    si_me.sin_addr.s_addr = htonl(INADDR_ANY);
    31    if (bind(s, &si_me, sizeof(si_me))==-1)
    32        diep("bind");
    33
    34    for (i=0; i<NPACK; i++) {
    35      if (recvfrom(s, buf, BUFLEN, 0, &si_other, &slen)==-1)
    36        diep("recvfrom()");
    37      printf("Received packet from %s:%d/nData: %s/n/n", 
    38             inet_ntoa(si_other.sin_addr), ntohs(si_other.sin_port), buf);
    39    }
    40
    41    close(s);
    42    return 0;
    43 }

Comments

  • Lines 8-10 define the buffer size (quite arbitrary), the number of packets to receive and the UDP port number to listen at. You could use any port number above 1023, although bind() will fail if someone else is using the same port simultaneously.
  • The function diep() is used for error handling.
  • 21: Declare receive buffer.
  • 22: sockaddr_in is a structure containing an Internet socket address. Basically, it contains:
    • an address family (always AF_INET for our purposes)
    • a port number
    • an IP address
    si_me defines the socket where the server will listen. si_other defines the socket at the other end of the link (that is, the client).
  • 24: Create a socket. AF_INET says that it will be an Internet socket. SOCK_DGRAM says that it will use datagram delivery instead of virtual circuits. IPPROTO_UDP says that it will use the UDP protocol (the standard transport layer protocol for datagrams in IP networks). Generally you can use zero for the last parameter; the kernel will figure out what protocol to use (in this case, it would choose IPPROTO_UDP anyway).
  • 27: We need to initialize the si_me structure. The first step is to fill it with binary zeroes, which is done on this line. (I doubt this step is actually necessary in modern Unix implementations, but better safe than sorry.)
  • 28: We will use Internet addresses.
  • 29: Here, the port number is defined. htons() ensures that the byte order is correct (Host TO Network order/Short integer).
  • 30: This line is used to tell what IP address we want to bind to. Most machines have more than one network interface (for example, 127.0.0.1 for the loopback interface and some other address for the network card; there may be more than one network card). In the general case, you want to accept packets from any interface, so you use INADDR_ANY instead of a specific address.
  • 31: Now we are ready to bind the socket to the address we created above. This line tells the system that the socket s should be bound to the address in si_me.
  • 35: This call says that we want to receive a packet from s, that the data should be put info buf, and that buf can store at most BUFLEN characters. The zero parameter says that no special flags should be used. Data about the sender should be stored in si_other, which has room for slen byte. Note that recvfrom() will set slen to the number of bytes actually stored. If you want to play safe, set slen to sizeof(si_other) after each call to recvfrom().
  • 37: The information about the sender we got from recvfrom() is displayed (IP:port), along with the data in the packet. inet_ntoa() takes a struct in_addr and converts it to a string in dot notation, which is rather useful if you want to display the address in a legible form.

The client

     1  #define SRV_IP "999.999.999.999"
     2  /* diep(), #includes and #defines like in the server */
     3
     4  int main(void)
     5  {
     6    struct sockaddr_in si_other;
     7    int s, i, slen=sizeof(si_other);
     8    char buf[BUFLEN];
     9
    10    if ((s=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP))==-1)
    11      diep("socket");
    12
    13    memset((char *) &si_other, 0, sizeof(si_other));
    14    si_other.sin_family = AF_INET;
    15    si_other.sin_port = htons(PORT);
    16    if (inet_aton(SRV_IP, &si_other.sin_addr)==0) {
    17      fprintf(stderr, "inet_aton() failed/n");
    18      exit(1);
    19    }
    20
    21    for (i=0; i<NPACK; i++) {
    22      printf("Sending packet %d/n", i);
    23      sprintf(buf, "This is packet %d/n", i);
    24      if (sendto(s, buf, BUFLEN, 0, &si_other, slen)==-1)
    25        diep("sendto()");
    26    }
    27
    28    close(s);
    29    return 0;
    30  }

Note: The client is quite similar to the server. Only the differences will be discussed.

  • 1: You need to know the IP address of the machine where the server runs. If you run the client and the server on the same machine, try 127.0.0.1. "999.999.999.999" is not a legal IP address; you need to substitute your own server's address.
  • 12: You may call bind() after the call to socket(), if you wish to specify which port and interface that should be used for the client socket. However, this is almost never necessary. The system will decide what port and interface to use.
  • 13-19: Here, we set up a sockaddr_in corresponding to the socket address where the server is listening. inet_aton() is used to convert a string in dotted-decimal ASCII notation to a binary address.
  • 24: Send BUFLEN bytes from buf to s, with no flags (0). The receiver is specified in si_other, which contains slen byte.

General tips

  • Remember to always check return values from system calls! By doing so, you will save time in the long run, I promise. Many people do not test return values in small quick-and-dirty test programs. However, in such cases it is especially important to check return values, because if you don't really know what you are doing you are much more likely to make a mistake. The checks help you understand what went wrong and why.
  • There is a tool called netcat (the actual command is nc) which is very useful for testing and debugging socket code. Check the man page if you are curious (of course, it might not be installed on your system).
  • If you want to cut and paste the code above, use cut -c9- to get rid of the line numbers. (The exact syntax of cut may be different on your system, and you may have to remove more or less than 9 characters.)
  • The command netstat can be useful to check which sockets are active. Try netstat -a.
  • For an overview over some of the structures used in socket programming, check out the code examples from lecture 13 on my course in Unix system programming. You will also find some material on TCP programming there. Disregard the initial material on select() and friends. There are some comments in Swedish, but most of the page is written in C.

 

<think>好的,我现在需要分析用户提供的代码片段,并给出相应的解释。用户给出的代码是关于UDP简单线程管理的静态变量,可能用于多线程环境中控制线程的创建和销毁。接下来我要逐步思考如何理解这些变量,并准确回答用户的问题。 首先,用户展示的代码有三个静态变量: 1. `static os_threadId udp_simple_default_thread_id;` 2. `static int udp_simple_thread_created_flag = 0;` 3. `static int udp_simple_thread_destroy_flag = 0;` 我的第一个任务是理解每个变量的作用。静态变量在C语言中具有文件作用域,生命周期从程序开始到结束,但作用域仅限于定义它们的文件。因此,这三个变量可能在某个源文件中用于管理UDP相关的线程状态。 接下来分析每个变量: 1. **`udp_simple_default_thread_id`**:类型是`os_threadId`,可能是一个线程ID的类型别名,用来存储线程的标识符。静态存储说明该变量在程序运行期间一直存在,用于记录某个默认线程的ID,可能在创建线程时被赋值,以便后续管理该线程(如等待其结束)。 2. **`udp_simple_thread_created_flag`**:初始化为0的整型变量。通常,这种标志变量用于表示某个事件是否已经发生。这里的变量名表明,当线程被创建后,这个标志可能被设置为1(或其他非零值),用来避免重复创建线程,或者用于检查线程是否已经初始化。 3. **`udp_simple_thread_destroy_flag`**:同样初始化为0的整型变量。根据变量名,这个标志可能在请求销毁线程时被设置,用来通知线程应该安全退出,或者在销毁完成后标记状态,防止重复销毁操作。 接下来需要考虑用户可能的背景和需求。用户之前询问了如何将UDP相关的结构体转换为KCP对应的结构体,现在又展示了这些线程管理的静态变量。可能用户正在开发一个基于UDP的网络应用,并希望引入KCP协议来增强传输可靠性,同时需要管理相关的线程。用户可能希望了解这些静态变量的作用,以便在转换到KCP时正确管理线程。 此外,用户可能希望了解这些变量在多线程环境中的正确使用方法,比如线程同步、避免竞态条件等。但用户当前的询问只是要求介绍这三个变量的作用,因此需要专注于解释每个变量的用途和可能的用法场景。 接下来需要验证我的理解是否正确。例如,`os_threadId`可能是某个操作系统或库定义的线程ID类型,比如在Windows中可能是`HANDLE`,而在POSIX系统中可能是`pthread_t`。静态变量意味着它们仅在当前文件可见,可能被用来在该文件内的函数之间共享线程状态信息。 标志变量通常用于控制线程的生命周期,比如创建标志确保线程只被创建一次,销毁标志用于通知线程退出循环。但需要注意,在多线程环境中,这些标志的访问可能需要同步机制(如互斥锁或原子操作)来避免竞态条件,否则可能导致未定义行为。例如,如果一个线程正在检查`udp_simple_thread_created_flag`的同时,另一个线程试图修改它,没有同步的话可能导致数据不一致。 但用户提供的代码中没有显示同步机制,这可能意味着这些变量在特定环境下使用,比如单线程中控制创建和销毁,或者用户代码中在其他地方有同步处理。不过,作为回答者,可能需要指出潜在的问题,比如多线程访问时的竞态条件,并建议适当的同步方法。 现在,我需要将这些分析整理成用户友好的解释,结构清晰,分点说明每个变量的作用,可能的用途,以及需要注意的问题。同时,用户之前的问题涉及KCP,但当前问题仅关于UDP的线程变量,因此不需要涉及KCP,除非用户有后续问题。 可能的回答结构如下: 1. 分别解释每个变量的作用: - `udp_simple_default_thread_id`:存储线程ID,用于后续操作。 - `udp_simple_thread_created_flag`:标记线程是否已创建,防止重复创建。 - `udp_simple_thread_destroy_flag`:标记线程是否应销毁,控制线程退出。 2. 说明它们的典型使用场景,比如在线程创建函数中设置ID和标志,在线程函数中检查销毁标志。 3. 提到多线程环境中可能的同步问题,建议使用互斥锁或原子操作。 此外,需要检查是否存在可能的错误或改进点。例如,标志变量用`int`类型,通常使用布尔类型更合适,但在C中可能使用`int`来兼容不同环境。另外,静态变量的命名方式是否合理,比如加前缀避免命名冲突,但用户代码中的命名已经具有描述性。 最后,确保回答符合用户的要求,即介绍这三个变量,可能不需要涉及具体如何实现线程函数,但可以简要说明它们如何协同工作。</think>### 变量解释: 以下是三个静态变量的作用及典型应用场景: --- #### 1. **`static os_threadId udp_simple_default_thread_id;`** - **作用**:存储 UDP 相关线程的线程 ID。 - **细节**: - `os_threadId` 可能是操作系统或框架定义的线程标识符类型(例如 Windows 中的 `HANDLE` 或 POSIX 中的 `pthread_t`)。 - 该变量用于记录某个默认线程的 ID,以便后续操作(如等待线程退出、发送信号等)。 - **典型用法**: ```c // 创建线程时赋值 udp_simple_default_thread_id = os_thread_create(thread_func, NULL); ``` --- #### 2. **`static int udp_simple_thread_created_flag = 0;`** - **作用**:标记 UDP 线程是否已创建,防止重复初始化。 - **细节**: - 初始值为 `0`,表示线程未创建。 - 当线程成功创建后,该标志会被设置为非零值(例如 `1`)。 - **典型用法**: ```c if (udp_simple_thread_created_flag == 0) { // 创建线程并设置标志 udp_simple_thread_created_flag = 1; } ``` --- #### 3. **`static int udp_simple_thread_destroy_flag = 0;`** - **作用**:标记线程是否需要销毁,用于安全终止线程。 - **细节**: - 初始值为 `0`,表示线程正常运行。 - 当需要终止线程时,该标志会被设置为非零值(例如 `1`)。 - 线程内部循环会定期检查此标志,若发现标记则退出循环并释放资源。 - **典型用法**: ```c // 线程函数内部 while (udp_simple_thread_destroy_flag == 0) { // 执行任务... } ``` --- ### 协作流程示例: 1. **初始化线程**: ```c if (udp_simple_thread_created_flag == 0) { udp_simple_default_thread_id = os_thread_create(udp_thread_func, NULL); udp_simple_thread_created_flag = 1; } ``` 2. **线程函数逻辑**: ```c void udp_thread_func(void *arg) { while (udp_simple_thread_destroy_flag == 0) { // 执行 UDP 数据收发或处理 } // 清理资源... } ``` 3. **终止线程**: ```c udp_simple_thread_destroy_flag = 1; os_thread_join(udp_simple_default_thread_id); // 等待线程退出 ``` --- ### 注意事项: 1. **多线程同步**: - 如果多个线程可能同时访问这些变量,需通过**互斥锁**或**原子操作**确保线程安全,避免竞态条件。 - 示例(使用互斥锁): ```c static os_mutex_t udp_mutex; // 全局互斥锁 // 修改标志时加锁 os_mutex_lock(&udp_mutex); udp_simple_thread_created_flag = 1; os_mutex_unlock(&udp_mutex); ``` 2. **标志变量的类型优化**: - 建议使用 `volatile` 修饰标志变量,防止编译器优化导致意外行为: ```c static volatile int udp_simple_thread_destroy_flag = 0; ``` 3. **资源泄漏风险**: - 确保在销毁线程后重置标志和线程 ID,避免重复释放或无效访问。 --- ### 扩展问题: 1. **如何检测线程是否仍在运行?** 可通过 `os_thread_alive(udp_simple_default_thread_id)`(假设有类似 API)判断线程状态。 2. **如何实现超时终止线程?** 在设置 `udp_simple_thread_destroy_flag = 1` 后,调用 `os_thread_join_with_timeout()`(若支持)等待线程退出。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值