Dodaj 'serwer.c'
This commit is contained in:
parent
ca6575e2d0
commit
e1b8c93fdb
398
serwer.c
Normal file
398
serwer.c
Normal file
@ -0,0 +1,398 @@
|
||||
/**
|
||||
* Implementacja wielowątkowe serwera obsługującego wiele klietów
|
||||
* protokół TCP
|
||||
* gniazda BSD
|
||||
* system Unix/Linux
|
||||
* testowany na serwerze lts.wmi***
|
||||
*
|
||||
* Tworzenie obiektu socketa dla serwera
|
||||
* Tworzenie obiektu gniazd dla klieta
|
||||
* 1. Wątki - obsługa polaczen od wielu klientow
|
||||
* 2. Pokazywanie listy dostępnych dla klienta plików
|
||||
* 3. Przesylanie za pomocą socketów wybranego przez klienta pliku
|
||||
*
|
||||
*
|
||||
* gcc serwer.c -lpthread -o S
|
||||
*/
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <netinet/in.h>
|
||||
#include <string.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <unistd.h>
|
||||
#include <stddef.h>
|
||||
#include <netdb.h>
|
||||
#include <dirent.h> // do listowania plikow w folderze
|
||||
#include<pthread.h> // do watkow; uwaga na komende klompilacji! [gcc serwer.c -lpthread -o S]
|
||||
#include <fcntl.h> // for open
|
||||
#include <unistd.h> // for close
|
||||
|
||||
|
||||
#define SERVER_PORT_NUMBER 12345
|
||||
#define BACKLOG 10 // ilosc dozwolonych połczeńw kolejce przychodzących
|
||||
#define BUFFER_SIZE 10000
|
||||
#define SOCKET_DATA_LENGTH 1024 // do obierania i wysylania danych recv() i send()
|
||||
|
||||
char BUF[BUFFER_SIZE]; //globalna zmienna na bufor
|
||||
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; //semafor do blokowania
|
||||
|
||||
|
||||
/********************* Deklaracje ****************************/
|
||||
int get_message_from_client(int client_socket);
|
||||
void * server_handler(void * client_socket);
|
||||
int accept_connection(int server_socket, struct sockaddr_in server_struct);
|
||||
void end_client_connection(int client_socket);
|
||||
|
||||
/*************************** Definicje /***************************/
|
||||
|
||||
struct sockaddr_in create_server_struct(int server_port){
|
||||
/* Zwraca obiekt gniazda serwera
|
||||
|
||||
struct sockaddr_in { // równoważna sockaddr stworzona przez programistów do łatwijeszej pracy
|
||||
short int sin_family; //rodzina adresów -- odpowiada sa_data z struct sockaddr
|
||||
unsigned short int sin_port; // numer portu; kolejnosć sieciowa
|
||||
struct in_addr sin_addr; //adres internetowy; sieciowa kolejność
|
||||
unsigned char siz_zero[8]; // - zerować memssetem dla zachowania rozmiaru struct sockaddr
|
||||
}
|
||||
|
||||
@server_address
|
||||
@server_port (int): numer portu serwera
|
||||
*/
|
||||
struct sockaddr_in server_struct;
|
||||
|
||||
server_struct.sin_family = AF_INET;
|
||||
server_struct.sin_port = htons(server_port); // używam wybranego przez siebie nr portu - ustawiajac na 0 pozwalam funkcji bind() wybrac losowo pot za mnie
|
||||
server_struct.sin_addr.s_addr = htonl(INADDR_ANY); // puryście mówią żeby owijać INADDR_ANY w funkcję htonl - Host-to-Network-Long dla pełnej przenośności kodu; INADDR_ANY - używa mojego adresu IP
|
||||
memset(&(server_struct.sin_zero), '\0', 8 ); //wyzerowuje reszte struktury
|
||||
|
||||
return server_struct;
|
||||
}
|
||||
|
||||
int create_socket_TCP(){
|
||||
/* buduje gniazdo TCP
|
||||
socket(int domain, int type, int protocol) - zwraca deskryptor pliku, bo w Liuksie wszystko jest plikiem
|
||||
gdzie: domain - doemna powinna być ustawiona na AF_INET
|
||||
type - mówi jądru jaki jest typ gniazda: SOCK_STREAM dla TCP / SOCK_DGRAM dla UDP
|
||||
protocol - ustawić na 0, żeby funckja socket() wybrała sama protokół bazują na parametrze type
|
||||
|
||||
@return int socket - desktyptor pliku
|
||||
*/
|
||||
return socket(AF_INET, SOCK_STREAM, 0);
|
||||
}
|
||||
|
||||
void bind_server_socket(int sdsocket, struct sockaddr_in myaddr){
|
||||
/* binduje gniazdo; UWAGA bind jest tylko z moją strukturą gniazda; nie z klienta łaczącego się zdalnie
|
||||
struktura sockaddr jest i sockaddr_in (in od internet) są równorzędne;
|
||||
ta druga powstała przez programitów aby ułatwić sobie pracę
|
||||
bind(int sockfd, struct sockaddr *myaddr, int addrlen);
|
||||
gdzie: int sockfd to gniado - deskrypor pliku zwrócony przez socet(); bo w Linuxie wszystko jest plikiem (zwykły int)
|
||||
struct sockaddr *myaddr - wskaźnikna strukturę która przechowuje informacje (dokłądniej porcie i adresie IP) o adresie gniazda dla różnych typów gniazda
|
||||
int addrlen - może być ustawiony na rozmiar structury typu sockaddr (jak powyżej): sizeof(struct sockaddr)
|
||||
|
||||
bind() w przypadku błędu zwraca -1 i ustawia odpowiednio `errno` na wartośc błędu
|
||||
nie jest totalnie potrzbeny gdy łączymy się ze zdalnym serwerem np telnetem wtedy sywtarczy tylko `connect`
|
||||
struct sockaddr {
|
||||
unsigned short sa_family; //rodzina adresów, AF_xxx
|
||||
char sa_data[14]; // 14-bajtowy adres protokołowy
|
||||
};
|
||||
struct sockaddr_in { // równoważna sockaddr stworzona przez programistów do łatwijeszej pracy
|
||||
short int sin_family; //rodzina adresów -- odpowiada sa_data z struct sockaddr
|
||||
unsigned short int sin_port; // numer portu; kolejnosć sieciowa
|
||||
struct in_addr sin_addr; //adres internetowy; sieciowa kolejność
|
||||
unsigned char siz_zero[8]; // - zerować memssetem dla zachowania rozmiaru struct sockaddr
|
||||
}
|
||||
@note: Używac tylko portów z przedziału [1024; 65535> jeśli nie sa zajęte przez inny program; chyba że jest się superuserem
|
||||
*/
|
||||
int addrlen = sizeof(struct sockaddr_in);
|
||||
|
||||
if(bind(sdsocket, (struct sockaddr* ) &myaddr, addrlen) < 0){
|
||||
//bind() w przypadku błedu zwraca -1
|
||||
printf("[ERROR] Bind sie nie pwoiodl :<\n");
|
||||
|
||||
exit(-1);
|
||||
}
|
||||
printf("Bind sie udał\n");
|
||||
}
|
||||
|
||||
void start_listen(int server_socket){
|
||||
/*Serwer rzpoczyna nasłuchiwanie
|
||||
listen(int sockfd, int backlog);
|
||||
gdzie: sockfd - deskryptor gniazda serwera zwrócony rzez socket()
|
||||
backlog - ilosć dozwolonych połaczęń w kolejsce przychodzacych
|
||||
listen() zwraca -1 w przypadku błedu i ustawia odpowiednio zmienna errno
|
||||
*/
|
||||
|
||||
if (listen(server_socket, BACKLOG) == -1){
|
||||
printf("[Error] Listen sie nie powiodl\n");
|
||||
exit(-1);
|
||||
}
|
||||
printf("Listen sie powiodl\n");
|
||||
}
|
||||
|
||||
int transfer_file_to_client(int client_socket, char* filename ){
|
||||
/*Przesyła wybrany plik do klienta*/
|
||||
|
||||
char file_data[SOCKET_DATA_LENGTH];
|
||||
|
||||
FILE *fs = fopen(filename, "r");
|
||||
if(fs == NULL)
|
||||
{
|
||||
printf("[ERROR] Brak pliku '%s'. :< \n", filename);
|
||||
return -2;
|
||||
}
|
||||
|
||||
bzero(file_data, SOCKET_DATA_LENGTH);
|
||||
|
||||
int fs_block_sz;
|
||||
|
||||
while((fs_block_sz = fread(file_data, sizeof(char), SOCKET_DATA_LENGTH, fs))>0)
|
||||
{
|
||||
if(send(client_socket, file_data, fs_block_sz, 0) < 0)
|
||||
{
|
||||
printf("[ERROR] Send nie wysłał pliku '%s' :<.\n", filename);
|
||||
return -1;
|
||||
}
|
||||
bzero(file_data, SOCKET_DATA_LENGTH);
|
||||
}
|
||||
printf("Udało sie wysłać plik '%s' do kleinta <3 \n", filename);
|
||||
return 1;
|
||||
|
||||
}
|
||||
|
||||
char* list_all_files_in_directory(){
|
||||
/*
|
||||
Zwraca listę plików jakie klient może pobrać
|
||||
- ograniczam sie do jednego folderu gdzie uruchamiany
|
||||
jest serwer bo nie chce dac mozliwosci wiekszego przegldana klientowi
|
||||
lub zmulenia spowodowanego wyswietlaniem zawartosci calego dysku
|
||||
*/
|
||||
|
||||
char *filename_list = malloc(sizeof(char) * 1024); //alokowanie zmiennej na całą liste plikow
|
||||
int filename_list_length = 0; // zmienna do sprawdzenia czy nie trzeba czasem rozszerzyc filename_list do przechowywania calej listy plikow
|
||||
DIR *d;
|
||||
struct dirent *dir;
|
||||
d = opendir(".");
|
||||
if (d) {
|
||||
while ((dir = readdir(d)) != NULL) {
|
||||
// printf("%s\n", dir->d_name);
|
||||
if (dir->d_type == DT_REG){ // bez tego wyswietla tez foldery a nie pliki
|
||||
// printf("%s\n", dir->d_name);
|
||||
|
||||
filename_list_length += sizeof(filename_list)/ sizeof(filename_list[0]);
|
||||
|
||||
if (filename_list_length >= SOCKET_DATA_LENGTH){
|
||||
//powiekszanie zmiennej
|
||||
filename_list = (char*)realloc(filename_list, (filename_list_length + 1024) * sizeof(char)); //rozszerzenie ablicy charów o 1024
|
||||
|
||||
if (! filename_list) { //jakby realloc sie nie udał
|
||||
perror("realloc");
|
||||
exit(-1);
|
||||
}
|
||||
}
|
||||
strcat(filename_list, dir->d_name);
|
||||
char tmp[] = "\n";
|
||||
// strcat(filename_list, (char*)'\n');
|
||||
strcat(filename_list, tmp);
|
||||
|
||||
}
|
||||
}
|
||||
closedir(d);
|
||||
}
|
||||
return filename_list;
|
||||
}
|
||||
|
||||
|
||||
int accept_connection(int server_socket, struct sockaddr_in server_struct){
|
||||
/* akcpetuje polaczenie na gniezdzie
|
||||
Czeka aż będzie jakieś połaczenie
|
||||
zwraca socket dla clienta
|
||||
*/
|
||||
|
||||
int client_socket;
|
||||
int struct_len = (sizeof(struct sockaddr_in));
|
||||
printf("Proba accept\n\n");
|
||||
|
||||
|
||||
client_socket = accept(server_socket, (struct sockaddr *)&server_struct, &struct_len);
|
||||
//dopoki klienci sie lacza tworz nowe watki dla klientow przychodzacych
|
||||
|
||||
if (client_socket < 0 ){ // nieudane accept()
|
||||
printf("[Error] Accept się nie powiódł\n");
|
||||
return -1; // nie wylaczam progeamu gdy np jeden z wielu klientow sie nie polaczyl
|
||||
}
|
||||
|
||||
return client_socket;
|
||||
}
|
||||
|
||||
int send_message_to_client(int client_socket, char * msg){
|
||||
/*Wysyła wiadomosc do klienta; czsami wiadomosc jest dluzsza
|
||||
@param int client_socket - gniazdo/deskryptor klienta
|
||||
@param char *msg wiadomosc do klienta(string)
|
||||
*/
|
||||
int send_length = send(client_socket,
|
||||
msg,
|
||||
strlen(msg), 0);
|
||||
|
||||
if (send_length == 0){
|
||||
printf("[Error] nie udało sie wysłac wiadomosci do klienta\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
char* find_filename_in_message(char* client_message){
|
||||
/*znajduje nazwe pliku pomiedzy znakami <nazwa_pliku.rozszerzenie>
|
||||
i ją zwraca - do komunikacji z klientem by przeslac mu wybrany przez siebie plik
|
||||
zakłdam że znaki < - informujacy o początku nazwy pliku
|
||||
i znak `>` informujacy że ońcyz się nazwa pliku ystępuje tylko raz;
|
||||
gdyby zakonczenia wystepowały wielokrotnie należy użyć `str.find_last_of(STOPDELIMITER);`
|
||||
*/
|
||||
client_message = "[3]<a.py>";
|
||||
int message_len = strlen(client_message);
|
||||
int start = -1; //poczatkowy indeks
|
||||
int stop = -1; //koncowy indeks
|
||||
|
||||
int i =0;
|
||||
while(i < message_len){
|
||||
if(client_message[i] == '<'){
|
||||
start = i+1;
|
||||
}
|
||||
if(client_message[i] == '>'){
|
||||
stop = i-1;
|
||||
}
|
||||
i++;
|
||||
}
|
||||
|
||||
char* filename = malloc(sizeof(char)*(stop-start+1)); //na razie alokuje na jeden znak
|
||||
int j = start;
|
||||
for(j; j<= stop; j++ ){
|
||||
filename[j-start] = client_message[j];
|
||||
}
|
||||
filename[stop] = '\0';
|
||||
|
||||
return filename;
|
||||
}
|
||||
|
||||
int get_message_from_client(int client_socket){ //Cała komunikacja i sterowanie z kliente,
|
||||
/* Odbiera wiadomosc od klienta; sprawdza tresc -
|
||||
Klient wysyła pojedyncze wiadomosci lub `[2]<nazwa_pliku.rozszerzenie>`
|
||||
dlatego nie zwiększam bufora response_msg
|
||||
jeśli klient wysłał widomosc o tresci [3] zamyka połączenie z tym klientem
|
||||
|
||||
jesli sa tam slowa kluczowe opcji to je wywoluje
|
||||
@param int client_socket - gniazdo/deskryptor klienta
|
||||
*/
|
||||
|
||||
char *response_msg = malloc( sizeof(char) * SOCKET_DATA_LENGTH);
|
||||
|
||||
while(1) {
|
||||
// spawdza występowanie słowa np `[1]`, `[2]`, `[3]` w odpowiedzi klienta
|
||||
printf("[Klient]: %s\n", response_msg);
|
||||
recv(client_socket, response_msg, SOCKET_DATA_LENGTH, 0 );
|
||||
|
||||
if ( strstr(response_msg, "[1]")){
|
||||
printf("TUTAJ [1]");
|
||||
// jeśli klietn wybrał pierszą opcję to wysyła klientowi liste plikow
|
||||
char* filename_list = list_all_files_in_directory();
|
||||
send_message_to_client(client_socket, filename_list);
|
||||
}
|
||||
|
||||
if ( strstr(response_msg, "[2]")){
|
||||
send_message_to_client(client_socket, "Przesylanie pliku");
|
||||
// sprawdzanie nazwy pliku
|
||||
char* filename = find_filename_in_message(response_msg);
|
||||
transfer_file_to_client( client_socket, filename );
|
||||
}
|
||||
|
||||
if ( strstr(response_msg, "[3]")){
|
||||
end_client_connection(client_socket);
|
||||
return 1;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
end_client_connection(client_socket);
|
||||
return 1;
|
||||
}
|
||||
|
||||
void end_client_connection(int client_socket){
|
||||
/*Zamyka polaczenie z klientem*/
|
||||
printf("Zakoczenie polaczenia z klientem\n");
|
||||
send_message_to_client(client_socket, "[Serwer] Połaczenie zamkniete przez klienta");
|
||||
close(client_socket);
|
||||
|
||||
}
|
||||
|
||||
|
||||
void* server_handler(void * thread_socket){ //Najrrudniejsza funkcja [!]
|
||||
/* Handler dla watku klienta
|
||||
w watkach przekazywanie warosic jest trudniejsze i dziwniejsze
|
||||
@ param: thread_socket - socket klienta do watku zwrocony przez accept() z accept_connection()
|
||||
*/
|
||||
|
||||
// pthread_mutex_lock(&lock); //niepotrzebny nie wspoldziele danych miedzy klientami
|
||||
|
||||
static char message_from_client[SOCKET_DATA_LENGTH]; // iadomosc od klienta
|
||||
int read_size, write_size;
|
||||
int client_socket = *(int*) thread_socket; // client_socket zostanie zamkniety tutaj
|
||||
printf("Funkcja Server handler\n");
|
||||
|
||||
|
||||
//wyslanie takiego mojego ACK Z serwera DO klienta
|
||||
send_message_to_client(client_socket, "Serwer wita :)\n");
|
||||
|
||||
if (get_message_from_client(client_socket)){
|
||||
printf("Klient sam sie rozaczyl\n");
|
||||
pthread_exit(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
if (client_socket){
|
||||
end_client_connection(client_socket); // zamykanie po zakonczeniue komunikacji jak sie nie uda w get_message_from_client
|
||||
}
|
||||
pthread_exit(NULL);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(){
|
||||
printf("Czekam na połaczenie \n\n\n");
|
||||
struct sockaddr_in myaddr, endpoint;
|
||||
int server_socket, client_socket;
|
||||
int server_port = SERVER_PORT_NUMBER; // na sztywno ustawiono dla szybszego testowania/ bez inputu
|
||||
|
||||
server_socket = create_socket_TCP(); //tworzenie gnaizda dla serwera
|
||||
myaddr = create_server_struct(server_port); //struktura przechowujaca dane serwera
|
||||
|
||||
bind_server_socket(server_socket, myaddr); //1. Bindowanie
|
||||
start_listen(server_socket); //2. Rozpoczenie nasluchu
|
||||
|
||||
// Akceptacja przychodzace polaczenie klienta
|
||||
pthread_t clients_thread_list[20];
|
||||
int i = 0; //licznik watkow; i jakos mi bardziej pasuje jak Licznik
|
||||
while(1){
|
||||
//Pętla główna dla serwera do akceptowania klientow i
|
||||
client_socket = accept_connection(server_socket, myaddr); //zawiesza program aż połączy się klient
|
||||
|
||||
// mu
|
||||
if( pthread_create(&clients_thread_list[i], NULL, server_handler, &client_socket) != 0 )
|
||||
printf("Failed to create thread\n");
|
||||
if( i >= BACKLOG) //gdzie BACKLOG to parametr z funkcji listen()
|
||||
//mowiąca o maksymalnej liczbie połaczeń w kolejnce; po cichu z reguły ustawiona na 5 lub 10
|
||||
{
|
||||
i = 0; //licznik watkow
|
||||
while(i < BACKLOG){
|
||||
// musi byc joinowanie w watku glownym, bo inaczej main nie czeka na zakonczenie dzialania watku klietna
|
||||
pthread_join(clients_thread_list[i++],NULL); //joinowanie watkow w glownym watku main
|
||||
}
|
||||
i = 0; //licznik watkow
|
||||
}
|
||||
}
|
||||
|
||||
close(server_socket); //zamykanie serwera
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user