热门问题
时间线
聊天
视角

Berkeley插座

Berkeley套接字 来自维基百科,自由的百科全书

Berkeley套接字
Remove ads

網際網路柏克萊插座,又稱為BSD 插座(英語:Internet Berkeley socketsBSD sockets) ,是一種應用程式介面(API),用於網路插座( socket)與Unix域插座,包括了一個用C語言寫成的應用程式開發庫,主要用於實現行程間通訊,在電腦網路通訊方面被廣泛使用。

Thumb
TCP 基本流程圖

Berkeley插座(也作BSD插座應用程式介面)剛開始是4.2BSD Unix作業系統(於1983發布)的一套應用程式介面。然而,由於AT&T的專利保護著UNIX,所以只有在1989年柏克萊大學才能自由地發布自己的作業系統和網路庫。

Berkeley插座應用程式介面形成了事實上的網路插座的標準精髓。 大多數其他的程式語言使用與這套用C語言寫成的應用程式介面[1]類似的介面。 這套應用程式介面也被用於Unix域插座(Unix domain sockets),後者可以在單機上為行程間通訊(IPC)的介面。

這種基於流的傳輸層介面(TLI)為插座應用程式介面提供了一種選擇。 不過,最近[何時?]提供TLI應用程式介面的的系統同時也提供Berkeley插座應用程式介面。[來源請求]

Remove ads

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() 用於為指定的插座設定一個特定的插座選項。

更多的細節如下給出。

Remove ads

socket()

socket() 為通訊建立一個端點,為插座返回一個檔案描述子。 socket() 有三個參數:

  • domain 為建立的插座指定協定集(或稱做位址族 address family)。 例如:
    • AF_INET 表示IPv4網路協定
    • AF_INET6 表示IPv6
    • AF_UNIX 表示本地插座(使用一個檔案)
  • type(socket類型)如下:
    • SOCK_STREAM (可靠的面向流服務或流插座
    • SOCK_DGRAM (資料報服務或者資料報插座
    • SOCK_SEQPACKET (可靠的連續封包服務)
    • SOCK_RAW (在網路層之上自行指定運輸層協定頭,即原始插座)
  • protocol 指定實際使用的傳輸協定。 最常見的就是IPPROTO_TCPIPPROTO_SCTPIPPROTO_UDPIPPROTO_DCCP。這些協定都在<netinet/in.h>中有詳細說明。 如果該項為「0」的話,即根據選定的domain和type選擇使用預設協定。

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

原型:
int socket(int domain, int type, int protocol);
Remove ads

bind()

bind() 為一個插座分配位址。當使用socket()建立插座後,只賦予其所使用的協定,並未分配位址。在接受其它主機的連接前,必須先呼叫bind()為插座分配一個位址。bind()有三個參數:

  • sockfd, 表示使用bind函式的插座描述符
  • my_addr, 指向sockaddr結構(用於表示所分配位址)的指標
  • addrlen, 用socklen_t欄位指定了sockaddr結構的長度

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

原型
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。

原型:

int listen(int sockfd, int backlog);
Remove ads

accept()

當應用程式監聽來自其他主機的面對資料流的連接時,通過事件(比如Unix select()系統呼叫)通知它。必須用 accept()函式初始化連接。 accept()為每個連接創立新的插座並從監聽佇列中移除這個連接。它使用如下參數:

  • sockfd,監聽的插座描述符
  • cliaddr, 指向sockaddr 結構體的指標,客戶機位址資訊。
  • addrlen,指向 socklen_t的指標,確定客戶機位址結構體的大小 。

返回新的插座描述符,出錯返回-1。進一步的通訊必須通過這個插座。

Datagram 插座不要求用accept()處理,因為接收方可能用監聽插座立即處理這個請求。

函式原型:
int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);

connect()

connect()系統呼叫為一個插座設定連接,參數有檔案描述子和主機位址。

某些類型的插座是無連接的,大多數是UDP協定。對於這些插座,連接時這樣的:預設傳送和接收資料的主機由給定的位址確定,可以使用 send()和 recv()。 返回-1表示出錯,0表示成功。

函式原型:
int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

select()

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。
Remove ads

getsockname() 和 getpeername ()

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

getsockname函式取得已繫結(可能是未呼叫bind的系統自動繫結)的套介面本地協定位址。

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資料結構。參考<Ws2tcpip.h>。

函式原型:
struct hostent *gethostbyname(const char *name);
struct hostent *gethostbyaddr(const void *addr, int len, int type);

setsockopt()

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

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網路的通用介面,允許使用各種網路協定和位址。

下面列出了一些例子,在現在的 LinuxBSD 中一般都已經實現了。

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位元組清空為零。

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.
  /* 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實現:

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()
  /* 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實現:

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:

#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實現:

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 。

#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實現:

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

參見

參考資料

外部連結

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads