Úvod
Programování TCP v C je rozdílné ve Windows a Linuxu. Existují wrappery a různé knihovny, které funkcionalitu sjednocují a zlepšují. Rozdíly jsou ale tak malé, že by mohli být řešeny preprocesorem, které by mělo několik výhod:
- Kód se může psát nativně pro jednu platformu.
- Direktivy budou krátké a jednoduché.
- Zkompilovaný bude rychlý, protože nebude žádná další režie (wrappery).
Hlavičkové soubory
Každý systém má vlastní sadu hlavičkových souborů, které jsou nutné pro funkcionalitu socketů:
Windows
Existuje několik možných hlavičkových souborů mimo tento (windows.h, Winsock.h).
#include <WinSock2.h>
Linux
#include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h>
Porovnání funkcí
Porovnával jsem následující funkce:
- socket
- bind
- listen
- accept
- send
- recv
- close (closesocket)
Funkce socket
Definice
Windows
SOCKET socket(int af, int type, int protocol);
Linux
int socket(int domain, int type, int protocol);
Parametry
Parametry jsou stejného datového typu.
Návratová hodnota
Z definice funkce se zdá, že jediným rozdílem je návratová hodnota, která je ve Windows typu SOCKET a linuxu typu int.
Ve winsock.h je SOCKET definován takto:
typedef u_int SOCKET;A u_int následně takto:
typedef unsigned int u_int;Je to tedy unsigned int a int.
Úspěch:
V případě úspěchu funkce vrací kladné číslo vyjadřující deskriptor daného socketu pro oba operační systémy.
Chybový stav:
Windows
Vrací konstantu INVALID_SOCKET, která je definována:#define INVALID_SOCKET (SOCKET)(~0)
Linux
Vrací -1, což je binární vyjádření hodnoty ~0Funkce bind
Definice
Windows
int bind(SOCKET s, const struct sockaddr *name, int namelen);
Linux
int bind(int s, struct sockaddr *my_addr, socklen_t addrlen);
Parametry
První parametr je rozdílný - SOCKET vs int. Rozdíly byly popsány u návratové hodnoty funkce socket.
V linuxu je třetí parametr typu socklen_t, ve windows je to int. Parametr obsahuje velikost truktury sockaddr* druhého parametru. Pokud ho získáme pomocí funkce sizeof("2. parametr"), v linuxu se vrátí datový typ kompatibilní s socketlen_t (nenašel jsem přesnou dokumentaci) a program se bez problémů zkompiluje.
Návratová hodnota
Návratová hodnota je 0 v případě úspěchu. V případě neúspěchu je v linuxu vrácena hodnota -1 a ve windows konstanta SOCKET_ERROR, která je taky -1.Funkce listen
Definice
Windows
int listen(SOCKET s, int backlog);
Linux
int listen(int s, int backlog);
Parametry
Podobně jako u funkce bind je zde rozdílný datový typ pro socket.
Funkce accept
Definice
Windows
SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);
Linux
int accept(int s, struct sockaddr *addr, socklen_t *address_len);
Parametry
Podobně jako u funkce bind je zde rozdílný datový typ pro socket. Další parametry jsou stejné jako u funkce bind s tím rozdílem, že tentokrát se posílají pointery.
Zde může nastat problém, protože některé překladače v linuxu nepovolí třetí parametr typu int bez explicitního přetypování (g++).
Návratová hodnota
Stejně jako u funkce socket.
Funkce send
Definice
Windows
int send(SOCKET s, const char *buf, int len, int flags);
Linux
int send(int s, const void *buf, size_t len, int flags);
Parametry
Stejný problém s datovým typem socketu a datovým typem pro velikost (size_t vs int).
Návratová hodnota
Windows
Počet odeslaných bytů, případně SOCKET_ERROR (-1) pokud nastala chyba.
Linux
Počet odeslaných bytů, případně -1.
Funkce recv
Definice
Windows
int recv(SOCKET s, char *buf, int len, int flags);
Linux
int recv(int s, void *buf, size_t len, int flags);
Parametry
Stejný problém s datovým typem socketu a datovým typem pro velikost (size_t vs int).
Návratová hodnota
Počet přijatých bytů, případně SOCKET_ERROR (-1) pokud nastala chyba. 0 pokud byl socket ukončen standardně.
Funkce close
Definice
Tady je problém přímo v definici - názvy nesouhlasí.Windows
int closesocket(SOCKET s);
Linux
int close(int fd);
Parametry
Stejný problém s datovým typem socketu.
Návratová hodnota
0 v případě úspěchu, SOCKET_ERROR (-1) pokud nastala chyba.
Windows - specifické funkce
Ve windows se navíc používají funkce na inicializaci a ukončení používání socketů, mezi základní patří funkce:WORD MAKEWORD(BYTE bLow, BYTE bHigh); (makro) int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); int WSACleanup(void); struktura WSADATA (LPWSADATA)Toto je jen nejzákladnější výčet, funkcí existuje víc. Tyto funkce jsou platformně velmi specifické, ale v jednoduchých případech není potřeba udělat nic víc než startup a cleanup.
Řešení
Mým snažením je udělat řešení tak, aby se dala aplikace psát nativně pro jednu platformu a pomocí makra byla funkční i v druhé platformě. Protože na windows je nutné používat WSA funkce, rozhodl jsem se tuto platformu použít jako nativní.
Odstranění problémů s WSA funkcemi
V linuxu není nutné tyto funkce volat, proto stačí jen vytvořit funkce s prázdným tělem vracející defaultní hodnoty, případně návratové hodnoty úspěchu.
Odstranění rozdílných datových typů pro socket
Protože nativní platformou je Windows, nejjednodušším způsobem je dodefinováním datového typu SOCKET.
Odstranění problému s socklen_t*
Pomocí makra přejmenujeme veškeré výskyty funkce accept na funkci acceptSocket, která bude vnitřně volat funkci accept s explicitním přetypováním na socklen_t*
Vytvoření konstant
V linuxu nejsou standartně konstanty SOCKET_ERROR a INVALID_SOCKET, stačí je přidat z winsock.h
Zdrojový kód
networking.h
#pragma once #ifdef __unix__ /* __unix__ is usually defined by compilers targeting Unix systems */ //g++ #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define closesocket(socket) close(socket) typedef int SOCKET; typedef unsigned short WORD; #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR (-1) struct WSADATA { int x; }; WORD MAKEWORD(int a, int b); int WSAStartup(int a, WSADATA* b); int WSACleanup(); #define accept(socket,addr,addrlen) acceptSocket(socket,addr,addrlen) int acceptSocket(int s, struct sockaddr *addr, int* addrlen); #elif defined _WIN32 /* _Win32 is usually defined by compilers targeting 32 or 64 bit Windows systems */ //WIN #include <WinSock2.h> #endif
networking.cpp
#include "networking.h" #ifdef __unix__ //g++ WORD MAKEWORD(int a, int b) { return 0; } int WSAStartup(int a, WSADATA* b) { return 0; } int WSACleanup() { return 0; } #define acceptSocket(socket,addr,addrlen) accept(socket,addr,addrlen) int acceptSocket(int s, struct sockaddr *addr, int* addrlen) { return accept(s, addr, (socklen_t*) addrlen); } #define accept(socket,addr,addrlen) acceptSocket(socket,addr,addrlen) #endif
Závěr
Výše uvedený hlavičkový soubor umožňuje mým C++ projektům ve Visual Studiu 2010 zkompilovatelost v linuxu pomocí g++ bez nutnosti provádění změn. Jsem céčkař začátečník a tak nevím jestli neexistuje podobné lepší řešení (žádné jsem nenašel). Některé věci by určitě šli udělat líp a věrohodněji (aby WSA api opravdu něco dělalo, např. vracelo chybové kódy).
Zdroje
http://en.wikipedia.org/wiki/C_preprocessorhttp://www.allegro.cc/forums/thread/370013/370226#target
http://tangentsoft.net/wskfaq/articles/bsd-compatibility.html
http://www.builder.cz/art/cpp/tcp_server_linux.html
http://www.builder.cz/art/cpp/tcp_server_windows.html
http://research.microsoft.com/en-us/um/redmond/projects/invisible/include/winsock.h.htm
http://pubs.opengroup.org/onlinepubs/7908799/xns/syssocket.h.html