UDP w C

Polecam taki podręcznik programowania gniazd w C.

Unicast/Broadcast

Serwer

#include<signal.h>
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/types.h>
#include<stdlib.h>
#include <unistd.h>

#include <iostream>
#include <stdexcept>

#define BUFSIZE 2048
#define PORT    9000

int main(int argv, char **argc){
    struct sockaddr_in myaddr;
    struct sockaddr_in remoteaddr;
    socklen_t addr_len;
    int recvlen;
    int fd;
    unsigned char buf[BUFSIZE+1];
    const char* response = "OK";

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("cannot create socket\n");
        return 1;
    }

    memset((char *)&myaddr, 0, sizeof(myaddr));
    myaddr.sin_family = AF_INET;
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    myaddr.sin_port = htons(PORT);

    if (bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr)) < 0) {
        perror("bind failed");
        return 2;
    }

    while(true){
        printf("waiting on port %d\n", PORT);
        memset((char *)&remoteaddr, 0, sizeof(remoteaddr));
        addr_len = sizeof(remoteaddr);
        recvlen = recvfrom(fd, buf, BUFSIZE, 0, (struct sockaddr *)&remoteaddr, &addr_len);
        if (recvlen < 0){
         perror("Error while receiving data");
        }
        printf("received %d bytes\n", recvlen);
        if (recvlen > 0) {
         buf[recvlen] = 0;
         printf("received message: \"%s\"\n", buf);
        }
        printf("remote host: %d\n", remoteaddr.sin_addr);
        printf("Sending response\n");
        sendto(fd, "OK", sizeof("OK"), 0, (struct sockaddr *)&remoteaddr, addr_len);
    }

}

Ważniejsze miejsca programu

Stworzenie gniazda, zamiast SOCK_STREAM wykorzystujemy SOCK_DGRAM — co oznacza skorzystanie z protokołu UDP.

fd = socket(AF_INET, SOCK_DGRAM, 0);

Odebranie danych, przy odbieraniu danych musimy skorzystać z funkcji recvfrom, która pozwala poznać adres komputera z którego nadchodzi pakiet.

memset((char *)&remoteaddr, 0, sizeof(remoteaddr));
addr_len = sizeof(remoteaddr);
recvlen = recvfrom(fd, buf, BUFSIZE, 0, (struct sockaddr *)&remoteaddr, &addr_len);

po wykonaniu tej funkcji w zmiennej remoteaddr znajdować się będzie adres z którego przyszedł komunikat.

Wysłanie danych

Tutaj musimy skorzystać z funkcji sendto która pozwala wysłać dane do zadanego celu. Przyjmuje ona adres uzyskany z recvfrom.

Klient

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netdb.h>
#include <string>

#include <iostream>

#define MAXRCVLEN 1000
#define PORTNUM 9000

int do_client(char * addr, int bcast){
    char buffer[MAXRCVLEN + 1]; /* +1 so we can add null terminator */
    int len, mysocket;
    struct sockaddr_in dest;

    struct hostent *hostinfo;

    hostinfo = gethostbyname ("192.168.1.70");

    if (hostinfo == 0)
    {
        perror("Couldn't find destination address\n");
        exit (1);
    }else{
        printf("Found destination address\n");
    }

    mysocket = socket(AF_INET, SOCK_DGRAM, 0);

    if (mysocket < 0){
       perror("Socket creation failed");
       exit(3);
    }

    struct timeval timeout;
    timeout.tv_sec = 1;
    timeout.tv_usec = 0;

    if (setsockopt (mysocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
               sizeof(timeout)) < 0){
       perror("setsockopt failed\n");
       exit(2);
    }

    if(bcast){
        int optval = 1;
        int response = setsockopt(mysocket, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
        if (response < 0){
           perror("setsockopt failed (for broadcast)\n");
           exit(2);
        }
    }

    memset(&dest, 0, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_addr = *(struct in_addr *) hostinfo->h_addr;
    dest.sin_port = htons(PORTNUM);

    std::string message = "Hello World";

    sendto(mysocket, message.c_str(), message.size(), 0, (const sockaddr*) &dest, sizeof(dest));

    len = recv(mysocket, buffer, MAXRCVLEN, 0);

    buffer[len] = '\0';

    if(len > 0){
       printf("response:\n");
       printf(buffer);
    }else{
       printf("No response\n");
    }

    close(mysocket);
    return 0;
}


int main(int argc, char **argv)
{
    char* addr = "192.168.1.70";
    char* bcast_addr ="192.168.1.255";
//  do_client(addr, 0); // Bez broadcasta
    do_client(bcast_addr, 1); // Z broadcastem

}

Ważniejsze miejsca programu

Ustawienie maksymalnego czasu oczekiwania

W TCP mogliśmy czekać na odpowiedź od serwera dowolnie długo — jeśli serwer ją wysłał to prędzej czy później dotrze ona do nas.

W UDP nie ma tego komfortu — pakiet może po drodze zniknąć, więc musimy ustawić maksymalny czas oczekiwania na dane (jest to czas po jakim funkcja recv zgłosi błąd jeśli nie otrzyma danych).

Do konfiguracji gniazd w Linuksie służy funkcja setsockopt, wywołana tak jak w przykładzie ustawia ona okres oczekiwania gniazda.

struct timeval timeout;
timeout.tv_sec = 1;
timeout.tv_usec = 0;

if (setsockopt (mysocket, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
       sizeof(timeout)) < 0){
    perror("setsockopt failed\n");
    exit(2);
}

Umożliwienie wysyłania broadcastów

Domyślnie gniazda nie pozwalają na wysłanie broadcastów. By to umożliwić musimy po raz kolejny skorzystać z funkcji setsockopt.

if(bcast){
    int optval = 1;
    int response = setsockopt(mysocket, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));
    if (response < 0){
       perror("setsockopt failed (for broadcast)\n");
       exit(2);
    }
}

Multicast

Serwer

#include<signal.h>
#include<stdio.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/types.h>
#include<stdlib.h>
#include <unistd.h>


#include <iostream>
#include <stdexcept>

#define BUFSIZE 2048
#define PORT    9000

int main(int argv, char **argc){
    struct sockaddr_in listenaddr;
    struct sockaddr_in remoteaddr;
    struct  ip_mreq group_addr;
    socklen_t addr_len;


    memset((char *)&listenaddr, 0, sizeof(listenaddr));
    listenaddr.sin_family = AF_INET;
    listenaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    listenaddr.sin_port = htons(PORT);

    int recvlen;
    int fd;
    unsigned char buf[BUFSIZE+1];
    const char* response = "OK";

    fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        perror("cannot create socket\n");
        return 1;
    }

    if(bind(fd, (const sockaddr*)&listenaddr, sizeof(listenaddr))<0){
        perror("Error while  binding");
        exit(8);
    }

    group_addr.imr_multiaddr.s_addr = inet_addr("224.3.29.71");
    group_addr.imr_interface.s_addr = htonl(INADDR_ANY);

    if (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group_addr, sizeof(group_addr))<0){
        perror("Couldn't listen to group");
        exit(1);
    }

    while(true){
        printf("waiting on port %d\n", PORT);
        memset((char *)&remoteaddr, 0, sizeof(remoteaddr));
        addr_len = sizeof(remoteaddr);
        recvlen = recvfrom(fd, buf, BUFSIZE, 0, (struct sockaddr *)&remoteaddr, &addr_len);
        if (recvlen < 0){
         perror("Error while receiving data");
        }
        printf("received %d bytes\n", recvlen);
        if (recvlen > 0) {
         buf[recvlen] = 0;
         printf("received message: \"%s\"\n", buf);
        }
        printf("remote host: %d\n", remoteaddr.sin_addr);
        printf("Sending response\n");
        sendto(fd, "OK", sizeof("OK"), 0, (struct sockaddr *)&remoteaddr, addr_len);
    }

}

Ważniejsze miejsca programu

Nasłuchiwanie połączeń

Mimo że nasz serwer nie nasłuchuje na żadnym IP (będzie słuchał wiadomości z grupy!) musimy wywołać funkcję bind, ale bez ustawiania żadnego konkretnego adresu.

memset((char *)&listenaddr, 0, sizeof(listenaddr));
listenaddr.sin_family = AF_INET;
listenaddr.sin_addr.s_addr = htonl(INADDR_ANY);
listenaddr.sin_port = htons(PORT);

if(bind(fd, (const sockaddr*)&listenaddr, sizeof(listenaddr))<0){
    perror("Error while listening to group.");
    exit(8);
}

Włączenia nasłuchiwania

Do nasłuchiwania na grupie multicast należy wywowołać funkcję setsockopt z parametrem IP_ADD_MEMBERSHIP, wartością tej opcji jest struktura ip_mreq zawierająca dwa pola. Określają one adres grupy oraz inferfejs na którym nasłuchiwaliśmy.

struct  ip_mreq group_addr;

group_addr.imr_multiaddr.s_addr = inet_addr("224.3.29.71");
group_addr.imr_interface.s_addr = htonl(INADDR_ANY);

if (setsockopt(fd, SOL_SOCKET, IP_ADD_MEMBERSHIP, &group_addr, sizeof(group_addr))<0){
    perror("Couldn't listen to group");
    exit(1);
}

Wyłączenie nasłuchiwania

W zasadzie powinniśmy wysłać IP_DROP_MEMBERSHIP, która odłączy nas od grupy multicastu. Jednak po zamknięciu gniazda (albo wyłączeniu programu — na jedno wychodzi) jądro linuksa samo wykona tą operację.

Klient

Bez zmian (starczy wysłać komunikat na adres grupy (i właściwy port)).

Dodatkowe przykłady

Dodatkowe przykłady do pobrania stąd: sk-examples.zip

Table Of Contents

Previous topic

UDP w Bashu

Next topic

UDP w Pythonie

This Page