一些關於網路知識的筆記,便於以後翻閱
棧存放:區域性變數,先入後出,系統自己分配和釋放。
函式引數
堆存放:陣列
少於200位元組的陣列
pthread_join等待執行緒函式結束
pthread_create建立執行緒函式,執行緒間同步操作。
select的作用:網路中當存在阻塞時,為了不影響別的程式執行,選擇了select
read / recv 阻塞函式,
accept返回值時一個新的socketID;
檔案描述符為什麼要加1?
因為檔案描述符是從0 開始的。 0 1 2。。。。。最大檔案描述符是2, 但是實際上
監聽的檔案描述符是3個
程序的記憶體結構:
在建立程序時,會給它分配記憶體。4G
一般情況下分為四個段,程式碼段,資料段,堆段,棧段
程式碼段時只讀的;資料段一般分為bss段(未初始化的全域性變數)
資料段(已初始化的全域性變數、靜態變數)和常量區
堆段(malloc / new)
棧段(區域性變數,引數,儲存現場)
1.UDP
開啟socket
繫結自己的ip,port
通訊recvfrom/set dest addr, sendto-->addr
關閉socket
2. UDP download
server:
開啟socket
繫結自己的ip,port
open file
傳送檔案大小
while(1)
{
ret = read();
if (0 >= ret)
break;
sendto
}
close file
close socket
client:
開啟socket
繫結自己的ip,port
create and open file
接收檔案大小
while(1)
{
ret = recvfrom();
if (0 >= ret)
break;
write
fileSize -= ret;
if (0 >= fileSize)
break;
}
close file
close socket
記憶體使用:小棧大堆
棧:先入後出;系統自己分配和釋放;用的時候分配,用完自動釋放。
棧裡存放-》區域性變數,引數... ...
例:陣列-》棧 -》少於200個位元組
堆:
ogm->視訊->7M空間
一、IO模型
1.fcntl修改阻塞<->非阻塞
2.多路複用
tcp server
版本1
開啟socket; 繫結自己的ip,port; 監聽
三次握手
通訊
三次握手
通訊
三次握手
通訊
三次握手
通訊
... ...
關閉socket
版本2
開啟socket; 繫結自己的ip,port; 監聽
while(1)
{
三次握手 accept
通訊 recv, (scanf)send
}
關閉socket
版本3
開啟socket; 繫結自己的ip,port; 監聽
while(1)
{
三次握手 accept
通訊 recv, recv, recv ... (scanf)send
}
關閉socket
版本4
開啟socket; 繫結自己的ip,port; 監聽
while(1)
{
三次握手 accept(socketID)
while(1){
通訊 recv(newID) //(scanf)send
}
}
關閉socket
總結:
當前的程式中有多個阻塞描述符,每一個描述符可能會對其它描述符的正常通訊造成影響。
可以使用多路複用來解決上述問題,例如select
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
引數
n: 監聽的最大檔案描述符+1.
read_fds : 讀檔案描述符集。-- 通俗的講是使用讀相關的函式來操作描述符的。
例: accept, read, recv, scanf ... ...
write_fds : 所有要的寫檔案檔案描述符的集合
except_fds : 其他要向我們通知的檔案描述符
timeout : 超時設定.
NULL:一直阻塞,直到有檔案描述符就緒或出錯
時間值為0:僅僅檢測檔案描述符集的狀態,然後立即返回
時間值不為0:在指定時間內,如果沒有事件發生,則超時返回。
定義變數fd_set read_fds;
FD_SET 將fd加入到fd_set
FD_CLR 將fd從fd_set裡面清除
FD_ZERO 從fd_set中清除所有的檔案描述符
FD_ISSET 判斷fd是否在fd_set集合中
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
//清空
FD_ZERO(&read_fds);
//把socketID加入到讀描述符集裡
FD_SET(socketID, &read_fds);
//設定描述符最大值加1
int maxFds = socketID + 1;
//呼叫select
select(maxFds, &read_fds, NULL, NULL, NULL);
多路複用版本1
開啟socket; 繫結自己的ip,port; 監聽
fd_set read_fds;
//清空
FD_ZERO(&read_fds);
//把socketID加入到讀描述符集裡
FD_SET(socketID, &read_fds);
//設定描述符最大值加1
int maxFds = socketID + 1;
while(1)
{
select(maxFds, &read_fds, NULL, NULL, NULL);
三次握手 accept(socketID)
//while(1){
通訊 recv(newID) //(scanf)send
//}
}
關閉socket
總結版本1,問題是:當一個客戶端連線伺服器後,accept會返回newID.
這時,直接呼叫recv,這時,如果當前客戶端沒有發過來資料,那麼recv阻塞。
此時,如果第二個客戶端想要連線伺服器,必須要等待。
多路複用版本2
開啟socket; 繫結自己的ip,port; 監聽
fd_set read_fds;
//清空
FD_ZERO(&read_fds);
//把socketID加入到讀描述符集裡
FD_SET(socketID, &read_fds);
//設定描述符最大值加1
int maxFds = socketID;
while(1)
{//1,3,4,5
fd_set tmp = read_fds;
select(maxFds +1, &tmp, NULL, NULL, NULL);
//select正確返回時,判斷每一個監聽的描述符是否可以進行IO操作
for (i = 0; i < maxFds + 1; i++)
{
if (FD_ISSET -> socketID && socketID == i)//tmp
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> i)//tmp
通訊 recv(i) //(scanf)send
}
}
關閉socket
typedef struct
{
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
__fd_mask型別 : long int
__fds_bits陣列名
__FD_SETSIZE / __NFDBITS陣列的大小: 1024 / (8 * sizeof(long int))
-->
typedef struct
{
long arr[128 / (sizeof(long))];
} fd_set;
fd_set readFds; --> 大小為1024bit
select(maxFds +1, &read_fds, NULL, NULL, NULL);
三次握手 accept(socketID)--> newID=13
FD_SET(newID, &read_fds);
if (maxFds < newID)13
{
maxFds = newID;
}
//2
select(maxFds +1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET -> socketID)
{
三次握手 accept(socketID)--> newID=15
FD_SET(newID, &read_fds); //15
if (maxFds < newID) 15
{
maxFds = newID;
}
}
if (FD_ISSET -> 13)
通訊 recv(13) //(scanf)send
//3-> socketID, 13, 15
select(maxFds +1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET -> socketID)
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> 13)
通訊 recv(13) //(scanf)send
if (FD_ISSET -> 15)
通訊 recv(15) //(scanf)send
//4 socketID, 13, 15, 16
select(maxFds +1, &read_fds, NULL, NULL, NULL);
//select正確返回時,判斷每一個監聽的描述符是否可以進行IO操作
if (FD_ISSET -> socketID)
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> 13)
通訊 recv(13) //(scanf)send
if (FD_ISSET -> 15)
通訊 recv(15) //(scanf)send
if (FD_ISSET -> 16)
通訊 recv(16)
思考:上面的在個if是重複性的動作,想要簡化。
方法1:
for (i = 0; i < array.count; i++)
{
if (FD_ISSET -> i)
通訊 recv(i) //(scanf)send
}
總結:陣列的大小無法確定,不好。
方法2:
for (i = 0; i < maxFds + 1; i++)
{
if (FD_ISSET -> i)
通訊 recv(i) //(scanf)send
}
---------------------------------------------
現在綜合上面把//4修改一下
select(maxFds +1, &read_fds, NULL, NULL, NULL);
//select正確返回時,判斷每一個監聽的描述符是否可以進行IO操作
for (i = 0; i < maxFds + 1; i++)
{
if (FD_ISSET -> socketID && socketID == i)
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> i)
通訊 recv(i) //(scanf)send
}