Berkeley套接字

<image>

網際網路伯克利套接字，又稱為BSD 套接字（Internet Berkeley sockets，BSD sockets），是一種應用程式接口（API），用於網路套接字（socket）與Unix域套接字，包括了一個用C語言寫成的應用程式開發庫，主要用於實現進程間通訊，在電腦網絡通訊方面被廣泛使用。

Berkeley套接字（也作BSD套接字應用程式接口）剛開始是4.2BSD Unix作業系統（於1983發布）的一套應用程式接口。然而，由於AT&T的專利保護著UNIX，所以只有在1989年伯克利大學才能自由地發布自己的作業系統和網路庫。

Berkeley套接字應用程式接口形成了事實上的網路套接字的標準精髓。大多數其他的程式語言使用與這套用C語言寫成的應用程式接口類似的接口。這套應用程式接口也被用於Unix域套接字（Unix domain sockets），後者可以在單機上為進程間通訊（IPC）的接口。

這種基於流的傳輸層接口（TLI）為套接字應用程式接口提供了一種選擇。

## Berkeley套接字接口

Berkeley套接字接口，一個應用程式接口（API），使用一個Internet套接字的概念，使主機間或者一台電腦上的進程間可以通訊。它可以在很多不同的輸入/輸出設備和驅動之上運行，儘管這有賴於作業系統的具體實現。接口實現用於TCP/IP協議，因此它是維持Internet的基本技術之一。它是由加利福尼亞的伯克利大學開發，最初用於Unix系統。如今，所有的現代作業系統都有一些源於Berkeley套接字接口的實現，它已成為連接Internet的標準接口。

套接字接口的接入有三個不同的級別，最基礎的也是最有效的就是raw socket級別接入。很少的應用程式需要在外向通訊控制的這個級別接入，所以raw socket級別是只為了用於開發電腦Internet相關技術的。最近幾年，大多數的作業系統已經實現了對它的全方位支持，包括Windows XP。

## 使用Berkeley套接字的系統

由於Berkeley套接字是第一個socket，大多數程式設計師很熟悉它們，所以大量系統把伯克利套接字作為其主要的網路API。一個不完整的列表如下：

- Windows Sockets (Winsock)，和Berkeley Sockets很相似，最初是為了便於移植Unix程序。
- Java Sockets
- Python sockets
- Perl sockets

## 頭文件

Berkeley套接字接口的定義在幾個頭文件中。這些文件的名字和內容與具體的實現之間有些許的不同。大體上包括：

`<sys/socket.h>`：核心BSD套接字核心函數和數據結構。

AF_INET、AF_INET6 地址集和它們相應的協議集PF_INET、PF_INET6. 廣泛用於Internet，這些包括了IP位址和TCP、UDP埠號。

`<netinet/in.h>`：AF_INET 和AF_INET6 地址家族和他們對應的協議家族 PF_INET 和 PF_INET6。在網際網路編程中廣泛使用，包括IP位址以及TCP和UDP埠號。

`<sys/un.h>`：PF_UNIX/PF_LOCAL 地址集。用於運行在一台電腦上的程序間的本地通信，不用於網路通訊。

`<arpa/inet.h>`：處理數值型IP位址的函數。

`<netdb.h>`：將協議名和主機名翻譯為數值地址的函數。搜索本地數據以及DNS。

## 套接字API函數

這個列表是一個Berkeley套接字API庫提供的函數或者方法的概要：

- `socket()` 創建一個新的確定類型的套接字，類型用一個整型數值標識（文件描述符），並為它分配系統資源。
- `bind()` 一般用於伺服器端，將一個套接字與一個套接字地址結構相關聯，比如，一個指定的本地埠和IP位址。
- `listen()` 用於伺服器端，使一個綁定的TCP套接字的tcp狀態由CLOSE轉至LISTEN；作業系統內核為此監聽socket所對應的tcp伺服器建立一個pending socket隊列和一個established socket隊列；參數backlog指定pending socket隊列的長度，0表示長度可以無限大。pending socket，就是某客戶端三次握手的syn包到達，內核為這個syn包對應的tcp請求生成一個socket（狀態為SYN_RECV），但三次握手還沒有完成時的socket。
- `connect()` 用於客戶端，為一個套接字分配一個自由的本地埠號。如果是TCP套接字的話，它會試圖獲得一個新的TCP連接。
- `accept()` 用於伺服器端。它接受一個從遠端客戶端發出的創建一個新的TCP連接的接入請求，創建一個新的套接字，與該連接相應的套接字地址相關聯。
- `send()`和`recv()`,或者`write()`和`read()`,或者`recvfrom()`和`sendto()`, 用於往/從遠程套接字發送和接受數據。
- `close()` 用於系統釋放分配給一個套接字的資源。如果是TCP，連接會被中斷。
- `gethostbyname()`和`gethostbyaddr()` 用於解析主機名和地址。
- `select()` 用於修整有如下情況的套接字列表：準備讀，準備寫或者是有錯誤。
- `poll()` 用於檢查套接字的狀態。套接字可以被測試，看是否可以寫入、讀取或是有錯誤。
- `getsockopt()` 用於查詢指定的套接字一個特定的套接字選項的當前值。
- `setsockopt()` 用於為指定的套接字設定一個特定的套接字選項。

更多的細節如下給出。

### socket()

`socket()` 為通訊創建一個端點，為套接字返回一個文件描述符。socket 有三個參數：

- domain 為創建的套接字指定協議集（或稱做地址族 address family)。例如：
- `AF_INET` 表示IPv4網路協議
- `AF_INET6` 表示IPv6
- `AF_UNIX` 表示本地套接字（使用一個文件）
- `SOCK_STREAM` （可靠的面向流服務或流套接字）
- `SOCK_DGRAM` （數據報文服務或者數據報文套接字）
- `SOCK_SEQPACKET` （可靠的連續數據包服務）
- `SOCK_RAW` (在網路層之上自行指定運輸層協議頭，即原始套接字)
- protocol 指定實際使用的傳輸協議。最常見的就是`IPPROTO_TCP`、`IPPROTO_SCTP`、`IPPROTO_UDP`、`IPPROTO_DCCP`。這些協議都在中有詳細說明。如果該項為「`0`」的話，即根據選定的domain和type選擇使用預設協議。

如果發生錯誤，函數返回值為-1。否則，函數會返回一個代表新分配的描述符的整數。

原型：

```c
int socket(int domain, int type, int protocol);
```

### bind()

`bind()` 為一個套接字分配地址。當使用`socket()`創建套接字後，只賦予其所使用的協議，並未分配地址。在接受其它主機的連接前，必須先調用`bind()`為套接字分配一個地址。`bind()`有三個參數：

- `sockfd`, 表示使用bind函數的套接字描述符
- `my_addr`, 指向sockaddr結構（用於表示所分配地址）的指針
- `addrlen`, 用socklen_t欄位指定了sockaddr結構的長度

如果發生錯誤，函數返回值為-1，否則為0。

原型:

```c
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
```

### listen()

當socket和一個地址綁定之後，`listen()`函數會開始監聽可能的連接請求。然而，這只能在有可靠數據流保證的時候使用，例如：數據類型(`SOCK_STREAM`, `SOCK_SEQPACKET`)。

listen函數需要兩個參數：

- `sockfd`, 一個socket的描述符.
- `backlog`, 完成三次握手、等待accept的全連接的隊列的最大長度上限。對於AF_INET類型的socket，全連接數量為：min(backlog, somaxconn)。當隊列滿時，新的全連接會返回錯誤。somaxconn默認為128.半連接隊列的最大長度可通過sysctl函數設置tcp_max_syn_backlog，默認值為256。Linux Kernel 2.2之後，全連接隊列與半連接隊列分別叫做accept queue與syns queue。根據/proc/sys/net/ipv4/tcp_abort_on_overflow里的值為0表示如果三次握手第三步的時候全連接隊列滿了，那麼server扔掉client發過來的ack，server過一段時間再次發送syn+ack給client（也就是重新走握手的第二步），如果client超時等待比較短，就很容易異常；tcp_abort_on_overflow為1表示第三次握手時如果全連接隊列滿了，server發送一個reset包給client，表示廢掉這個握手過程和這個連接。

一旦連接被接受，返回0表示成功，錯誤返回-1。

原型:

```c
int listen(int sockfd, int backlog);
```

### accept()

當應用程式監聽來自其他主機的面對數據流的連接時，通過事件（比如Unix select系統調用）通知它。必須用 `accept()`函數初始化連接。`accept()`為每個連接創立新的套接字並從監聽隊列中移除這個連接。它使用如下參數：

- `sockfd`,監聽的套接字描述符
- `cliaddr`, 指向sockaddr 結構體的指針，客戶機地址資訊。
- `addrlen`,指向 `socklen_t`的指針，確定客戶機地址結構體的大小。

返回新的套接字描述符，出錯返回-1。進一步的通信必須通過這個套接字。

Datagram 套接字不要求用accept處理，因為接收方可能用監聽套接字立即處理這個請求。

函數原型：

```c
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
```

### connect()

`connect()`系統調用為一個套接字設置連接，參數有文件描述符和主機地址。

某些類型的套接字是無連接的，大多數是UDP協議。對於這些套接字，連接時這樣的：默認發送和接收數據的主機由給定的地址確定，可以使用 send和 recv。

返回-1表示出錯，0表示成功。

函數原型：

```c
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
```

### select()

```c
int select (int nfds, fd_set FAR * readfds, fd_set FAR * writefds, fd_set FAR * exceptfds, const struct timeval FAR * timeout);
```

- 第一個參數nfds：沒有用，僅僅為與伯克利Socket兼容而提供。
- 第二個參數readfds：指定一個Socket陣列，select檢查該陣列中的所有Socket。如果成功返回，則readfds中存放的是符合『可讀性』條件的陣列成員（如緩衝區中有可讀的數據）。
- 第三個參數writefds：指定一個Socket陣列，select檢查該陣列中的所有Socket。如果成功返回，則writefds中存放的是符合『可寫性』條件的陣列成員（包括連接成功）。
- 第四個參數exceptfds：指定一個Socket陣列，select檢查該陣列中的所有Socket。如果成功返回，則cxceptfds中存放的是符合『有異常』條件的陣列成員（包括連接接失敗）。
- 第五個參數timeout：指定select執行的最長時間，如果在timeout限定的時間內，readfds、writefds、exceptfds中指定的Socket沒有一個符合要求，就返回0。

### getsockname() 和 getpeername ()

```c
int getsockname (SOCKET s, struct sockaddr *name, int* namelen);
```

getsockname函數獲取已綁定（可能是未調用bind的系統自動綁定）的套接口本地協議地址。

```c
int getpeername (SOCKET s, struct sockaddr *name, int* namelen);
```

getpeername函數獲得與指定套接口連接的遠程信息（IP:PORT）。

### gethostbyname() 和 gethostbyaddr()

`gethostbyname()` 和 `gethostbyaddr()`函數是用來解析主機名和地址的。可能會使用DNS服務或者本地主機上的其他解析機制（例如查詢/etc/hosts）。返回一個指向 struct hostent的指針，這個結構體描述一個IP主機。函數使用如下參數：

- name 指定主機名。例如 www.wikipedia.org
- addr 指向 struct in_addr的指針，包含主機的地址。
- len 給出 addr的長度，以字節為單位。
- type 指定地址族類型 （比如 AF_INET）。

出錯返回NULL指針，可以通過檢查 h_errno 來確定是臨時錯誤還是未知主機。正確則返回一個有效的 struct hostent *。

這些函數並不是伯克利套接字嚴格的組成部分。這些函數可能是過時了，只能處理IPv4地址。在IPv6中，替代的新函數是 getaddrinfo and getnameinfo, 這些新函數是基於addrinfo數據結構。參考。

函數原型：

```c
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, int len, int type);
```

### setsockopt()

```c
int setsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
```

setsockopt函數用來設置套接字選項。

參數：

- sockfd: 套接字
- level: 協議層 SOL_SOCKET/IPPROTO_IP/IPPRO_TCP
- optname: 選項名 每一個協議層都有其固定的選項名
- optval: 緩衝區 set是指向將要存放的地址, get是指向目前存放資訊的地址
- optlen: 緩衝區大小長度

在socket層, 有以下一些選項:

- SO_BROADCAST 允許發送廣播數據 int
- SO_DEBUG 允許調試 int
- SO_DONTROUTE 不查找路由 int
- SO_ERROR 獲得套接字錯誤 int
- SO_KEEPALIVE 保持連接 int
- SO_LINGER 延遲關閉連接 struct linger
- SO_OOBINLINE 帶外數據放入正常數據流 int
- SO_RCVBUF 接收緩衝區大小 int
- SO_SNDBUF 發送緩衝區大小 int
- SO_RCVLOWAT 接收緩衝區下限 int
- SO_SNDLOWAT 發送緩衝區下限 int
- SO_RCVTIMEO 接收超時 struct timeval
- SO_SNDTIMEO 發送超時 struct timeval
- SO_REUSERADDR 允許重用本地地址和埠 int
- SO_TYPE 獲得套接字類型 int
- SO_BSDCOMPAT 與BSD系統兼容 int

### ioctlsocket

```c
int ioctlsocket(_In_ SOCKET s,  _In_ long   cmd,   _Inout_ u_long *argp);
```

根據第二個參數的取值，設置socket I/O模式：

- FIONBIO：允許設置socket為阻塞或非阻塞：當第三個參數argp為0是阻塞模式，為非0則為非阻塞模式。如果已對一個套接口進行了WSAAsynSelect 操作，則任何用ioctlsocket來把套接口重新設置成阻塞模式的試圖將以WSAEINVAL失敗。為了把套接口重新設置成阻塞模式，應用程式必須首先用WSAAsynSelect調用（IEvent參數置為0）來禁至WSAAsynSelect, 或者通過設置lNetworkEvents參數為0來調用WSAEventSelect。
- FIONREAD：返回套接字s下一次自動讀入的數據量的大小。用來確定（determin）懸掛（pending）在網路輸入緩衝區中，能從socket s中讀取的數據總數。返回單次recv函數能讀取的數據的總數
- SIOCATMARK：返回所有的「緊急」（帶外）數據是否都已被讀入。僅適用於SOCK_STREAM類型的套接口，且該套接口已被設置為可以在線接收帶外數據（SO_OOBINLINE）

### inet_pton與inet_ntop

inet_pton與inet_ntop兩個函數，在ASCII字符描述的IP位址與網路字節序的4位元組IP位址之間轉換。字母"n"與"p"，分別是numerical與presentation的縮寫。

## 協議和地址

套接字API是Unix網路的通用接口，允許使用各種網路協議和地址。

下面列出了一些例子，在現在的 Linux 和 BSD 中一般都已經實現了。

```
PF_LOCAL, PF_UNIX, PF_FILE
                Local to host (pipes and file-domain)
PF_INET         IP protocol family
PF_AX25         Amateur Radio AX.25
PF_IPX          Novell Internet Protocol
PF_APPLETALK    Appletalk DDP
PF_NETROM       Amateur radio NetROM
PF_BRIDGE       Multiprotocol bridge
PF_ATMPVC       ATM PVCs
PF_X25          Reserved for X.25 project
PF_INET6        IP version 6
PF_ROSE         Amateur Radio X.25 PLP
PF_DECnet       Reserved for DECnet project
PF_NETBEUI      Reserved for 802.2LLC project
PF_SECURITY     Security callback pseudo AF
PF_KEY          PF_KEY key management API
PF_NETLINK, PF_ROUTE
                routing API
PF_PACKET       Packet family
PF_ASH          Ash
PF_ECONET       Acorn Econet
PF_ATMSVC       ATM SVCs
PF_SNA          Linux SNA Project
PF_IRDA         IRDA sockets
PF_PPPOX        PPPoX sockets
PF_WANPIPE      Wanpipe API sockets
PF_BLUETOOTH    Bluetooth sockets
```

socket的通用address描述結構sockaddr是一個16位元組大小的結構（2+14），sa_family可以認為是socket address family的縮寫。另外的14位元組是用來描述地址。當指定sa_family=AF_INET之後，sa_data的形式也就被固定了下來：最前端的2位元組用於記錄16位的埠，緊接著的4位元組用於記錄32位的IP位址，最後的8位元組清空為零。

```c
struct sockaddr
{
    unsigned short sa_family;
    char sa_data[14];
};

struct sockaddr_in //means socket address internet
{
    unsigned short sin_family; //sin means socket (address) internet
    unsigned short sin_port;
    struct in_addr sin_addr;
    char sin_zero[8];
};

struct in_addr
{
    unsigned long s_addr; // means source address
};
```

## 使用TCP的伺服器客戶機舉例

### 伺服器

設置一個簡單的TCP伺服器涉及下列步驟：

- 調用socket函數建立套接字，應當使用的參數參見例程。
- 調用bind函數把套接字綁定到一個監聽埠上。注意bind函數需要接受一個sockaddr_in結構體作為參數，因此在調用bind函數之前, 程序要先聲明一個 sockaddr_in結構體,用memset函數將其清零，然後將其中的sin_family設置為AF_INET，接下來，程序需要設置其sin_port成員變量，即監聽埠。需要說明的是，sin_port中的埠號需要以網路字節序存儲，因此需要調用htons函數對埠號進行轉換（函數名是"host to network short"的縮寫）。
- 調用listen函數，使該套接字成為一個處在監聽狀態的套接字。
- 接下來，伺服器可以通過accept函數接受客戶端的連接請求。若沒有收到連接請求，accept函數將不會返回並阻塞程序的執行。接收到連接請求後，accept函數會為該連接返回一個套接字描述符。accept函數可以被多次調用來接受不同客戶端的連接請求，而且之前的連接仍處於監聽狀態——直到其被關閉為止。
- 現在，伺服器可以通過對send，recv或者對write，read等函數的調用來同客戶端進行通信。
- 對於一個不再需要的套接字，可以使用close函數關閉它。Note that if there were any calls to `fork()`, each process must close the sockets it knew about (the kernel keeps track of how many processes have a descriptor open), and two processes should not use the same socket at once.

```c
  /* Server code in C */
     
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  
  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
    if(-1 == SocketFD)
    {
      perror("can not create socket");
      exit(EXIT_FAILURE);
    }
  
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
  
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    stSockAddr.sin_addr.s_addr = INADDR_ANY;
  
    if(-1 == bind(SocketFD,(const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
    {
      perror("error bind failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
  
    if(-1 == listen(SocketFD, 10))
    {
      perror("error listen failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
  
    for(;;)
    {
      int ConnectFD = accept(SocketFD, NULL, NULL);
  
      if(0 > ConnectFD)
      {
        perror("error accept failed");
        close(SocketFD);
        exit(EXIT_FAILURE);
      }
  
     /* perform read write operations ... */
  
      shutdown(ConnectFD, SHUT_RDWR);
  
      close(ConnectFD);
    }

    close(SocketFD);
    return 0;
  }
```

Python實現：

```python
from socket import *
from time import ctime
HOST=''
PORT=1100
BUFSIZ=1024
ADDR=(HOST, PORT)
sock=socket(AF_INET, SOCK_STREAM)
sock.bind(ADDR)
sock.listen(5)
while True:
    print('waiting for connection')
    tcpClientSock, addr=sock.accept()
    print('connect from ', addr)
    while True:
        try:
            data=tcpClientSock.recv(BUFSIZ)
        except:
            print(e)
            tcpClientSock.close()
            break
        if not data:
            break
        s='Hi,you send me :[%s] %s' %(ctime(), data.decode('utf8'))
        tcpClientSock.send(s.encode('utf8'))
        print([ctime()], ':', data.decode('utf8'))
tcpClientSock.close()
sock.close()
```

### 客戶機

建立一個客戶機連接涉及以下步驟：

- 調用 `socket()`建立套接字。
- 用`connect()`連接到伺服器,類似伺服器端的操作，將一個sin_family設為AF_INET,sin_port設為伺服器的監聽埠（依然要以網路字節序）,sin_addr設為伺服器IP位址的（還是要用網路字節序）的sockaddr_in作為參數傳入。
- 用`send()` 和 `recv()` 或者 `write()` 和 `read()`進行通信。
- 用`close()`終止連接。如果調用`fork()`, 每個進程都要用`close()`。

```c
  /* Client code in C */

  #include <sys/types.h>
  #include <sys/socket.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <unistd.h>
  
  int main(void)
  {
    struct sockaddr_in stSockAddr;
    int Res;
    int SocketFD = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  
    if (-1 == SocketFD)
    {
      perror("cannot create socket");
      exit(EXIT_FAILURE);
    }
  
    memset(&stSockAddr, 0, sizeof(struct sockaddr_in));
  
    stSockAddr.sin_family = AF_INET;
    stSockAddr.sin_port = htons(1100);
    Res = inet_pton(AF_INET, "192.168.1.3", &stSockAddr.sin_addr);
  
    if (0 > Res)
    {
      perror("error: first parameter is not a valid address family");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
    else if (0 == Res)
    {
      perror("char string (second parameter does not contain valid ipaddress");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }

    if (-1 == connect(SocketFD, (const struct sockaddr *)&stSockAddr, sizeof(struct sockaddr_in)))
    {
      perror("connect failed");
      close(SocketFD);
      exit(EXIT_FAILURE);
    }
  
    /* perform read write operations ... */
  
    shutdown(SocketFD, SHUT_RDWR);
  
    close(SocketFD);
    return 0;
  }
```

Python實現：

```python
from socket import *

HOST='192.168.1.3'
PORT=1100
BUFSIZ=1024
ADDR=(HOST, PORT) 
client=socket(AF_INET, SOCK_STREAM)
client.connect(ADDR)
while True:
    data=input('>')
    if not data:
        break
    client.send(data.encode('utf8'))
    data=client.recv(self.BUFSIZ)
    if not data:
        break
    print(data.decode('utf8'))
```

## 使用UDP的伺服器客戶機舉例

使用者數據報協議（UDP）是一個不保證正確傳輸的無連接協議。UDP數據包可能會亂序到達，多次到達或者直接丟失。但是設計的負載比TCP小。

UDP地址空間，也即是UDP埠，和TCP埠是沒有關係的。

### 伺服器

Code may set up a UDP server on port 7654 as follows:

```c
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h> /* for close() for socket */ 
#include <stdlib.h>

int main(void)
{
  int sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  struct sockaddr_in sa; 
  char buffer[1024];
  ssize_t recsize;
  socklen_t fromlen;

  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = INADDR_ANY;
  sa.sin_port = htons(7654);
 
  if (-1 == bind(sock,(struct sockaddr *)&sa, sizeof(struct sockaddr)))
  {
    perror("error bind failed");
    close(sock);
    exit(EXIT_FAILURE);
  }

  for (;;) 
  {
    printf ("recv test....\n");
    recsize = recvfrom(sock, (void *)buffer, 1024, 0, (struct sockaddr *)&sa, &fromlen);
    if (recsize < 0)
      fprintf(stderr, "%s\n", strerror(errno));
    printf("recsize: %d\n ",recsize);
    sleep(1);
    printf("datagram: %s\n",buffer);
  }
}
```

上面的無限循環用recvfrom接收給UDP埠7654的數據包。使用如下參數：

- 指向緩存數據指針
- 緩存大小
- 標誌
- 地址
- 地址結構體大小

同樣功能的Python實現：

```python
import socket
port=7654
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#从指定的端口，从任何发送者，接收UDP数据
s.bind(('',port))
print('正在等待接入...')
while True:
    #接收一个数据
    data,addr=s.recvfrom(1024)
    print('Received:',data,'from',addr)
```

### 客戶機

用UDP數據包發送一個"Hello World!" 給地址127.0.0.1（迴環地址），埠 7654。

```c
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h> /* for close() for socket */
 
int main(int argc, char *argv[])
{
  int sock;
  struct sockaddr_in sa;
  int bytes_sent, buffer_length;
  char buffer[200];
 
  buffer_length = snprintf(buffer, sizeof(buffer), "Hello World!");
 
  sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
  if (-1 == sock) /* if socket failed to initialize, exit */
    {
      printf("Error Creating Socket");
      exit(EXIT_FAILURE);
    }
 
  memset(&sa, 0, sizeof(sa));
  sa.sin_family = AF_INET;
  sa.sin_addr.s_addr = htonl(0x7F000001);
  sa.sin_port = htons(7654);
 
  bytes_sent = sendto(sock, buffer, buffer_length, 0,(struct sockaddr*)&sa, sizeof (struct sockaddr_in));
  if (bytes_sent < 0)
    printf("Error sending packet: %s\n", strerror(errno));
 
  close(sock); /* close the socket */
  return 0;
}
```

`buffer`指定要發送數據的指針, `buffer_length`指定緩存內容的大小。

同樣功能的Python實現：

```python
import socket
port=7654
host='localhost'
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
s.sendto(b'Hello World!',(host,port))
```

## 參考資料

The "de jure" standard definition of the Sockets interface is contained in the POSIX standard, known as:

- IEEE Std. 1003.1-2001 Standard for Information Technology—Portable Operating System Interface (POSIX).
- Open Group Technical Standard: Base Specifications, Issue 6, December 2001.
- ISO/IEC 9945:2002

Information about this standard and ongoing work on it is available from the Austin website.

The IPv6 extensions to the base socket API are documented in RFC 3493 and RFC 3542.

## 外部連結

- 手冊頁
- accept(2)
- connect(2)
- Beej's Guide to Network Programming - 2007
- Beej's Guide to Network Programming 正體中文版（zh-tw） - 2014
- Beej's Guide to Network Programming 簡體中文版（zh-cn） - 2014
- UnixSocket FAQ
- Get system IP list - C++ Example
- quick TCP-IP NetIntro with C examples
- Porting Berkeley Socket programs to Winsock - Microsoft's documentation.
- Programming UNIX Sockets in C - Frequently Asked Questions - 1996
- Linux network programming - Linux Journal, 1998

本條目部分或全部內容出自以GFDL授權發佈的《自由線上電腦詞典》（FOLDOC）