UDP w C ======= Polecam taki `podręcznik programowania gniazd w C `_. Unicast/Broadcast ################# Serwer ------ .. code-block:: c #include #include #include #include #include #include #include #include #include #include #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``. .. code-block:: c 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. .. code-block:: c 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 ------ .. code-block:: c #include #include #include #include #include #include #include #include #include #include #include #include #include #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. .. code-block:: c 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``. .. code-block:: c 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 ------ .. code-block:: c #include #include #include #include #include #include #include #include #include #include #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. .. code-block:: c 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. .. code-block:: c 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: :download:`sk-examples.zip`