Polecam taki podręcznik programowania gniazd w C.
#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);
}
}
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.
#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
}
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);
}
}
#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);
}
}
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ę.
Bez zmian (starczy wysłać komunikat na adres grupy (i właściwy port)).
Dodatkowe przykłady do pobrania stąd: sk-examples.zip