Socket programming 筆記

這系列是閱讀 The Linux socket TCP/IP protocols network programming tutorials所記錄的筆記,這份tutorial寫的相當好,如果有不清楚的地方,建議大家看看原文,寫的非常詳細,這裡只節錄重點。

##Socket

TCP

1. socket是IPC所使用的一種API。
2. 也稱為Berkeley Socket或BSD Socket
3. Connection-oriented socket (TCP)

UDP

4.Connectionless socket (UDP)

###Data structure for socket

1. protocol family: 建socket 的一個參數
2. Service type: 建socket 的一個參數 (Stream, Datagram)
3. Local IP address: 用bind()設定
4. Local port: 用bind()設定
5. Remote IP address: 用connect()設定
6. Remote port: 用connect()設定

###Socket endpoint

TCP/IP 的communication是由2個endpoint組成。一個endpoint是由一個ip加一個port,所以一臺電腦可以有很多的endpoint。PF_INET是internet protocol family; AF_INET是internet address family (Normally PF_INET = AF_INET = 2)。

###Host / Hostname host指的是internet上的一臺電腦 (computer, file server, etc),而hostname則是用來識別此電腦的名稱,有時會跟domain name混用,雖然有些技術上的差異,如www.google.com,主要是用來uniquely identify。

Host address data type

hostaddress是由4byte的數字組成,如140.113.23.3,前3個稱為network number,最後1個叫local address。hostaddress可用一個unsigned long int表示。scoket中的struct in_addr將這個整數包裝起來,定義在in.h。

/* Internet address. */
struct in_addr {
    __be32    s_addr;
};

這個s_addr用unsigned long int 所表示的host address number。 特殊的host address number:

1. INADDR_LOOPBACK: 指本機的address,也就是127.0.0.1 (localhost),而不需去查其真實的address。只在本機的IPC,使用INADDR_LOOPBACK可避免不必要的traffic。
2. INADDR_ANY: 指任何連上來的address。如果要接受所有來自 internet的connection可使用。
3. INADDR_BROADCAST: 傳送broadcast訊息可使用。
4. INADDR_NONE: 某些function錯誤時的回傳值。

host address的處理函數

定義在arpa/inet.h。(network byte order v.s. host byte order??)
1.int inet_aton(const char *name, struct in_addr *addr): 將像140.113.23.3這樣的adress轉成binary,並且存入struct in_addr。成功時傳回非零,失敗傳0。
2. unsigned long int inet_addr(const char *name):也是將address轉成binary,但錯誤時傳回 INADDR_NONE。這是inet_aton的舊版,因為INADDR_NONE也是合法的address (255.255.255.255),而inet_aton有比較好的error return處理。
3. unsigned long int inet_network(const char *name): 將像140.113.23.3的address轉成host byte order的number,成功傳回轉換結果,失敗傳回-1。
4. char * inet_ntoa(struct in_addr addr): 將internet host address轉回像140.113.23.3的address,傳回轉換結果的pointer。

接下來的函數是處理同一塊buffer,連續呼叫會覆寫之前的結果。因此要將處理結果先存下來。

5. struct in_addr inet_makeaddr(int net, int local)
6. int inet_lnaof(struct in_addr addr)
7. Function int inet_netof(struct in_addr addr)

socket的API

###structure

struct sockaddr {
    sa_family_t    sa_family;    /* address family, AF_xxx    */
    char        sa_data[14];    /* 14 bytes of protocol address    */
};

struct sockaddr_in {
    sa_family_t        sin_family;    /* Address family        */
    __be16        sin_port;    /* Port number            */
    struct in_addr    sin_addr;    /* Internet address        */

    /* Pad to size of `struct sockaddr'. */
    unsigned char        __pad[__SOCK_SIZE__ - sizeof(short int) -
                               sizeof(unsigned short int) - sizeof(struct in_addr)];
};
1. To deal with struct sockaddr, programmers created a parallel structure: struct sockaddr_in ("in" for "Internet".)
2. The sin_zero field is reserved, and you must set it to hexadecimal zeroes.
3. sockaddr_in is a "specialized" sockaddr.
4. The sin_port and sin_addr must be in Network Byte Order.

##函數

  • socket()
NAME
       socket() - create an endpoint for communication
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>
       int socket(int domain, int type, int protocol);
1. 由server和client使用
2. domain: 設AF_INET
3. type: SOCK_STREAM或SOCK_DGRAM
4. protocol: 0 (讓socket()根據type自動設定)。更好的方式是使用getprotobyname()。
5. 成功傳回socket descriptor,失敗傳回-1 (並使用errno的macro)
6. 正確使用為在struct sockaddr_in使用AF_INET,在socket()使用PF_INET。但AF_INET可用在任何地方。
  • bind()
NAME
       bind() - bind a name to a socket
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>
       int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
1. 由server使用
2. sockfd為socket()的回傳值。
3. myaddr 須指定port和ip後傳入,注意是用struct sockaddr_in宣告,傳入時轉為struct sockaddr。
4. addrlen直接用 sizeof(struct sockaddr)
5. bind就是將local的endpoint attach到一個socket
  • connect()
NAME
       connect() - initiate a connection on a socket.
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>
       int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
1. connect()由client呼叫
2. sockfd由socket()產生,serv_addr指定server的ip和port後傳入
3. addrlen直接用 sizeof(struct sockaddr)
  • listen()
NAME
       listen() - listen for connections on a socket
SYNOPSIS
       #include <sys/socket.h>
       int listen(int sockfd, int backlog);
1. 由server使用
2. sockfd由socket()產生,backlog設定可queue住的connection數量 (等待accept())。
3. 失敗傳回-1,並使用errno
  • accept()
NAME
       accept() - accept a connection on a socket
SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>
       int accept(int sockfd, struct sockaddr *addr, int *addrlen);
 
1. sockfd: listen()所使用的那個sockfd
2. addr 宣告完就拿來用,將存放client的資訊
3. addrlen直接用 sizeof(struct sockaddr)
4. 失敗傳回-1,並使用errno
5. server listen()完後開始等待accept()。accept()傳回一個file descriptor以供此connection的I/O之用。accpet完之後,server繼續使用listen()的fd等待下一個connection。
  • send()
int send(int sockfd, const void *msg, int len, int flags);
1. sockfd可以是listen()的fd或是accpet的fd。
2. msg就是data,len就是data 的length (sizeof(msg)),flag就設0。
3. 回傳值為送出去的大小
4. 失敗傳回-1,並使用errno
  • recv()
The recv() call is similar in many respects:
int recv(int sockfd, void *buf, int len, unsigned int flags);
1. sockfd: 要從哪個fd接收。
2. 回傳值為收到的大小。若回傳0,表示對方把connection切了。
  • write()
NAME
       write() - write to a file descriptor
SYNOPSIS
       #include <unistd.h>
       ssize_t write(int fd, const void *buf, size_t count);
1. 可寫到file,device或socket
  • read()
NAME
       read() - read from a file descriptor
SYNOPSIS
       #include <unistd.h>
       ssize_t read(int fd, void *buf, size_t count);
1. 從file,device或socket讀取
2. 沒有data則read會block
3. count指定要讀的長度,如果沒有那麼多,則return,不會block
 
 
int shutdown(int sockfd, int how);
1. 0: 不允許將來的receive; 1: 不允許將來的send; 2: 不允許將來的send和receice (跟close()一樣,但沒有真的的close fd,將fd free掉)

##Byte order

不同的架構在communication時,其word會使用不同的byte order==>Big-endian(MSB)和Little-endian。 Internet protocols 有其標準的byte order,即network byte order。建立socket時,必須確定ip和port (sin_addr和sin_port)是用network byte order來表示。 getservbyname(),gethostbyname和inet_addr都是network byte order,因此可直接使用。 轉換的函數如下,並定義在netinet/in.h:

htons() / ntohs(): 轉換sin_port
htonl()/ ntohl: 轉換sin_addr

ex: ina.sin_addr.s_addr = inet_addr("10.12.110.57"); //obsolete of inet_aton (ascii to network),但不是每個平臺都有implement inet_aton() Typical use:

struct sockaddr_in my_addr;
/* host byte order */
my_addr.sin_family = AF_INET;
/* short, network byte order */
my_addr.sin_port = htons(MYPORT);
inet_aton("10.12.110.57", &(my_addr.sin_addr));
/* zero the rest of the struct */
memset(&(my_addr.sin_zero), 0, 8);
reverse function:
printf("%s", inet_ntoa(ina.sin_addr));
要注意的是,inet_ntoa使用的是靜態的buffer,所以會有如下的情形:
char *a1, *a2;
...
...
a1 = inet_ntoa(ina1.sin_addr); /* this is 192.168.4.1 */
a2 = inet_ntoa(ina2.sin_addr); /* this is 10.11.110.55 */
printf("address 1: %s\n", a1);
printf("address 2: %s\n", a2);
  • Will print:
address 1: 10.11.110.55
address 2: 10.11.110.55

因此可先用strcpy將a1存起來。

Endian -- 0x01020304
Big-endian: 01, 02, 03, 04 ((MSB)-first, network byte order)
Little-endian: 04, 03, 02, 01 (intel)

Name resolution

1. symbolic name比numbers-and-dots notation好記。
2. db的來源是/etc/hosts或是dns server。
3. 相關定義在netdb.h。
4. structure:

hostent: 用來表示db中的entry

Data Type

Description

char *h_name

host 的正式名稱 (如www.google.com.tw)

char **h_aliases

該host的別名

int h_addrtype

host 的address type。通常是AF_INET

int h_length

address 的長度,以byte為單位

char **h_addr_list

如果host連到不同的網路,就會有不同的address,這是所有address的pointer

char *h_addr

h_addr_list[0];;就是第一個host address

5. 在hostent裡的host address是network byte order。
6. 使用static的buffer,連續呼叫需先將結果存起來。
7. 失敗傳回nul,使用h_errno (用extern  int   h_errno宣告),error code如下:

h_errno

Description

HOST_NOT_FOUND

No such host is known in the data base.

TRY_AGAIN

This condition happens when the name server could not be contacted. If you try again later, you may succeed then.

NO_RECOVERY

A non-recoverable error occurred.

NO_ADDRESS

The host database contains an entry for the name, but it doesn't have an associated Internet address.

函數

Looking Up a Computer Name

NAME
       gethostbyname() - get network host entry
SYNOPSIS
       #include <netdb.h>
       extern int h_errno;
       struct hostent
       *gethostbyname(const char *name);
 
1. name可以是name / dot decimal address

Looking Up a Port Number by Name

NAME
   getservbyname() - get service entry
SYNOPSIS
   #include <netdb.h>
   struct servent *getservbyname(const char *name, const char *proto);
struct servent {
   char  *s_name;
   char  **s_aliases;
   int   s_port;
   char  *s_proto;
}
1. s_port: 是用network byte order表示

Looking Up a Protocol by Name

NAME
       getprotobyname() - get protocol entry
SYNOPSIS
       #include <netdb.h>
       struct protoent
       *getprotobyname(const char *name);
struct protoent {
   char  *p_name;
   char  **p_aliases;
   int   p_proto;
}
p_proto: protocol number (可用在socket())

Reference

byte order convention


书籍推荐