1. 程式人生 > >UNIX再學習 -- 網路IPC:套接字

UNIX再學習 -- 網路IPC:套接字

一、基本概念

1、程式設計介面

什麼是伯克利套接字(Berkeley Socket)?

美國加利福尼亞大學比克利分校於 1983年釋出 4.2 BSD Unix 系統。其中包含一套用 C 語言編寫的應用程式開發庫。該庫既可用於在同一臺計算機上實現程序間通訊,也可用於在不同計算機上實現網路通訊。當時的 Unix 還受 AT&T 的專利保護,因此直到 1989 年,伯克利大學才能自由釋出他們的作業系統和網路庫,而後者即被稱為伯克利套接字應用程式設計介面(Berkeley Socker APIs)。伯克利套接字介面的實現完全基於 TCP/IP 協議,因此它是構建一切網際網路應用的基石。幾乎所有現代作業系統都或多或少有一些源自伯克利套接字介面的實現。它已成為應用程式連線網際網路的標準介面

什麼是套接字?

套接字(socket)的本意是指電源插座,這裡將其引申為一個基於 TPC/IP 協議可實現基本網路通訊功能的邏輯物件。機器與機器的通訊,或者程序與程序的通訊,在這裡都可以被抽象地看作是套接字與套接字的通訊。應用程式編寫者無需瞭解網路協議的任何細節,更無需知曉系統核心和網路裝置的運作機制,只要把想傳送的資料寫入套接字,或從套接字中讀取想接收的資料即可。從這個意義上講,套接字就相當於一個檔案描述符,而網路就是一種特殊的檔案,面向網路的程式設計與面向檔案的程式設計已沒有分別,而這恰恰是 unix 系統一切皆檔案思想的又一例證。

什麼是套接字的異構性?

如前所述,套接字是對 ISO/OSI 網路協議模型中傳輸及其以下諸層的邏輯抽象
,是對 TCP/IP 網路通訊協議的高階封裝,因此無論所依賴的是什麼硬體,所執行的什麼作業系統所使用的是什麼程式語言,只要是基於套接字構建的應用程式,只要是在網際網路環境中通訊,就不會存在任何障礙。

2、通訊模式

單播模式

每個資料包傳送單個目的主機,目的地址指明單個接收者。伺服器可以及時響應客戶機的請求。伺服器可以針對不同客戶的不同請求提供個性化的服務。網路中傳輸的資訊量與請求該資訊的使用者量成正比,當請求該資訊的使用者量較大時,網路中將出現多份內容相同的資訊流,此時頻寬就成了限制傳輸質量的瓶頸。

廣播模式

一臺主機向網上的所有其它主機發送資料。無需路徑選擇,裝置簡單,維護方便,成本低廉。伺服器不用向每個客戶機單獨傳送資料,流量負載極低。
無法針對具體客戶的具體要求,及時提供個性化的服務。網路無條件地複製和轉發每一臺主機產生的資訊,所有的主機可以收到所有的資訊,而不管是否需要,網路資源利用率低,頻寬浪費嚴重。禁止廣播包穿越路由器,防止在更大範圍內氾濫。

多播模式

網路中的主機可以向路由器請求加入或退出某個組,路由器和交換機有選擇地複製和轉發資料,只將組內資料轉發給那些加入組的主機。需要相同資訊的客戶機只要加入同一個組即可共享同一份資料,降低了伺服器和網路的流量負載。既能一次將資料傳輸給多個有需要的主機,又能保證不影響其他不需要的主機。多播包可以穿越路由器,並在穿越中逐漸衰減。缺乏糾錯機制,丟包錯包在所難免。

3、繫結與連線

如前所述,套接字是一個提供給程式設計師使用的邏輯物件,它表示對 ISO/OSI 網路協議模型中傳輸層及其以下諸層的的抽象。但真正傳送和接收資料的畢竟是那些實實在在的物理裝置。這就需要在物理裝置和邏輯物件之間建立一種關聯,使後續所有針對這個邏輯物件的操作,最終都能反映到實際的物理裝置上。建立這種關聯關係的過程就叫做繫結
繫結只是把套接字物件和一個代表自己的物理裝置關聯起來。但為了實現通訊還需要把自己的物理裝置與對方的物理裝置關聯起來。只有這樣才能建立一種以物理裝置為媒介的,跨越不同程序甚至機器的,多個套接字物件之間的聯絡。建立這種聯絡的過程就叫做連線

二、常用函式

1、函式 socket:建立套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:成功返回套接字描述符,失敗返回 -1

(1)引數解析

domain:通訊域,即地址族,可取以下值:    AF_LOCAL/AF_UNIX   本地通訊,即程序間通訊 (重點)    AF_INET                       基於 IPv4 的網路通訊  (重點)    AF_INET6                     基於 IPv6 的網路通訊    AF_PACKET                  基於底層包介面的網路通訊type:套接字型別,可取以下值:    SOCK_STREAM         流式套接字,即使用 TCP 協議的套接字。有序的、可靠的、雙向的、面向連線的位元組流。    SOCK_DGRAM          資料報套接字,即使用 UDP 協議的套接字。固定長度的、無連線的、不可靠的報文傳遞。    SOCK_RAW               原始套接字,即使用 IP 協議的套接字。    SOCK_SEQPACKET    固定長度的、有序的、可靠的、面向連線的報文傳遞。protocol:特殊協議,通常不用,取 0 即可。表示為給定的域和套接字型別選擇預設協議。當對同一域和套接字型別支援多個協議時,可以使用 protocol 選擇一個特定協議。下圖列出了為因特網域套接字定義的協議:

(2)函式解析

socket 函式所返回的套接字描述符類似於檔案描述符,Unix 系統把網路也看成是檔案,傳送資料即寫檔案,接收資料即讀檔案,一切皆檔案。雖然套接字描述符本質上是一個檔案描述符,但不是所有引數為檔案描述符的函式都可以接受套接字描述符。下圖總結了到目前為止所討論的大多數以檔案描述符為引數的函式使用套接字描述符的行為。未指定和由實現定義的行為通常意味著該函式對套接字描述符無效。例如,lseek 不能以套接字描述符為引數,因為套接字不支援檔案偏移量的概念。

(3)示例說明

int sockfd = socket (AF_LOCAL, SOCK_DGRAN, 0);
if (sockfd == -1)
    perror ("socket"), exit (1);
    
int sockfd = socket (AF_INET, SOCK_STREAM, 0);
if (sockfd == -1)
    perror ("socket"), exit (1);
    
int sockfd = socket (AF_INET, SOCK_DGRAN, 0);
if (sockfd == -1)
    perror ("socket"), exit (1);

2、函式 shutdown:禁止套接字

套接字通訊是雙向的。可以採用 shutdown 函式來禁止一個套接字的 I/O。
#include<sys/socket.h>  
int shutdown(int sockfd, int how); 
返回值:若成功則返回0,出錯則返回-1.  
如果 how 是 SHUT_RD (關閉讀端),那麼無法從套接字讀取資料;如果 how 是 SHUT_WR (關閉寫端),那麼無法使用套接字傳送資料。如果 how 是 SHUT_RDWR ,則既無法讀取資料,又無法傳送資料。能夠關閉(close)套接字,為什麼還要使用 shutdown 呢?這裡有若干個理由。
首先,只有最後一個活動引用關閉時,close 才釋放網路端點。這意味著,如果複製一個套接字(如採用 dup),套接字直到關閉了最後一個引用它的檔案描述符之後才會被釋放。而 shutdown 允許使一個套接字處於不活動狀態,和引用它的檔案描述符數目無關。
其次,有時可以很方便地關閉套接字雙向傳輸中的一個方向。

3、地址結構

套接字介面庫通過地址結構定位一個通訊主體,可以是一個檔案,可以是一臺遠端主機,也可以是執行自己。基本地址結構,本身沒有實際意義,僅用於泛型化引數。

為使不同格式地址能夠傳入到套接字函式,地址會被強制轉換成一個通用的地址結構 sockaddr

struct sockaddr{
sa_family_t sa_family; //地址族
char sa_data[]; //地址值
...
};

本地地址結構,用於 AF_LOCAL/AF_UNIX 域的本地通訊

#include <sys/un.h>
struct sockaddr_un
{
	sa_family_t sun_family; //地址族(AF)LOCAL)
	char sun_path[];  //踏破戒指檔案路徑
};

網路地址結構,用於 AF_INET 域的 IPv4 網路通訊

#include <netinet/in.h>
struct sockaddr_in
{
	sa_family_t sin_family;  //地址族(AF_INET)
	in_port_t sin_port;  //埠號
	struct in_addr sin_addr;  //IP地址
};

struct in_addr
{
	in_addr_t s_addr;
};

typedef unit16_t in_port_t;  //無符號短整型
typedef unit32_t in_addr_t;  //無符號長整型

與IPv4因特網域(AF_INET)相比,IPv6因特網域(AF_INET6)套接字使用如下結構sockaddr_in6表示

struct in6_addr{
unit8_t s6_addr[16]; //IPv6地址
}

struct sockaddr_in6{
sa_family_t sin6_family; //地址family
in_port_t sin6_port; //埠地址
uint32_t sin6_flowinfo; //流量等級和flow資訊
struct in6_addr sin6_addr; //IPv6地址
uint32_t sin6_scope_id; //set of interfaces for scope
};
如前所述,通過 IP 地址可以定位網路上的一臺主機,但一臺主機上可能同時又多個網路應用在執行,究竟想跟哪個網路應用通訊呢?這就需要靠所謂的埠號來區分,因為不同的網路應用會使用不同的埠號。用 IP 地址定位主機,再用埠號定位執行子啊這臺主機上的一個具體的網路應用,這樣一種對通訊主體的描述才是唯一確定的。套接字介面庫中的埠號被定義為一個 16 位的無符號整數,其值介於 0 到 65535,其中 0 到 1024 已被系統和一些網路服務佔據,比如 21 埠用於 ftp 服務、23埠用於 telnet服務。80埠用於 www 服務等,因此一般應用程式最好選擇 1024 以上的埠號,以避免和這些服務衝突網路應用與單機應用不同,經常需要在具有不同硬體架構和作業系統的計算機之間交換資料,因此程式語言裡一些多位元組資料型別的位元組序問題就是需要特別予以關注。這就涉及到大小端問題,
假設一臺小端機器裡有一個 32 位整數:0x1234 5678,它在記憶體中按照小端位元組序低位低地址的規則存放:

現在,這個整數通過網路被傳送到一臺大端機器裡,記憶體中的形態不會有絲毫差別,但在大端機器看來地址越低的位元組數位應該越高,因此它會把這 4 個位元組解讀為:0x7856 3412,而這顯然有悖於傳送端的初衷。
 為了避免位元組序帶來的麻煩,套接字介面庫規定凡是在網路中交換的多位元組整數(short、int、long、long long 和它們的 unsigned 版本)一律採用網路位元組序傳輸而所謂網路位元組序,其實就是大端位元組序。也就是說,發資料時,先從主機位元組序轉成網路位元組序,然後傳送;收資料時,先從網路位元組序轉成主機位元組序,然後使用。
網路地址結構 sockaddr_in 中表示埠號的 sin_port 成員和表示 IP 地址的 sin_addr.s_addr 成員,分別為 2 位元組和 4 位元組的無符號整數,同樣需要用網路位元組序來表示。
struct sockaddr_in add;
addr.sin_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr (192.168.182.48);
其中 htons 函式將一個 16 位整數形式的埠號從主機序轉成網路序(小端轉大端),而 inet_addr 函式則將一個點分十進位制字串形式的 IP 地址轉成網路序的 32 位整數形式

4、將套接字和地址結構繫結

將套接字物件和自己的地址結構繫結在一起
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功返回 0,失敗返回 -1

(1)引數解析

sockfd:套接字描述符addr:自己的地址結構addrlen:地址結構長度(以位元組為單位)

(2)函式解析

套接字介面庫中的很多函式都用到地址結構,但為了同時支援不同的地址結構型別,其引數往往都會選擇更一般化的 sockaddr 型別的指標,使用時需要強制型別轉換。例如:
struct sockaddr_un_addr;
addr.sun_family = AF_LOCAL;
strcpy (addr.sun+path, "mysock");
if (bind (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
   perror ("bind"), exit (1);
在上面例子中,將套接字繫結到一個本地檔案上,此後所有針對這個本地檔案的操作都可以反映到這個套接字之上。
struct sockaddr_in addr;
addr.sun_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = int_addr ("192.168.182.48");
if (bind (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
   perror ("bind"), exit (1);
在上面例子中,將套接字繫結到一個 IP 和埠上,此後所有針對這個 IP 和埠的操作都可以反映到這個套接字之上。
struct sockaddr_in addr;
addr.sun_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
   perror ("bind"), exit (1);
在上面的例子中,IP 地址使用了 INADDR_ANY 巨集,該巨集的值被定義為 0,表示任意 IP。這樣的繫結操作主要用於伺服器,假設伺服器主機配置了多個 IP 地址,無論客戶機用哪個 IP 地址發起通訊,伺服器套接字都能感覺到。檢視 /usr/include/netinet/in.h 可看到關於 INADDR_ANY 定義:#defineINADDR_ANY((in_addr_t) 0x00000000)

(3)擴充套件

可以呼叫函式 getsockname 來發現繫結到一個套接字地址。
#include<sys/socket.h>  
int getsockname(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp); 
//成功則返回0,出錯則返回-1.  
呼叫 getsockname 之前,設定 alenp 為一個指向整數的指標,該整數指向緩衝區 sockaddr 的大小。返回時,該整數會被設定成返回地址的大小。如果該地址和提供的緩衝區長度不匹配,則將其截斷而不報錯。如果當前沒有繫結到該套接字的地址,其結果沒有定義。

如果套接字已經和對方連線,呼叫 getpeername 來找到對方的地址。
#include<sys/socket.h>  
int getpeername(int sockfd, struct sockaddr *restrict addr, socklen_t *restrict alenp); 
//成功則返回0,出錯則返回-1.  

5、建立連線

將套接字物件和對方的地址結構連線在一起
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
返回值:成功返回 0,失敗返回 -1

(1)引數解析

sockfd:套接字描述符addr:對方的地址結構addlen:地址結構長度(以位元組為單位)

(2)函式解析

struct sockaddr_un addr;
addr.sun_family = AF_LOCAL;
strcpy (addr.sun_path, "mysock");
if (connect (sockfd, (strcut sockaddr*)&addr, sizeof (addr)) == -1)
    perror ("connect"), exit (1);

struct sockaddr_in addr;
addr.sun_family = AF_INET;
addr.sin_port = htons (8888);
addr.sin_addr.s_addr = inet_addr ("192.168.182.48");
if (connect (sockfd, (strcut sockaddr*)&addr, sizeof (addr)) == -1)
    perror ("connect"), exit (1);

6、通過套接字傳送位元組流

#include <unistd.h>
ssize_t write(int sockfd, const void *buf, size_t count);
返回值:成功返回實際傳送的位元組數(0 表示未傳送),失敗返回 -1

(1)引數解析

sockfd:套接字描述符buf:記憶體緩衝區count:期望傳送的位元組數

(2)函式解析

ssize_t written = write (sockfd, text, towrite);
if (written== -1)
    perror ("write"), exit (1);

7、通過套接字接收位元組流

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
返回值:成功返回實際接收的位元組數(0表示連線關閉),失敗返回 -1

(1)引數解析

sockfd:套接字描述符buf:記憶體緩衝區count:期望讀取的位元組數

(2)函式解析

ssize_t readed = read (sockfd, text, toread);
if (readed == -1)
    perror ("read"), exit (1);

8、關閉套接字

關閉處於開啟狀態的套接字描述符
#include <unistd.h>
int close (int sockfd);
返回值:成功返回 0,失敗返回 -1

(1)引數解析

sockfd:套接字描述符

(2)函式解析

if (close (sockfd) == -1)
    perror ("close"), exit (1);

9、位元組序轉換

參看:C語言再學習-- 大端小端詳解(轉)
將主機或網路位元組序的長短整數轉換為網路或主機位元組序
#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);
返回值:返回網路或主機位元組序的長短整數
htonl()     //32位無符號整型的主機位元組順序到網路位元組順序的轉換(小端->>大端)
htons()
    //16位無符號短整型的主機位元組順序到網路位元組順序的轉換  (小端->>大端)
ntohl()  
  //32位無符號整型的網路位元組順序到主機位元組順序的轉換  (大端->>小端)
ntohs()
    //16位無符號短整型的網路位元組順序到主機位元組順序的轉換  (大端->>小端)
注,主機位元組順序,X86一般多為小端(little-endian),網路位元組順序,即大端(big-endian);h表示“主機”位元組序,n 表示“網路位元組序,l 表示“長”整數,s 表示“短”整數。舉個例子:
//示例一  
#include <stdio.h>  
#icnlude <arpa/inet.h>  
int main (void)  
{  
    union  
    {  
        short i;  
        char a[2];  
    }u;  
    u.a[0] = 0x11;  
    u.a[1] = 0x22;  
    printf ("0x%x\n", u.i);  //0x2211 為小端  0x1122 為大端  
    printf ("0x%.x\n", htons (u.i)); //大小端轉換   
    return 0;  
}  
輸出結果:  
0x2211  
0x1122  

10、IP地址轉換

(1)點分十進位制字串->網路位元組序 32 位無符號整數

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
返回值:返回網路位元組序 32 位無符號整數形式的 IP 地址

《1》引數解析

cp:點分十進位制字串形式的 IP 地址

《2》示例說明

struct sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr ("1292.168.182.48");

(2)點分十進位制字串->網路位元組序 32 位無符號整數

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
返回值:成功返回 0,失敗返回 -1

《1》引數解析

cp:點分十進位制字串形式的 IP 地址inp:輸出包含網路位元組序 32 位無符號整數形式 IP 地址的 in_addr 結構

《2》示例說明

struct sockaddr_in addr;
if (inet_aton ("192.168.182.48", &addr.sin_addr) == -1)
    perror ("inet_aton"), exit (1);

(3)網路位元組序 32 位無符號整數->點分十進位制字串

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr in);
返回值:返回點分十進位制字串形式的 IP 地址

《1》引數解析

in:包含網路位元組序 32 位無符號整數形式 IP 地址的 in_addr 結構

《2》示例說明

struct sockaddr_in addr;
if (inet_ntoa ("192.168.182.48", &addr.sin_addr) == -1)
    perror ("inet_ntoa"), exit (1);
printf ("%s\n", inet_ntoa (addr.sin_addr));

(4)函式 inet_ntop 和 inet_pton

需要說明一下,上面講的這兩個函式 inet_addr 和 inet_ntoa,用於二進位制地址格式與點分十進位制字元表示(a.c.b.d)之間的相互轉換。但是這些函式僅適用於 IPv4 地址。有兩個新函式 inet_ntop 和 inet_pton 具有相似的功能,而且同時支援 IPv4 地址和 IPv6 地址。
#include<arpa/inet.h>  
const char *inet_ntop(int domain, const void *restrict addr, char *restrict str, socklen_t size);   
返回值:若成功,返回地址字串指標;若出錯,返回NULL。  

int inet_pton(int domain, const char *restrict str, void *restrict addr);  
返回值:若成功,返回 1;若格式無效,返回 0;若出錯,返回 -1 
函式 inet_ntop 將網路位元組序的二進位制地址轉換成文字字串格式inet_pton 將文字字串格式轉換成網路位元組序的二進位制地址。引數 domian 僅支援兩個值:AF_INET 和 AF_INET6。舉個例子:
#include <stdio.h>  
#include <stdlib.h>  
#include <netinet/in.h>  
#include<arpa/inet.h>  
  
int main(void){  
        char addr_p[16];  
        struct in_addr addr_n;  
        if(inet_pton(AF_INET, "192.168.1.2",&addr_n) < 0){  
                perror("inet_pton");  
                return -1;  
        }  
        printf("address:%x\n",addr_n.s_addr);  
  
        if(inet_ntop(AF_INET, &addr_n,addr_p,(socklen_t)sizeof(addr_p)) == NULL){  
                perror("inet_ntop");  
                return -1;  
        }  
        printf("address:%s\n",addr_p);  
  
}  
輸出結果:
address:201a8c0
address:192.168.1.2

三、通訊程式設計

1、程序間通訊

(1)基於套接字實現程序間通訊的程式設計模型


(2)步驟解析

建立套接字時使用 AF_LOCAL 域。int sockfd = socket (AF_LOCAL, ...);
準備地址結構時使用 sockaddr_un 結構體型別
struct sockaddr_un addr;addr.sun_family = AF_LOCAL;strcpy (addr.sun_path, "mysock");
套接字檔案如不再用需要顯示刪除
unlink ("mysock");

(3)示例說明

//伺服器 sockA.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    int sockfd = socket (AF_LOCAL, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror ("socket");
        exit (EXIT_FAILURE);
    }
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy (addr.sun_path, "mysock");
    if (bind (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
    {
        perror ("bind");
        unlink("mysock");
        exit (EXIT_FAILURE);
    }
    char text[1024];
    ssize_t readed = read (sockfd, text, 1024);
    if (readed == -1)
    {
        perror ("read");
        unlink("mysock");
        exit (EXIT_FAILURE);
    }
    printf("客戶機發送內容:%s\n", text);
    if (close (sockfd) == -1)
    {
        perror ("close");
        exit (EXIT_FAILURE);
    }
    unlink("mysock");
    return 0;
}
//客戶機 sockB.c
#include <stdio.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int main()
{
    int sockfd = socket (AF_LOCAL, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror ("socket");
        exit (EXIT_FAILURE);
    }
    struct sockaddr_un addr;
    addr.sun_family = AF_LOCAL;
    strcpy (addr.sun_path, "mysock");
    if (connect (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
    {
        perror ("connect");
        exit (EXIT_FAILURE);
    }
    char text[1024] = "你好,伺服器";
    ssize_t written = write (sockfd, text, strlen(text));
    if (written == -1)
    {
        perror ("write");
        exit (EXIT_FAILURE);
    }
    if (close (sockfd) == -1)
    {
        perror ("close");
        exit (EXIT_FAILURE);
    }
    return 0;
}
輸出結果:

在一個終端執行:
# ./sockA
客戶機發送內容:你好,伺服器

在另一個終端執行:
# ./sockB

2、網路通訊

(1)基於套接字實現網路通訊的程式設計模型


(2)步驟解析

建立套接字時使用 AF_INET 域int sockfd = socket (AF_INET, ...);
準備地址結構時使用 sockaddr_in 結構體型別
—伺服器struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons (8888);addr.sin_addr.s_addr = INADDR_ANY;—客戶機struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons (8888);addr.sin_addr.s_addr = inet_addr ("129.168.182.48");—客戶機連線本機伺服器可以使用本地環回地址addr.sin_addr.s_addr = inet_addr ("127.0.0.1");

(3)示例說明

//伺服器  sockNetA.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
    int sockfd = socket (AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror ("socket");
        exit (EXIT_FAILURE);
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons (8888);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
    {
        perror ("bind");
        exit (EXIT_FAILURE);
    }
    char text[1024];
    ssize_t readed = read (sockfd, text, 1024);
    if (readed == -1)
    {
        perror ("read");
        exit (EXIT_FAILURE);
    }
    printf("客戶機發送內容:%s\n", text);
    if (close (sockfd) == -1)
    {
        perror ("close");
        exit (EXIT_FAILURE);
    }
    return 0;
}
//客戶機 sockNetB.c
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
    int sockfd = socket (AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1)
    {
        perror ("socket");
        exit (EXIT_FAILURE);
    }
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons (8888);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (connect (sockfd, (struct sockaddr*)&addr, sizeof (addr)) == -1)
    {
        perror ("connect");
        exit (EXIT_FAILURE);
    }
    char text[1024] = "你好,伺服器";
    ssize_t written = write (sockfd, text, strlen(text));
    if (written == -1)
    {
        perror ("write");
        exit (EXIT_FAILURE);
    }
    if (close (sockfd) == -1)
    {
        perror ("close");
        exit (EXIT_FAILURE);
    }
    return 0;
}
輸出結果:

在一個終端執行:
# ./sockNetA
客戶機發送內容:你好,伺服器


在另一個終端執行:
# ./sockNetB