Linux下使用poll函数编写UDP客户端、服务器程序

一、UDP服务器与客户端的区别

对于UDP服务器与客户端,两者都可以通过sendto和recvfrom函数收发数据,它们的主要区别是:

1.服务器一般是等待并响应来自客户端的请求,客户端则是主动发送请求并且等待服务器的响应。

2.服务器端要将地址和端口号绑定,如果不绑定就无法使用recvfrom函数接受数据(也就是说服务器需要调用bind函数将一个套接字与一个地址绑定,而客户端不需要)。所以对于UDP,其服务器与其说是服务端不如说是后发端。

所以如果阅读一份源码,要快速判断其是UDP服务器还是客户端,一个简单的方法是查看代码中是否调用了bind函数就可以了。

二、UDP是否可以使用select/poll/epoll

UDP是一种无连接的传输协议,因此通常情况下没有必要使用多路复用。UDP只是逐个接收数据段(数据包),并按照其规则进行处理,其并不关心单个数据包所属的任何特定数据流或数据包。但是UDP是可以使用select/poll/epoll的,某些开源软件,比如FFmpeg内部使用了这些多路复用来处理UDP。

三、使用poll函数编写UDP客户端、服务器程序

代码摘自:《IPV4 UDP server client program with Poll system call》

UDP客户端代码udpClient.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 1024

int client_socket = -1;

static void sigint_handler(int signo)
{
  (void)close(client_socket);
  sleep(2);
  (void)printf("Caught sigINT!\n");
   exit(EXIT_SUCCESS);
}

void validate_convert_port(
char *port_str,
struct sockaddr_in *sock_addr)
{
 int port;

 if (port_str == NULL) {
   perror("Invalid port_str\n");
   exit(EXIT_FAILURE);
 }

 if (sock_addr == NULL) {
   perror("Invalid sock_addr\n");
   exit(EXIT_FAILURE);
 }

 port = atoi(port_str);

 if (port == 0) {
   perror("Invalid port\n");
   exit(EXIT_FAILURE);
 }

 sock_addr->sin_port = htons(
 (uint16_t)port);
 printf("Port: %d\n",
 ntohs(sock_addr->sin_port));
}

void validate_convert_addr(
char *ip_str,
struct sockaddr_in *sock_addr)
{
  if (ip_str == NULL) {
   perror("Invalid ip_str\n");
   exit(EXIT_FAILURE);
 }

 if (sock_addr == NULL) {
   perror("Invalid sock_addr\n");
   exit(EXIT_FAILURE);
 }

 printf("IP Address: %s\n", ip_str);

 if (inet_pton(AF_INET, ip_str,
 &(sock_addr->sin_addr)) <= 0) {
    perror("Invalid address\n");
    exit(EXIT_FAILURE);
  }
}

void recv_data(char *buffer)
{
  int ret, len;

  len = recvfrom(client_socket,
  buffer, BUFFER_SIZE, 0, NULL, NULL);

  if (len > 0) {
    buffer[len] = '\0';
    (void)printf("Received: %s\n",
    buffer);

  } else if (len == 0) {
     printf("Connection closed\n");
     exit(EXIT_FAILURE);
   }
}

void register_signal_handler(
int signum,
void (*handler)(int))
{
  if (signal(signum, handler) == SIG_ERR)
  {
     printf("Cannot handle signal\n");
     exit(EXIT_FAILURE);
  }
}

int main(int argc, char *argv[])
{
  int ret, len;
  struct sockaddr_in
  server_addr;
  char buffer[BUFFER_SIZE];
  struct pollfd fds[1];
  char *str = "HI";

  register_signal_handler(SIGINT,
  sigint_handler);

  if (argc != 3) {
    printf("%s\n",
    argv[0]);
    exit(EXIT_FAILURE);
  }

  memset(&server_addr, 0,
  sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  validate_convert_port(argv[1],
  &server_addr);
  validate_convert_addr(argv[2],
  &server_addr);

  client_socket = socket(AF_INET,
                  SOCK_DGRAM,
                  IPPROTO_UDP);

  if (client_socket < 0) {
    perror("socket");
    return -1;
  }

  while (1) {
    ret = sendto(client_socket, str,
    strlen(str), 0,
    (struct sockaddr*)&server_addr,
    sizeof(server_addr));
    printf("sendbuffer = %s\n", str);

    if (ret < 0) {
       perror("send error\n");
       (void)close(client_socket);
       break;
    }

    fds[0].fd = client_socket;  
    fds[0].events = POLLIN;

    ret = poll(fds, 2, 1000);

    if (ret < 0) {
       perror("poll");
       (void)close(client_socket);
       break;
    }

    if (fds[0].revents & POLLIN) {
       recv_data(buffer);
    }
  }

  (void)close(client_socket);

  return 0;
}

UDP服务器代码udpServer.c:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BUFFER_SIZE 1024

int server_socket = -1;

static void sigint_handler(int signo)
{
  (void)close(server_socket);
  sleep(2);
  (void)printf("Caught sigINT!\n");
  exit(EXIT_SUCCESS);
}

void register_signal_handler(
int signum,
void (*handler)(int))
{
  if (signal(signum, handler) == SIG_ERR) {
     printf("Cannot handle signal\n");
     exit(EXIT_FAILURE);
  }
}

void validate_convert_port(
char *port_str,
struct sockaddr_in *sock_addr)
{
 int port;

 if (port_str == NULL) {
   perror("Invalid port_str\n");
   exit(EXIT_FAILURE);
 }

 if (sock_addr == NULL) {
   perror("Invalid sock_addr\n");
   exit(EXIT_FAILURE);
 }

 port = atoi(port_str);

 if (port == 0) {
     perror("Invalid port\n");
     exit(EXIT_FAILURE);
 }

 sock_addr->sin_port = htons(
 (uint16_t)port);
 printf("Port: %d\n",
 ntohs(sock_addr->sin_port));
}

void recv_send(
char *buffer,
struct sockaddr_in *client_addr)
{
  int len, ret;

  socklen_t client_addr_len = sizeof(
  client_addr);

  len = recvfrom(server_socket,
  buffer, BUFFER_SIZE, 0,
  (struct sockaddr*)&client_addr,
  &client_addr_len);

  if (len > 0) {
    buffer[len] = '\0';
    printf("Received: %s\n",
    buffer);

    memset(buffer, 0,
    sizeof(buffer));
    strncpy(buffer, "HELLO",
    strlen("HELLO") + 1);
    buffer[strlen(buffer) + 1] = '\0';

    ret = sendto(server_socket,
    buffer,
    strlen(buffer), 0,
    (struct sockaddr*)&client_addr,
    client_addr_len);

    if (ret < 0) {
       perror("sendto");
       exit(EXIT_FAILURE);
      }
    } else if (len < 0) {
        perror("recvfrom");
        exit(EXIT_FAILURE);
    }
    printf("Sentbuffer = %s\n",
    buffer);
}

int main(int argc, char *argv[])
{
  int ret;
  struct sockaddr_in
  server_addr,
  client_addr;
  char buffer[BUFFER_SIZE];
  struct pollfd fds[1];

  register_signal_handler(SIGINT,
  sigint_handler);

  if (argc != 2) {
   printf("%s ",
   argv[0]);
   exit(EXIT_FAILURE);
  }

  memset(&server_addr, 0,
  sizeof(server_addr));
  server_addr.sin_family = AF_INET;
  server_addr.sin_addr.s_addr =
  INADDR_ANY;
  validate_convert_port(argv[1],
  &server_addr);

  server_socket = socket(AF_INET,
                  SOCK_DGRAM,
                  IPPROTO_UDP);

  if (server_socket < 0) {
    perror("socket");
    return -1;
  }

  ret = bind(server_socket,
  (struct sockaddr*)&server_addr,
  sizeof(server_addr));

  if (ret < 0) {
    perror("bind");
    (void)close(server_socket);
    return -2;
  }

  printf("UDP listining\n");

  memset(fds, 0, sizeof(fds));
  fds[0].fd = server_socket;
  fds[0].events = POLLIN;

  while (1) {

    ret = poll(fds, 1, 1000);

    if (ret < 0) {
	perror("poll");
    	break; 
    }

    if (fds[0].revents & POLLIN) {
      recv_send(buffer, 
      &client_addr);
    }
  }

  (void)close(server_socket);

  return 0;
}

编译:

gcc udpClient.c -o udpClient -g
gcc udpServer.c -o udpServer -g

运行:

客户端执行命令:

./udpClient 1234 127.0.0.1

服务器执行命令:

./udpServer 1234

运行后两者即可通讯。

从上面的代码可以看出来,上述UDP客户端和服务器都使用了poll函数监视文件描述符是否可读。区别在于服务器调用了bind函数将套接字与地址绑定,而客户端没有调用bind函数而已。

四、参考

《UDP服务器与客户端之间的区别?》

《UDP协议为什么分客户端和服务器端》

你可能感兴趣的:(linux,udp,网络协议,网络)