socket是一種IPC方法。容許單臺或經過網絡鏈接的主句進程間交換數據linux
- 各應用程序建立一個socket
- 服務器將本身的socket綁定到一個都知道的地址上
fd=socket(domain, type, protocol)
編程
#include<sys/socket.h>
int socket(int domain, int type, int protocal) //return fd success, -1 error int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//return 0 s, -1 e
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
複製代碼
socket緩存
- domain:識別出socket的地址格式,代表通訊範圍
- type:
SOCKET_STREAM
(TCP)和SOCKET_DGRAM
(UDP)- protocol 通常爲0
對於fd,
getsockname
,getpeername
服務器
#include<sys/socket.h>
int listen(int sockfd, int backlog);
//return 0 s, -1 e
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//return fd success, -1 error
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//return 0 s, -1 e
複製代碼
主動和被動socket網絡
- 一個主動socket經過connect 創建一個到被動socket的鏈接
- 一個被動socket經過listen 被標記成容許接入鏈接的socket
listen併發
- backlog: 若是在服務器調用accept前,客戶端connect,會產生一個未決的鏈接,backlog限制這個數量,在這個數量內,客戶端鏈接請求會當即成功,數量外的會阻塞到一個未決鏈接被accept,並從未決鏈接隊列刪除
accept負載均衡
- 會建立一個新的socket,這個新的socket會與connect另外一端的socket鏈接
- addr和addrlen在調用事後會爲對端地址信息,若是不關心對端信息傳入時設爲NULL,0
#nclude<sys/socket.h>
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
//return num of bytes received, 0 EOF, -1 error
ssize_t recvfrom(int sockfd, void *buffer, size_t length, int flags, struct sockaddr *dest_addr, socklen_t *addrlen);
//return num of bytes send, -1 error
複製代碼
- 客戶端若是須要接受服務端發送的數據報的話還須要
bind
一下- 數據報能夠
connect
以後調用write
和直接send_to
同樣,只適用於發起connect
的一端,connect
以後內核會記錄socket的對端,屢次調用connect後面的會修改前面的
struct sockaddr{
sa_family_t sa_family;
char sa_data[108];
};
const char *SOCKNAME = "/tmp/mysock";
int sfd;
struct sockaddr_un addr;
sfd = socket(AF_UNIX, SOCK_STREAM, 0);
if (sfd == -1)
errExit("socket");
/* Create socket */
memset(&addr, 0, sizeof(struct sockaddr_un));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKNAME, sizeof(addr.sun_path) - 1);
if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1)
errExit("bind");
複製代碼
- 沒法將socket綁定到一個已有路徑名上,bind會返回EADDRINUSE
- 沒法open 打來一個socket
- 當不須要一個socket時使用unlink
- 若是sun_path的第一個字符爲NULL字節,會建立抽象socket命名空間
- 無需擔憂名字和文件系統衝突
- socket關閉後自動刪除抽象名
- 權限無需寫權限
#include<sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
複製代碼
- domian只能是AF_UNIX
- 調用完sockpair以後,進程會fork一個子進程,子進程繼承fd副本進行通訊
netstat -i
dom
- 因爲端口號和IP地址須要被網絡中的全部主機理解,須要統一標準字節序,即網絡字節序,它是大端的
#include<arpa/inet.h>
// return netword byte order
uint16_t htons(uint16_t host_uint16);
uint32_t htonl(uint32_t host_uint32);
// return host byte order
uint16_t ntohs(uint16_t net_uint16);
uint32_t ntohl(uint32_t net_uint32);
複製代碼
#include<netinet/in.h>
struct in_addr { /* IPv4 4-byte address */
in_addr_t s_addr; /* Unsigned 32-bit integer */
};
struct sockaddr_in { /* IPv4 socket address */
sa_family_t sin_family; /* Address family (AF_INET) */
in_port_t sin_port; /* Port number */
struct in_addr sin_addr; /* IPv4 address */
unsigned char __pad[X]; /* Pad to size of 'sockaddr' structure (16 bytes) */
};
struct in6_addr { /* IPv6 address structure */
uint8_t s6_addr[16]; /* 16 bytes == 128 bits */
};
struct sockaddr_in6 { /* IPv6 socket address */
sa_family_t sin6_family; /* Address family (AF_INET6) */
in_port_t sin6_port; /* Port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in kernel 2.4) */
};
複製代碼
#include<arpa/inet.h>
// 二進制ip地址和可讀ip地址('127.0.0.1')轉換
int inet_pton(int domain, const char *src_str, void *addrptr);
//return 1 success, 0 格式str不催, -1 error
const char *inet_ntop(int domain, const void *addrptr, char *dst_str, size_t len);
//return dst_str的指針success, NULL error
複製代碼
- 解析也是從頂級域名開始一級級解析
- 域名的補全規則定義在
/etc/resolv.conf
文件中,默認會用本機域名補全
/etc/services
記錄了(IANA)端口號和服務名
#include<sys/socket.h>
#include<netdb.h>
int getaddrinfo(const char *host, const char *service, const struct addrinfo *hints, struct addrinfo **result);
//return 0 success, other error,可能須要向DNS服務器發請求
struct addrinfo {
int ai_flags; /* Input flags (AI_* constants) */
int ai_family; /* Address family (AF_INET | AF_INET6)*/
int ai_socktype; /* Type: SOCK_STREAM, SOCK_DGRAM */
int ai_protocol; /* Socket protocol */
size_t ai_addrlen; /* Size of structure pointed to by ai_addr */
char *ai_canonname; /* Canonical name of host */
struct sockaddr *ai_addr; /* Pointer to socket address structure */
struct addrinfo *ai_next; /* Next structure in linked list */
};
int freeaddrinfo(struct addrinfo *result);
//釋放result分配的內存
int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, size_t hostlen, char *service, size_t servlen, int flags);
// 0 success, other fail,不想要獲取的參數傳NULL,len設爲0
複製代碼
getaddrinfosocket
- host 爲主機名或者ip字符串
- service 爲一個服務名或者十進制端口號
- hint指向的結構規定了,result返回的socket地址結構的標準。hint能夠設置
ai_flags
,ai_family
,ai_socktype
,ai_protocol
字段用做過濾- result指向一個包含addrinfo結構鏈表的表頭
- 預先建立進/線程池,動態改變池大小
- 單個進程處理多個客戶端(I/O多路複用,信號驅動,或者epoll)
- 服務器集羣
- DNS輪轉負載共享,緩存,沒法良好的負載均衡,沒法確保高可用等一些問題
- 負載均衡
用來消除運行大量很是用服務器進程的須要tcp
- 監視一組指定套接字端口,按須要啓動其它服務
- 簡化了啓動其它服務的編程工做
inetd
守護進程一般在系統啓動時運行。以後執行以下步驟
- 在
/etc/inetd.conf
中指定的每項服務,inetd都會建立一個恰當類型的套接字,而後綁定到指定端口上,TCP的話會listen- 經過
select
監視select
阻塞直到有請求,在TCP中會先accept建立新的socket- inetd調用
fork
建立一個新的進程,exec()
啓動服務器程序,在exec()
以前
- 除了socket描述符外,關閉其餘繼承的文件描述符
- 在0,1,2上覆制套接字文件描述符,關閉套接字文件描述符自己(這樣相應代碼直接從標準輸出輸入讀取寫入客戶端信息就行了)
- 爲啓動進程設定與用戶和組ID
- 若是是tcp而且
accept
建立了socket fd,inetd關閉這個描述符- 繼續
select
監視