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