1. 程式人生 > 實用技巧 ># 2021-01-03 #「tar」- 限制資源使用

# 2021-01-03 #「tar」- 限制資源使用

SOCKET程式設計

本身也是一種程序的通訊。

傳統的程序間通訊藉助核心提供的IPC機制進行, 但是隻能限於本機通訊, 若要跨機通訊, 就必須使用網路通訊.( 本質上藉助核心-核心提供了socket偽檔案的機制實現通訊- ---實際上是使用檔案描述符), 這就需要用到核心提供給使用者的socket API函式庫.

socket實際上也是建立檔案描述符來處理但是有點區別:

對比pipe管道講述socket檔案描述符的區別.

使用socket會建立一個socket pair.

預備知識

網路位元組序:

大端和小端的概念

大端: 低位地址存放高位資料, 高位地址存放低位資料

小端: 低位地址存放低位資料, 高位地址存放高位資料

如何驗證本機上大端還是小端??-----使用共用體.

編寫程式碼endian.c進行測試, 測試本機上是大端模式還是小端模式

#include<stdio.h>

union oneMem
{
char a;
short b;
int c;
};

int main()
{
oneMem om;
om.c = 0x12345678;
if( om.a == 0x78 )
printf("little endian\n");
else if( om.a == 0x12)
printf("big endian\n");
else
printf("error\n");
return 0;
}

網路傳輸用的是大端法, 如果機器用的是小端法, 則需要進行大小端的轉換.

下面4個函式就是進行大小端轉換的函式:

#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);

函式名的h表示主機host, n表示網路network, s表示short, l表示long

上述的幾個函式, 如果本來不需要轉換函式內部就不會做轉換.

IP地址轉換函式

p->表示點分十進位制的字串形式

to->到

n->表示network網路

int inet_pton(int af, const char *src, void *dst);

函式說明: 將字串形式的點分十進位制IP轉換為大端模式的網路IP(整形4位元組數)

引數說明:

af: AF_INET //表示Ip協議的版本

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

dst: 存放轉換後的變數的地址

例如: inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

手工也可以計算: 如192.168.232.145, 先將4個正數分別轉換為16進位制數,

192--->0xC0 168--->0xA8 232--->0xE8 145--->0x91

最後按照大端位元組序存放: 0x91E8A8C0, 這個就是4位元組的整形值.

把網路地址轉為點分十進位制

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);

函式說明: 網路IP轉換為字串形式的點分十進位制的IP

引數說明:

af: AF_INET

src: 網路的整形的IP地址

dst: 轉換後的IP地址,一般為字串陣列

size: dst的長度

返回值:

成功--返回指向dst的指標

失敗--返回NULL, 並設定errno

例如: IP地址為010aa8c0, 轉換為點分十進位制的格式:

01---->1 0a---->10 a8---->168 c0---->192

由於從網路中的IP地址是高階模式, 所以轉換為點分十進位制後應該為: 192.168.10.1

socket程式設計用到的重要的結 構體:struct sockaddr

struct sockaddr結構說明:

struct sockaddr {

sa_family_t sa_family;

char sa_data[14];

}

struct sockaddr_in結構://**通常用這個**

struct sockaddr_in {

sa_family_t sin_family; /* address family: AF_INET */

//表示ip的協議號

in_port_t sin_port; /* port in network byte order */
//埠號
struct in_addr sin_addr; /* internet address */
//ip地址

};

/* Internet address. */

struct in_addr {

uint32_t s_addr; /* address in network byte order */

}; //網路位元組序IP--大端模式

通過man 7 ip可以檢視相關說明

socket程式設計主要的API函式介紹

建立socket

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

函式描述: 建立socket

引數說明:

domain: 協議版本

AF_INET IPV4

AF_INET6 IPV6

AF_UNIX AF_LOCAL本地套接字使用

type:協議型別

SOCK_STREAM 流式, 預設使用的協議是TCP協議

SOCK_DGRAM 報式, 預設使用的是UDP協議

protocal:

一般填0, 表示使用對應型別的預設協議.

返回值:

成功: 返回一個大於0的檔案描述符

失敗: 返回-1, 並設定errno

當呼叫socket函式以後, 返回一個檔案描述符, 核心會提供與該檔案描述符相對應的讀和寫緩衝區, 同時還有兩個佇列, 分別是請求連線佇列和已連線佇列.

bind繫結socket檔案描述符的IP和埠

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函式描述: 將socket檔案描述符和IP,PORT繫結

引數說明:

        sockfd: 呼叫socket函式返回的檔案描述符

addr: 本地伺服器的IP地址和PORT,
//需要進行初始化
//也可以用bzero函式來處理進行初始化字串賦值為0並且新增\0
struct sockaddr_in serv;

serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);

//INADDR_ANY: 表示使用本機任意有效的可用IP

//這裡表示固定IP,是一個本地迴環地址

inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

addrlen: addr變數的佔用的記憶體大小

返回值:

成功: 返回0

失敗: 返回-1, 並設定errno

將套接字由主動狀態變為被動狀態

int listen(int sockfd, int backlog);

函式描述: 將套接字由主動態變為被動態

引數說明:

sockfd: 呼叫socket函式返回的檔案描述符

backlog: 同時請求連線的最大個數(還未建立連線)
//最多建立128個連線

返回值:

成功: 返回0

失敗: 返回-1, 並設定errno

獲得連線

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);  

//函式說明:獲得一個連線, 若當前沒有連線則會阻塞等待

函式引數:

sockfd: 呼叫socket函式返回的檔案描述符

addr: 傳出引數, 儲存客戶端的地址資訊

addrlen: 傳入傳出引數, addr變數所佔記憶體空間大小

返回值:

成功: 返回一個新的檔案描述符,用於和客戶端通訊

失敗: 返回-1, 並設定errno值.

accept函式是一個阻塞函式, 若沒有新的連線請求, 則一直阻塞.

從已連線佇列中獲取一個新的連線, 並獲得一個新的檔案描述符, 該檔案描述符用於和客戶端通訊. (核心會負責將請求佇列中的連線拿到已連線佇列中)

連線伺服器函式

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

函式說明: 連線伺服器

函式引數:

sockfd: 呼叫socket函式返回的檔案描述符

addr: 服務端的地址資訊

addrlen: addr變數的記憶體大小

返回值:

成功: 返回0

失敗: 返回-1, 並設定errno值

addr: 本地伺服器的IP地址和PORT,
//需要進行初始化
//也可以用bzero函式來處理進行初始化字串賦值為0並且新增\0
struct sockaddr_in serv;

serv.sin_family = AF_INET;
serv.sin_port = htons(8888);
//serv.sin_addr.s_addr = htonl(INADDR_ANY);

//INADDR_ANY: 表示使用本機任意有效的可用IP

//這裡表示固定IP,是一個本地迴環地址

inet_pton(AF_INET, "127.0.0.1", &serv.sin_addr.s_addr);

接下來就可以使用write和read函式進行讀寫操作了.

除了使用read/write函式以外, 還可以使用recv和send函式

讀取資料和傳送資料:

ssize_t read(int fd, void *buf, size_t count);

ssize_t write(int fd, const void *buf, size_t count);

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

對應recv和send這兩個函式flags直接填0就可以了.

注意: 如果寫緩衝區已滿, write也會阻塞, read讀操作的時候, 若讀緩衝區沒有資料會引起阻塞.

開發流程

使用socket的API函式編寫服務端和客戶端程式的步驟圖示:

服務端開發流程

1 建立socket,返回一個檔案描述符--socket()

該檔案描述符用於監聽客戶端連線

2 將lfd和IP PORT進行繫結---bind()

3 將lfd由主動變為被動監聽--listen()

4 接受一個新的連線,得到一個通訊檔案描述符---accept()

該檔案描述符用於和客戶端通訊

5 收發資料

while(1)
{
接受資料--read或者recv
傳送資料--write或者send
}

6 關閉檔案描述符--close(lfd),close(cfd)。

客戶端開發流程

1 建立socket,返回一個檔案描述符cfd

這個cfd用來和客戶端進行通訊

2 連線服務端--connect()

3 收發資料

while(1)
{


}

4 close(cfd) 關閉

細節

呼叫accept不是新建連線,而是呼叫前面已經繫結的

根據服務端和客戶端編寫程式碼的流程, 編寫程式碼並進行測試.

測試過程中可以使用netstat命令檢視監聽狀態和連線狀態

netstat命令:

a表示顯示所有,

n表示顯示的時候以數字的方式來顯示

p表示顯示程序資訊(程序名和程序PID)