To jest stara wersja strony!
Programowanie połączeń sieciowych
DO PRZYGOTOWANIA
należy sobie przypomnieć użycie
fork()
WPROWADZENIE
Network Byte Order
Zapoznać się z następującymi pojęciami:
Network Byte Order
Big-Endian
Little-Endian
Zapoznać się z funkcjami: htonl, htons, ntohl, ntohs
getaddrinfo
Obsługa adresów IPv4 oraz IPv6
Gniazda
Gniazdo z ang. socket.
Są używane na wyższej, czwartej warstwie sieciowego modelu OSI/ISO.
Otwieranie ganiazd dokonuje się za pomocą funkcji socket(int domain, int type, int protocol).
Proszę przeczytać manual funkcji (man socket(3))
Adres IP identyfikuje hosta w danej sieci (podsieci), co identyfikuje numer portu
Czym różni się deskryptor gniazda od deskryptora pliku?
Odczytanie numeru portu na podstawie deskryptora dokonuje się za pomocą funkcji bind(int sockfd, struct sockaddr *my_addr, int addrlen).
Połączenie
Do nawiązywania połączeń wykorzystujemy funkcję connect(int sockfd, struct sockaddr *serv_addr, int addrlen)
sockfd - deskryptor gniazda
serv_addr - adres hosta docelowego, który możemy otrzymać przy pomocy funckji getaddrinfo
addrlen - długość adresu, najczęściej podaje się wartość addrinfo::ai_addrlen
Dopiero po pomyślnym nawiązaniu połączenia możemy używać sockfd do komunikowania się z serwerem.
Nasłuchiwanie
Rozpoczęcie nasłuchiwania nie wymaga użycia funkcji connect ponieważ to zdalny klient będzie jej używał do połączenia się z naszym serwerem.
Nasłuchiwanie można rozpocząć przy pomocy funkcji listen(int sockfd, int backlog)
Ostatnim krokiem rozpoczęcia komunikacji z klientem jest akceptacja jego próby połączenia. Dokonuje się tego za pomocą funkcji: accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
Funkcja accept(3) jako wartość zwraca nowy deskryptor gniazda który służy do komunikacji z akceptowanym połączeniem.
Wysyłanie/odbieranie danych
Wszystko w systemach GNU/Linux/Unix jest reprezentowane za pomocą plików - tak więc gniazda również.
Wysyłanie/odbieranie danych przez/z gniazd jest bardzo podobne do zapisu/odczytu danych do/z pliku.
Jest tak podobne że do tego celu można użyć funkcji
write(2),
read(2)
Jednak system oferuje funckje specjalizowane send(3), recv(3) które oferują dodatkową konfigurację.
Zamknięcie połączenia
Po zakończeniu wysyłania/odbierania danych należy zamknąć połączenie.
Zamknięcie połączenia reprezentowanego przez dany deskryptor można dokonać przy pomocy funkcji close(3)
Dla zainteresowanych: porównać funkcję close(3) z funkcją shutdown(3).
ĆWICZENIA
Telnet i usługi sieciowe
SMTP
POP3
HTTP
cd ; mkdir public_html ; chmod a+rx public_html ; chmod o+x . ; echo "Jestem $USER" > public_html/index.html
Wprowadzenie do gniazd
Programowanie gniazd
gethostbyname
Przeanalizować, skompilować i uruchomić program:
#include <stdio.h>
#include <errno.h>
#include <netdb.h> /* 4 gethostbyname, hostent structure */
#include <unistd.h> /* 4 exit */
#include <netinet/in.h> /* 4 ntohn */
int main(int argc, char *argv[])
{
int i,j;
struct hostent *he;
if (argc != 2)
{
fprintf(stderr,"usage: %s hostname\n", argv[0]);
return 1;
}
if ((he = gethostbyname(argv[1])) == NULL)
{
fprintf(stderr, "gethostbyname error\n");
return 1;
}
/* host info: */
printf("\nHost name: %s", he->h_name);
printf("\nAliases:");
for(i=0;he->h_aliases[i] != NULL;++i)
printf("\n%d. %s", i+1, he->h_aliases[i]);
if(he->h_addrtype == AF_INET)
printf("\nAddres type: IPv4");
if(he->h_addrtype == AF_INET6)
printf("\nAddres type: IPv6");
printf("\nAddress length: %d bytes", he->h_length);
printf("\nAddresses:");
for(j=0;j<he->h_length;++j)
{
printf("%d", (uint8_t)he->h_addr[j]);
if(j < (he->h_length-1))
printf(".");
}
printf("\n");
return 0;
}
Sprawdzić działanie programu dla
www.google.pl
oraz innych wybranych adresów symbolicznych.
Do powyższego kodu dopisać instrukcje które szczegółowo sprawdzają typ błędu funkcji gethostbyname i w zależności od tego wyświetlają odpowiedni komunikat.
Zmodyfikować tak program aby wyświetlał wszystkie adresy IP odnoszące się do podanego adresu.
Komunikator
Poniżej przedstawiony jest kod programu server.c
/*
** server.c -- a stream socket server demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#define MYPORT 3490 // the port users will be connecting to
#define BACKLOG 10 // how many pending connections queue will hold
void sigchld_handler(int s)
{
while(waitpid(-1, NULL, WNOHANG) > 0);
}
int main(void)
{
int sockfd, new_fd; // listen on sock_fd, new connection on new_fd
struct sockaddr_in my_addr; // my address information
struct sockaddr_in their_addr; // connector's address information
socklen_t sin_size;
struct sigaction sa;
int yes=1;
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
perror("socket");
exit(1);
}
if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) {
perror("setsockopt");
exit(1);
}
my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons(MYPORT); // short, network byte order
my_addr.sin_addr.s_addr = INADDR_ANY; // automatically fill with my IP
memset(my_addr.sin_zero, '\0', sizeof my_addr.sin_zero);
if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof my_addr) == -1) {
perror("bind");
exit(1);
}
if (listen(sockfd, BACKLOG) == -1) {
perror("listen");
exit(1);
}
sa.sa_handler = sigchld_handler; // reap all dead processes
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
if (sigaction(SIGCHLD, &sa, NULL) == -1) {
perror("sigaction");
exit(1);
}
while(1) { // main accept() loop
sin_size = sizeof their_addr;
if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {
perror("accept");
continue;
}
printf("server: got connection from %s\n",inet_ntoa(their_addr.sin_addr));
if (!fork()) { // this is the child process
close(sockfd); // child doesn't need the listener
if (send(new_fd, "Hello, world!\n", 14, 0) == -1)
perror("send");
close(new_fd);
exit(0);
}
close(new_fd); // parent doesn't need this
}
return 0;
}
Należy przerobić powyższy program tak aby działał jako server. Łącząc się za pomocą np. programu telnet program powinien umożliwiać prowadzenie dialogu jak popularne komunikatory internetowe (np. gg, tlen, itp).
Podpowiedź po akceptacji połączenia program powinien utworzyć dwa procesy potomne, jeden do czytania portu, drugi do pisania.
Transfer plików
Powyższy komunikator może być z łatwością zmodyfikowany aby zamiast tekstu wysyłał plik.
Zmodyfikować komunikator tak aby po nawiązaniu połączenia przez klienta rozpoczął wysyłanie pliku.
Zmodyfikować program client.c tak aby był zdolny odebrać i zapisać przesyłany plik.
Użycie Select
Dla poszerzenia wiedzy