Oprogramowywanie gniazd sieciowych za pomocą C/C++ jest zdecydowanie najdtudniejsza. Jeśli państwo chcecie napiać pracę domową w C, możecie spróbować napisać ją za pomocą BOOST:Asio.
#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 80
int main(int argc, char **argv)
{
char buffer[MAXRCVLEN + 1]; /* +1 so we can add null terminator */
int mysocket;
struct sockaddr_in dest;
struct hostent *hostinfo;
hostinfo = gethostbyname ("google.pl");
if (hostinfo == 0)
{
std::cerr << "Couldn't find google";
exit (1);
}else{
std::cout << "Found google" << std::endl;
}
mysocket = socket(AF_INET, SOCK_STREAM, 0);
memset(&dest, 0, sizeof(dest)); /* zero the struct */
dest.sin_family = AF_INET;
dest.sin_addr = *(struct in_addr *) hostinfo->h_addr; /* set destination IP number - localhost, 127.0.0.1*/
dest.sin_port = htons(PORTNUM); /* set destination port number */
connect(mysocket, (struct sockaddr *)&dest, sizeof(struct sockaddr));
std::string get = "GET / HTTP/1.0\n\n";
send(mysocket, get.c_str(), get.size(), 0);
len = recv(mysocket, buffer, MAXRCVLEN, 0);
/* We have to null terminate the received data ourselves */
buffer[len] = '\0';
std::cout << buffer << std::endl;
std::cout << "We have reclieved " << len << " bytes"<< std::endl;
close(mysocket);
return 0;
}
Domyślnie by połączyć się z serwerem musimy znać jego adres IP, by pobrać adres IP znając nazwę danego komputera należy wywołać funkcję: gethostbyname.
struct hostent *hostinfo;
hostinfo = gethostbyname ("google.pl");
Funkcja ta zwraca adres NULL (czyli 0), jeśli nie uda się wyznaczyć adresu IP.
Do stworzenia socketa służy funkcja socket.
mysocket = socket(AF_INET, SOCK_STREAM, 0);
Pierwszy argumend definuje którą warstwę sieci dany socket będzie wykożystywać, AF_INET oznacza wykorzystanie prokokołu IP.
Drugi rgument oznacza którą warstwę transportu SOCK_STREAM oznacza wykorzystanie protokoły TCP oraz abstrakcji strumieni danych którą on udostępnia.
Trzeci argument to pozwala wyspecufikować dodatkowo protokół.
Więcej na man socket.
Do nawiązywania połączenia służy funkcja connect:
connect(mysocket, (struct sockaddr *)&dest, sizeof(struct sockaddr));
Przyjmuje ona takie argumenty:
Note
Funkcja socket pozwala stworzyć gniazdo wielu protokołów warstwy sieci (np. IP czy Ipv6), które mają różne rodzaje adresowania (oraz różne długości adresów).
Stąd konieczność podania zarówno adresu, jak i określenia jego długości.
Do wysłania komunikatu służy funkcja send, przyjmuje ona:
Do wysłania komunikatu służy funkcja recv, przyjmuje ona:
Funkcja zwtaca ilość odczytanych bajtów (nie więcej niż długość bufora).
Serwer wysyłający Hello World pierwszej osobie która się doń połączy.
#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 <iostream>
#include <stdexcept>
int open_server_socket(std::string host, int port){
struct sockaddr_in myaddr ,clientaddr;
int sockid;
sockid=socket(AF_INET,SOCK_STREAM,0);
memset(&myaddr,'0',sizeof(myaddr));
myaddr.sin_family=AF_INET;
myaddr.sin_port=htons(5555);
myaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(sockid==-1)
{
throw std::runtime_error("Couldnt socket socket");
}
int len=sizeof(myaddr);
if(bind(sockid,( struct sockaddr*)&myaddr,len)==-1)
{
throw std::runtime_error("Couldnt bind socket");
}
if(listen(sockid,10)==-1)
{
throw std::runtime_error("Couldnt listen socket");
}
return sockid;
}
void handle_connection(int client_socket){
std::string hello_world = "Hello world\n";
send(client_socket, hello_world.c_str(), hello_world.size(), 0);
}
int main()
{
int sockid = open_server_socket("localhost", 5555);
int newsockid = accept(sockid,0,0);
handle_connection(newsockid);
}
W kliencie (żeby Państwa nie przeciążać) nie zawarłem obsługi błędów, tutaj jest ona już widoczna. metody bind, listen, socket zwracają -1 jeśli nastąpi błąd.
Tak jak kliencie.
By oznaczyć gniazdo jako gniazdo serwerowe należy wywołać funkcję bind, następnie oznaczamy gniazdo jako mające odbierać połączenia zapomocą listen.
Do odbierania połączenuia służy metoda accept. Zwraca ona inta reprezentującego nowe gniazdo, dające połączenie klienta z serweremn.
Na takim gnieździe można wykonywać juz send i recv.
Względem poprzedniego przykładu zmienia się funkcja main, oraz handle_connection:
int main()
{
int sockid = open_server_socket("localhost", 5555);
while(true){
int newsockid = accept(sockid,0,0);
handle_connection(newsockid);
}
}
teraz poszczególne połączenia wykonywane są w pętli.
W funkcji handle_connection dodajemy obsługę kończenia połączenia, oraz odpisywania na wiadomości:
std::string read_message(int client_socket){
char buffer = 0;
std::string result;
while(buffer!='\n'){
int recv_result = recv(client_socket, &buffer, 1, 0);
if (recv_result == 0){
throw std::runtime_error("Client closed socket earlier");
}
if(recv_result == -1){
throw std::runtime_error("Eroor reading from socket");
}
result+=buffer;
}
return result;
}
void handle_connection(int client_socket){
std::string hello_world = "Wpisz coś\n";
send(client_socket, hello_world.c_str(), hello_world.size(), 0);
while (true){
std::string message = read_message(client_socket);
if (message.find("END") != std::string::npos){
std::string msg = "Kończymy!\n";
send(client_socket, msg.c_str(), msg.size(), 0);
close(client_socket);
return;
}
send(client_socket, message.c_str(), message.size(), 0);
}
}
W porównaniu z poprzednim przykładem zmienia nam się tylko funkcja main.
int main()
{
int sockid = open_server_socket("localhost", 5555);
while(true){
int newsockid = accept(sockid,0,0);
int pid = fork();
if (pid == -1){
throw std::runtime_error("Forking error");
}
if (pid == 0){
handle_connection(newsockid);
return 0;
}
}
}
Teraz główna pętla serwera wykorzystuje funkcje fork funkcja fork działa w sposób następujący:
Logika działania funkcji main jest następująca: