套接字連線
套接字是一種通訊機子。憑藉這樣的機制。客戶/server系統的開發工作既能夠在本地單機上進行。也能夠誇網路進行。
套接字的建立和使用與管道是有差別的。由於套接字明白地將客戶和server區分開來。
套接字連線:
首先,server應用程式用系統呼叫socket來建立一個套接字,它是系統分配給該server程式的類似檔案描寫敘述符的資源,它不能與其它程式共享。
接下來。server程式會給套接字起個名字。本地套接字的名字是Linux檔案系統中的檔名稱,對於網路套接字它的名字是與客戶連線的特定網路有關的服務識別符號。這個識別符號同意Linux將進入的針對特定port號的連線轉到正確的server程式。
我們用bind來給套接字命名。然後server程式就開始等待客戶連線到這個命名的套接字。
系統呼叫listen的作用是。建立一個佇列並將其用於存放來自客戶的進入連線。server通過系統呼叫accept來接受客戶的連線。
server呼叫accept時。它會建立一個與原有的命名套接字不同的新套接字。這個套接字僅僅用於與這個特定的客戶進行通訊,而命名套接字則被保留下來繼續處理來自客戶的連線。
基於套接字系統的client更簡單。
客戶首先呼叫socket建立一個未命名套接字,然後將server的命名套接字作為一個地址來呼叫connect與server建立連線。
套接字屬性
套接字的特性由三個屬性確定:域、型別和協議。套接字還用地址作為它的名字。地址的格式隨域的不同而不同。每一個協議又能夠使用一個或多個地址來定義格式。
套接字的域
域指定套接字通訊中使用的網路介質。
最常見的套接字域是AF_INET,它指的是網際網路絡。其底層協議——網際協議僅僅有一個地址族。它使用一種特定的方式來指定網路中的計算機,即人們常說的IP地址。
server計算機上可能同一時候有多個服務正在執行。
客戶能夠通過IPport來指定一臺聯網機器上的某個特定服務。在系統內部,port通過分配一個唯一16位的整數來標識,在系統外部,則須要通過IP地址和port號的組合來確定。
套接字作為通訊的終點。必須在開始通訊之前繫結一個port。
套接字域還能夠是AF_UNIX,即使一臺還未聯網的計算機上的套接字也能夠使用這個域。這個域的底層協議就是檔案輸入/輸出,而他的地址就是絕對路徑的檔名稱。
我們的伺服器套接字的地址是server_socket。
套接字型別
一個套接字域可能有多種不同的通訊方式,而每種通訊方式又有不同的特性。
AF_UNIX域的套接字沒有這種問題。由於他們提供一個可靠的雙向通訊路徑。
因特網協議提供兩種不同的服務:流和資料包
流套接字:
流套接字提供一個有序、可靠、雙向位元組流的連線。因此,傳送的資料能夠確保不會丟失、複製或亂序到達,而且在這一過程中發生的錯誤也不會顯示出來。流套接字由型別SOCK_STREAM指定,他們是在AF_INET域中通過TCP/IP連線實現。
他們也是AF_UNIX域中最常見的套接字型別。
資料包套接字
由型別SOCK_DGRAM指定的資料包套接字不建立和維持一個連線。它對能夠傳送的資料包的長度有限制。
資料包作為一個單獨的網路訊息被傳輸,可能會丟失、複製或亂序到達。
資料包套接字是在AF_INET域中通過UDP/IP連線實現的,它提供的是一種無須的不可靠服務。但從紫雲的角度來看,它們的開銷比較小,由於不須要維持網路連線。
並且由於無需花費時間來建立。所以他們的速度也非常快。UDP代表的是資料包協議。
套接字協議
僅僅要底層的傳輸機制同意不止一個協議來提供要求的套接字型別。就能夠為套接字選擇一個特定的協議。
建立套接字
socket系統呼叫建立一個套接字並返回一個描寫敘述符。該描寫敘述符能夠用來訪問該套接字。
#include<sys/types.h>
#include<sys/socket.h>
int socket(intdomain, int type, int protocol);
建立的套接字是一條通訊線路的一個端點。
domain引數指定協議族,type引數指定這個套接字的通訊型別。protocol引數指定使用的協議。
最常見的套接字是AF_UNIX和AF_INET。
前者用於實現UNIX檔案系統的本地套戒指。後者用於網路套接字。
type取值包含SOCK_STREAM和SOCK_DGRAM。
通訊所用的協議一般有套接字型別和套接字域決定,通常不須要進行選擇。將protocol引數設定為0表示使用預設協議。
socket系統呼叫返回一個描寫敘述符。
套接字地址
每一個套接字域都有其自己的格式。
對於AF_UNIX域套接字來說。它的地址由結構sockaddr_un來描寫敘述,該結果定義在標頭檔案sys/un.h中。
structsockrrd_un{
sa_family_t sun_family;
char sun_path[];
};
此處sun_family指定地址型別。sun_path為檔名稱來指定套接字地址。
在AF_INET域中,套接字地址由結構socketaddr_in來指定,該結構定義在檔案netinet/in.h中,至少包括下面幾個成員:
structsocket_in{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
};
IP地址結構in_addr定義為
struct in_addr{
unigned long int s_addr;
};
IP地址中的四個位元組組成一個32位的值,一個AF_INET套接字由它的域、IP地址和port號來全然確定。
命名套接字
要想讓通過socket呼叫建立的套接字能夠被其它程式使用,server就必須給該套接字命名。這樣,AF_UNIX套接字就會關聯到一個檔案系統的路徑名。AF_INET套接字結匯關聯到一個IPport。
#include<sys/types.h> /* See NOTES*/
#include <sys/socket.h>
int bind(intsockfd, const struct sockaddr *addr, socklen_t addrlen);
bind系統呼叫把引數addr中的地址分配給與檔案描寫敘述符socket關聯的未命名套接字。地址結構的長度由引數address_len傳遞。
bind呼叫須要將一個特定的地址結構指標轉化為指向通用地型別(struct sockaddr *)。
bind呼叫成功返回0,失敗返回-1。
建立套接字佇列
為了可以在套接字上接受進入的連線,server程式必須建立一個佇列來儲存未處理的請求。使用listen系統呼叫來實現這一工作。
#include<sys/types.h> /* See NOTES*/
#include <sys/socket.h>
int listen(int sockfd, int backlog);
在套接字佇列中,等待處理的進入連線的個數不能超過backlog這個數字。再往後的連線將被拒絕。
接受連線
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr*addr, socklen_t *addrlen);
一旦server程式建立並命名了套接字後,它就能夠通過accept系統呼叫來等待客戶建立對該套接字的連線。
accept系統呼叫僅僅有當客戶試圖連線到由sockfd引數指定的套接字上時才返回。
這裡的客戶指,在套接字佇列中排在第一個的未處理連線。accept函式將建立一個新套接字來和該客戶進行通訊,而且返回新套接字的描寫敘述符。
新套接字的型別和server監聽套接字型別一樣。
套接字必須事先由bind呼叫命名,而且有listen呼叫給他分配一個連線佇列。
連線客戶的地址將被放入address引數指向的sockaddr結構中。
引數address_len指定客戶結構的長度。假設客戶地址的長度超過這個值,它將被截段。所以在呼叫accept之前,address_len必須被設定為預期的地址長度。
當這個呼叫返回時,address_len將被設定為連線客戶地址結構的實際長度。
假設套接字佇列中沒有未處理的聯結,accept將被堵塞直到有客戶建立連線為止。能夠通過對套接字檔案描寫敘述符設定O_NOBLOCK標誌來改變這一行為,使用函式fcntl。
請求連線
客戶通過一個在未命名套接字和server監聽套接字之間建立連線的方法來連線到server。
#include <sys/types.h>
#include <sys/socket.h>
int connect(intsockfd, const struct sockaddr *addr, socklen_t addrlen);
引數sockfd指定的套接字將連線到引數addr指定的server套接字,addr指向的結構的長度由引數addrlen指定。
關閉套接字
能夠通過close函式來終止server和客戶上套接字的連線。
主機位元組序和網路位元組序
為了使不同計算機能夠通過網路傳輸的多位元組整數的值達成一致,須要一個網路位元組序。
客戶和server必須在傳輸之前,將它們的內部整數表示方式轉換為網路位元組序。
能夠通過下面函式完畢這一工作。
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_thtons(uint16_t hostshort);
uint32_tntohl(uint32_t netlong);
uint16_tntohs(uint16_t netshort);
htonl表示”host tonetwork ,long”。
網路資訊
通過呼叫gethostent,能夠找到給定計算機主機資訊
#include<netdb.h>
Struct hostent *gethostent (void);
Void sethostent (int stayopen);
Void endhostent (void);
Struct hostent{
Char *h_name;
Char **h_aliases;
Inth_addrtype;
Inth_length;
Char **h_addr_list;};
從介面獲得網路名字和網路號:
#include<netdb.h.
Struct netent * getnetbyaddr (uint32_t net,int type);
Struct netent * getnetbyname (const char *name);
Struct netent * getnetent (void);
Void setnetent (int stayopen);
Void endnetent (void);
Struct netent{
Char *n_name;
Char **n_aliases;
Intn_addrtype;
Uint32_t n_net;};
將協議名字和協議號採用下面函式對映
#include <netdb.h>
Struct protoent * getprotobyname (const char* name);
Struct protoent * getprotobynumber (intproto);
Struct protoent * getprotoent (void);
Void setprotoent (int stayopen);
Void endprotoent (void)。
Struct protoent{
Char *p_name;
Char **p_aliases;
Intp_proto;};
從一個服務名對映到一個port號,服務名
#include<netdb.h>
Struct servent * getservbyname (const char *name, const char * proto);
Struct servent * getservbyport (int port,const char * proto);
Struct servent * getservent( (void);
Void setervent (int stayopen);
Void endservent (void);
Struct servent{
Char *s_name;
Char **s_aliases;
Ints_port;
Char *s_proto;};
從一個主機名字和服務名字對映到一個地址
#include <sys/socket.h>
#include <netdb.h>
Int getaddrinfo (const char * restrict host,const char * restrict service, const struct addrinfo * restrict hint, structaddrinfo ** restrict res);
Void freeaddrinfo (struct addrinfo * ai);
Struct addrinfo{
Intai_flags;
intai_family;
Intai_socktype;
Intai_protocol;
Socklen_t ai_addrlen;
Structsockaddr * ai_addr;
Char *ai_canonname;
Structaddrinfo * ai_next;};
gai_strerror將返回的錯誤碼轉換成錯誤訊息
#include<netdb.h>
Const char * gai_strerror (int error);
將地址轉換成主機或者服務名
#include<sys/socket.h>
#include <netdb.h>
Int getnameinfo (const struct sockaddr *restrict addr, socklen_t alen, char * restrict host,socklen_t hostlen, char *restrict service, socklen_t servlen, unsigned int flags);
傳輸資料
傳送資料:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr,socklen_t addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
struct msghdr {
void *msg_name; /* optional address */
socklen_t msg_namelen; /* size of address */
struct iovec *msg_iov; /* scatter/gather array */
size_t msg_iovlen; /* # elements in msg_iov */
void *msg_control; /* ancillary data, see below */
size_t msg_controllen; /* ancillary databuffer len */
int msg_flags; /*flags on received message */
};
接收資料:
#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr,socklen_t *addrlen);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
套接字選項
#include<sys/types.h> /* See NOTES*/
#include <sys/socket.h>
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t*optlen);
int setsockopt(int sockfd, int level, int optname,
const void *optval,socklen_t optlen);
能夠用以上函式來設定和獲取套接字選項。