course:nctu-高等unix程式設計:chapter16
目錄表
Network IPC:Socket
0x00 Outline
- Introduction
- Socket Descriptors
- Addressing
- Connection Establishment
- Data Transfer
- Socket Option
0x01 Introduction
- classical IPC’s 讓跑在同一台電腦上的 processes 可以互相通訊
- network IPC 則可讓跑在不同電腦的 processes 互相通訊
- network IPC 可用在 inter-machine communication 和 intra-machine communication
- 本章節主要討論 TCP/IP Sockets
0x02 Socket Descriptors
socket descriptor
- A socket is an abstraction of a communication endpoint
- socket descriptor 在 UNIX 系統中是以 file descriptor 方式實作的
- 對於 file descriptor 操作的那些 function 如 read、write 也同樣能對 socket descriptor 操作
#include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); /*return file (socket) descriptor if success, or -1 on error*/
- 建立一個 socket
- domain:
- AF_INET: IPv4 Internet protocols
- AF_INET6: IPv6 Internet protocols
- AF_UNIX or AF_LOCAL: Local communication
- type:
- SOCK_STREAM: stream TCP
- SOCK_DGRAM: datagram UDP
- protocol: 0(default), IPPROTO_TCP, IPPROTO_UDP
Socket Descriptors and File I/O Functions
- close
- dup,dup2
- fcntl
- read
- write
Release a socket descriptor
#include <sys/socket.h> int shutdown(int socket, int how); /*returns 0 if success, or -1 on error*/
- how:
- SHUT_RD
- SHUT_WR
- SHUT_RDWR
- socket communication 是雙向的,相較於 close,利用 shutdown 可以僅關閉單一方向的通訊
- close 會關閉呼叫它的 process 的 file descriptor 參照,file descriptor 參照數量 -1,但其他 process 仍可對 file descriptor 繼續操作,直到 file descriptor 參照數量為 0 時 file descriptor 才會被關閉
- 如果多個 processes 參照同個 file descriptor,而其中一個 process 呼叫 shutdown,則 file descriptor 會立即被關閉,其他 processes 也無法繼續操作 file descriptor
0x03 Addressing
identify socket
- 使用 addressing schemes 來識別 socket
- AF_UNIX : a pathname
- AF_INET + SOCK_STREAM + IP_PROTO_TCP : IPv4 address and TCP port number
- AF_INET + SOCKET_DGRAM + IP_PROTO_UDP : IPv4 address and UDP port number
- AF_INET6 + SOCK_STREAM + IP_PROTO_TCP : IPv6 address and TCP port number
- AF_INET6 + SOCKET_DGRAM + IP_PROTO_UDP : IPv6 address and UDP port number
byte ordering
- 電腦世界中有兩種 byte order
- Big-Endian(正序)
- Little-Endian(倒序)
- Network Byte Order 是 Big-Endian
- Host Byte Order 則由電腦架構而定,x86 為 Little-Endian
- 我們在寫網路程式時,需要確認 port number 跟 IP address 都是 Network Byte Order,而 C 語言提供了一些函式,讓我們直接假設 Host Byte Order 皆不是 Network Byte Order,而可使用這些函式進行轉換
#include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
- 這些函式可使我們將 unsigned integer,unsigned short integer 在 Host Byte Order 和 Network Byte Order 間轉換
- h means host
- n means network
- l means long
- s means short
address formats
- Generic:
/*On Linux*/ struct sockaddr { sa_family_t sa_family; /* address family */ char sa_data[14]; /* variable-length address */ }; /*On some other system*/ struct sockaddr { unsigned char sa_len; /* total length */ sa_family_t sa_family; /* address family */ char sa_data[14]; /* variable-length address */ };
- IPv4 on Linux
typedef uint16_t in_port_t; typedef uint32_t in_addr_t; struct in_addr { in_addr_t s_addr; /* IPv4 address */ }; struct sockaddr_in { sa_family_t sin_family; /* address family */ in_port_t sin_port; /* port number */ struct in_addr sin_addr; /* IPv4 address */ unsigned char sin_zero[8]; };
- IPv6 on Linux
typedef uint16_t in_port_t; struct in6_addr { union { /* IPv4 address */ uint8_t __u6_addr8[16]; uint16_t __u6_addr16[8]; uint32_t __u6_addr32[4]; } __in6_u; }; struct sockaddr_in6 { sa_family_t sin_family; /* address family */ in_port_t sin6_port; /* port number */ uint32_t sin6_flowinfo; /* IPv6 flow info */ struct in6_addr sin6_addr; /* IPv6 address */ uint32_t sin6_scope_id; /* IPv6 scope id */ };
conversion of address
- 為了將 address 已可讀的形式印出來,我們須將 numeric address 轉換為 text
#include <arpa/inet.h> const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); /*Returns: pointer to address string on success, or NULL on error*/
- 將 text formatted address 轉回 numeric address
#include <arpa/inet.h> int inet_pton(int af, const char *src, void *dst); /*Returns: 1 on success, 0 if the format is invalid, or -1 on error and errno=EAFNOSUPPORT*/
if(inet_pton(AF_INET, argv[1], &addr4) == 1) { printf("IPv4: 0x%08x\n", htonl(addr4.s_addr)); } if(inet_pton(AF_INET6, argv[1], &addr6) == 1) { printf("IPv6: 0x%08x%08x%08x%08x\n", htonl(addr6.__in6_u.__u6_addr32[0]), htonl(addr6.__in6_u.__u6_addr32[1]), htonl(addr6.__in6_u.__u6_addr32[2]), htonl(addr6.__in6_u.__u6_addr32[3])); }
address lookup
- 本機上我們可以由三項條件做位址查詢
- known hosts
- known protocols
- known services
- 另外可通過 DNS 查詢 hostname
- known hosts
- 在系統中可以查看
/etc/hosts
檔案
struct hostent { char *h_name; /*official name of host*/ char **h_aliases; /* alias list */ int h_addrtype; /* host address type */ int h_length; /* length of address */ char **h_addr_list; /* list of addresses */ }
/* for AF_INET */ #include <sys/socket.h> struct hostent *gethostent(void); /*Returns: valid pointer if success, or NULL on error*/ void sethostent(int stayopen); void endhostent(void);
- gethostent() is not thread-safe
- sethostent(),若 database 未開啟則 open 已開啟則 rewind
- endhostent(),關閉 host database
- 這兩個 function 在 DNS lookup 時會有不同意義
int main() { int i; char buf[64]; struct hostent *h; while((h = gethostent()) != NULL) { if(h->h_addrtype != AF_INET) continue; printf(“name=%s, addr={ “, h->h_name); for(i = 0; h->h_addr_list[i] != NULL; i++) { printf("%s ", inet_ntop(AF_INET, h->h_addr_list[i], buf, sizeof(buf))); } printf("}\n"); } return(0); }
name=cshome.cs.nctu.edu.tw, addr={ 140.113.235.101 } name=csduty.cs.nctu.edu.tw, addr={ 140.113.235.102 }
- known protocols
- 在系統中可以查看
/etc/protocols
檔案
truct protoent { char *p_name; /*officialprotocolname*/ char **p_aliases; /* alias list */ int p_proto; /* protocol number */ }
#include <netdb.h> struct protoent *getprotoent(void); /*Returns: valid pointer if success, or NULL on error*/ struct protoent *getprotobyname(const char *name); /*returns a protoent structure for the entry from the database that matches the protocol name,NULL on error*/ struct protoent *getprotobynumber(int proto); /*returns a protoent structure for the entry from the database that matches the protocol number,NULL on error*/ void setprotoent(int stayopen); void endprotoent(void);
- getprotobyname 和 getprotobynumber 都不是 thread-safe
int main() { int i; struct protoent *p; while((p = getprotoent()) != NULL) { printf("name=%s (%d), ", p->p_name, p->p_proto); printf("alias={ "); for(i = 0; p->p_aliases[i] != NULL; i++) { printf("%s ", p->p_aliases[i]); printf("}\n"); } } return(0); }
name=ip (0), alias={ IP } name=icmp (1), alias={ ICMP } name=igmp (2), alias={ IGMP } ... name=tcp (6), alias={ TCP } ... name=udp (17), alias={ UDP } ...
- known services
- 在系統中可以查看
/etc/services
檔案
struct servent { char *s_name; /*officialservicename*/ char **S_aliases; /* alias list */ int s_port; /* port number */ char *s_proto; /*protocoltouse*/ }
#include <netdb.h> struct servent *getservent(void); /*Returns: valid pointer if success, or NULL on error*/ struct servent *getservbyname(const char *name, const char *proto); /*returns a servent structure for the entry from the database that matches the service name using protocol proto, or NULL on error*/ struct servent *getservbyport(int port, const char *proto); /*returns a servent structure for the entry from the database that matches the service port(network byte order) using protocol proto, or NULL on error*/ void setservent(int stayopen); void endservent(void);
- getservbyport 的 port 須為 network byte order
- getservent, getservbyname, getservbyport 都不是 thread-safe
int main() { int i; struct servent *s; while((s = getservent()) != NULL) { printf("name=%s (%s/%d), ", s->s_name, s->s_proto, ntohs(s->s_port)); printf("alias={ "); for(i = 0; s->s_aliases[i] != NULL; i++) { printf("%s ", s->s_aliases[i]); printf("}\n"); } } return(0); }
... name=telnet (tcp/23), alias={ } name=ftp (tcp/21), alias={ } name=pop3 (tcp/110), alias={ pop-3 } name=www (tcp/80), alias={ http } ...
- hostname via DNS
#include <netdb.h> extern int h_errno; struct hostent *gethostbyname(const char *name); /*Returns: valid pointer if success, or NULL on error*/ #include <sys/socket.h> struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type); /*Returns: valid pointer if success, or NULL on error*/
- gethostbyaddr 的參數 type 可為 AF_INET 或 AF_INET6
- 此兩個 function 預設是使用 UDP protocol,可使用 sethostent() 變更,如果 stayopen 為 true,會使用 TCP socket 連線,且 query 成功時,連線會保持,若 stayopen 為 false,則採用預設 UDP datagrams
- 此兩個 function 也是 not thread-safe
thread-safe query
- 前面提了許多 function 都是 not thread-safe 的,接下來介紹幾個 thread-safe 的 function
- query address and port
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); /*Returns: zero if success, or nonzero error code on error*/
- node: 要查詢的節點(name or address)
- service: service 名稱
- hints: 查詢標準
- flags (see the next slide)
- address family (AF_INET/AF_INET6)
- socktype (SOCK_DGRAM/SOCK_STREAM, can be 0)
- protocol (can be 0)
- Other fields must be zero
- res: return the queried result
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; };
- 當 getaddrinfo 有 error 時無法從 perror 或 stderr 取得錯誤訊息,需要呼叫 gai_strerror function 將 error code 轉為可讀的文字訊息
#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> const char *gai_strerror(int error);
int main(int argc, char *argv[]) { int s; struct addrinfo hints, *result, *rp; if (argc < 3) { fprintf(stderr, "usage: %s host port\n", argv[0]); exit(-1); } bzero(&hints, sizeof(struct addrinfo)); hints.ai_family = AF_INET; /* allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; /* stream socket */ hints.ai_flags = 0; hints.ai_protocol = 0; /* any protocol */ if((s=getaddrinfo(argv[1], argv[2], &hints, &result))!=0) { fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); exit(-1); } for(rp = result; rp != NULL; rp = rp->ai_next) { struct sockaddr_in *p = (struct sockaddr_in*) rp->ai_addr; printf( "%s:%d\n", inet_ntoa(p->sin_addr), ntohs(p->sin_port)); } return(0); }
$ ./getaddrinfo google.com www 74.125.45.100:80 209.85.171.100:8 74.125.67.100:80
- query of name and service
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags); /*Returns:zero if success,or nonzero on error*/
int main(int argc, char *argv[]) { struct sockaddr_in sin; char host[64], serv[64]; int s; if (argc < 3) { fprintf(stderr, "usage: %s ip port\n", argv[0]); exit(-1); } bzero(&sin, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_addr.s_addr = inet_addr(argv[1]); sin.sin_port = htons(atoi(argv[2])); if((s = getnameinfo((struct sockaddr*) &sin, sizeof(sin), host, sizeof(host), serv, sizeof(serv), 0)) != 0) { fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s)); exit(-1); } printf("%s:%s\n", host, serv); return(0); }
0x04 Connection Establishment
server connect step
- 建立 socket file descriptor
- 準備一個 sockaddr_in data structure,將 socket address
bind
到 socket descriptor- port number 不能小於 1024
listen
for incoming connectionaccept
incoming connection
client connect step
- 建立 socket file descriptor
- 準備一個 sockaddr_in data structure
connect
to server
socket function
#include <sys/socket.h> int bind(int socket, const struct sockaddr *address, socklen_t address_len); /*Returns: zero if success, or -1 on error*/
#include <sys/socket.h> int connect(int socket, const struct sockaddr *address, socklen_t address_len); /*Returns: zero if success, or -1 on error*/
#include <sys/socket.h> int listen(int socket, int backlog); /*Returns: zero if success, or -1 on error*/
- backlog is the number of outstanding connect requests in a queue
- backlog 在 Linux 上最大是 128 (SOMAXCONN constant)
- queue 一旦滿了,系統會拒絕其他新的連線
#include <sys/socket.h> int accept(int sockfd, struct sockaddr *restrict address, socklen_t *restrict address_len); /*Returns: file (socket) descriptor if success, or -1 on error*/
- The returned descriptor is the socket connected to the client
- 新的 socket descriptor 和舊的 sockfd 會有相同 socket type 和 address family
- address 保存了 client address 和 port,若我們不需要這些資訊可將此設為 NULL
- 如果沒有等到 connect request,connect 會 block 直到有連線請求到達
#include <sys/socket.h> int getsockname(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len); int getpeername(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
- getsockname get the local address bound to a socket
- getpeername get the remote address bound to a socket
- address 和 address_len 要先宣告,在呼叫這兩個函式前,address_len 要設成 address 的 data structure(sockaddr) 的長度
0x05 Data Transfer
send data
- 可以使用 write function 對 file descriptor 操作
- 也可使用較彈性的 send/sendto function
#include <sys/socket.h> ssize_t send(int socket, const void *buffer, size_t length, int flags); ssize_t sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len); /*Returns: number of bytes sent if success, or -1 on error*/
- send function 適用於連線導向的 socket
- sendto function 可用於連線或非連線導向的 socket
- 如果 send function 的 flag 設為 0,基本上相等於 write function
- 在非連線導向中,我們要明確指定 dest_addr 參數
receive data
- 可以使用 read function 對 file descriptor 操作
- 也可使用較彈性的 recv/recvfrom function
#include <sys/socket.h> ssize_t recv(int socket, void *buffer, size_t length, int flags); ssize_t recvfrom(int socket, void *restrict buffer, size_t length, int flags, struct sockaddr *restrict address, socklen_t *restrict address_len); /*Returns: number of bytes received if success, 0 if no messages are available and peer has done an orderly shutdown, or -1 on error*/
- recv function 適用於連線導向的 socket
- recvfrom function 可用於連線或非連線導向的 socket
- 如果 recv function 的 flag 設為 0,基本上相等於 read function
- 在非連線導向中,recvfrom 的 address 參數會記錄 data sender 的 address 和 port
0x06 Socket Option
#include <sys/socket.h> int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len); int getsockopt(int socket, int level, int option_name, void *restrict option_value, socklen_t *restrict option_len); /*Returns: zero if success, or -1 on error*/
- The level argument identify the protocol (by a protocol number) to apply.For example, IPPROTO_IP, IPPROTO_TCP…
0x06 參考資料
course/nctu-高等unix程式設計/chapter16.txt · 上一次變更: 由 127.0.0.1