【Linux C/C++】 第08講 多執行緒TCP傳輸檔案/select模型
一、多執行緒
pthread.h
libpthread.so -lpthread
1.建立多執行緒
1.1 程式碼
回撥函式
1.2 執行緒ID
pthread_t
1.3 執行執行緒
pthread_create
int pthread_create(pthread_t* restrict th,
const pthread_attr_t * attr,//執行緒屬性 ,為NULL/0,使用程序的預設屬性
void* (*run) (void*), //執行緒回撥函式
void* data //傳遞給執行緒回撥函式的資料
);
restrict -> 只修飾引數指標,第一次載入之後一直使用暫存器的值
結論:
<1> 程序結束,所有子執行緒結束
int pthread_join(pthread_t thread,
void** re//子執行緒結束的返回值);
等待子執行緒結束
<2> 建立子執行緒後,主執行緒繼續完成系統分配的時間片
<3> 子執行緒結束就是執行緒函式返回
<4> 子執行緒和主執行緒有同等優先順序
2.執行緒的基本控制
執行緒的狀態:
ready -> runny -> deady
|
sleep/pause
結束一個執行緒
內部結束:
pthread_exit(void* value_ptr)
return 只能在執行緒函式中使用
外部結束:
pthread_cancel(pthread_t);
執行緒開始
3.多執行緒的問題
資料髒
4.多執行緒問題的解決
互斥鎖/互斥量 mutex
定義互斥量 pthread_mutex_t
初始化互斥量 1 pthread_mutex_init
互斥量操作
置0 pthread_mutex_lock
判定互斥量 如果是0 -> 阻塞
如果是1 -> 置0,返回
置1 pthread_mutex_unlock
置1,返回
釋放互斥量 pthread_mutex_destroy
結論:
互斥量只能保證鎖定的程式碼一個執行緒執行,
但不能保證必須執行完
在lock與unlock之間,呼叫pthread_exit
或者線上程外部呼叫pthread_cancel 其他執行緒被永久死鎖
pthread_cleanup_push {
pthread_cleanup_pop }
這對函式作用類似於atexit
注意:
這不是函式,而是巨集
必須成對使用
1.基礎(ip)
1.1 網路工具
ping
ping ip地址
ping -b ip廣播地址
ifconfig -a
route
lsof
netstat
1.2 網路的基本概念
網路程式設計採用socket模型,網路通訊的本質也是程序之間的IPC(程序間通訊),是不同主機之間。
識別主機:4位元組整數 -> IP地址
識別程序:2位元組整數 -> 應用埠
ip地址的表示:
字串表示“192.168.0.13”
整數表示 in_addr_t
字結構表示 struct in_addr
1.3 IP地址的轉換
inet_addr -> 把字串轉換為整數 (網路位元組序)
inet_aton -> 把字串轉換為struct in_addr (網路位元組序)
inet_network -> 把字串轉換為整數(本地位元組序)
inet_ntoa -> 把結構體轉換為字串
1.4 計算機系統中的網路配置
/etc/hosts檔案 -> 配置IP,域名,主機名
gethostbyname
gethostbyaddr
/etc/protocols檔案 -> 配置系統支援的協議
/etc/services檔案 -> 配置服務
2.TCP/UDP程式設計
對等模型 :AF_INET SOCK_DGRAM 預設UDP
socket
繫結IP地址bind
read/recv
recvfrom 可以獲取傳送者的地址
關閉close
C/S模型: AF_INET SOCK_STREAM 預設TCP
3.TCP的伺服器程式設計模型
TCP的服務端維護多個客戶的網路檔案描述符
1. 多程序
2. IO的非同步模型(select模型 poll模型)
3. 多執行緒
4.多程序池
5.多執行緒池
4.綜合應用 -- 多程序應用
1.怎樣使用多程序
2.多程序的缺陷,以及怎麼解決
三、多執行緒TCP傳輸檔案例項
第04講 多程序TCP傳輸檔案 的客戶端部分不夠完善,
沒有處理非阻塞模式下的連線問題
1.補充一下select模型的概念
select在socket程式設計中還是比較重要的
可是對於初學socket的人來說都不太愛用select寫程式
他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程式
所謂阻塞方式block,顧名思義,就是程序或執行緒執行到這些函式時必須等待
某個事件發生,如果事件沒有發生,程序或執行緒就被阻塞,函式不能立即返回
可是使用select就可以完成非阻塞,所謂非阻塞方式non-block,
就是程序或執行緒執行此函式時不必非要等待事件發生,一旦執行立即返回
以返回值的不同來反映函式的執行情況,如果事件發生則與阻塞方式相同,
若事件沒有發生則返回一個程式碼來告知事件未發生,而程序或執行緒繼續執行
所以效率較高,它能夠監視需要監視的檔案描述符的變化情況-讀寫或異常
select函式格式:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timsval *timeout);
先說明兩個結構體:
1> struct fd_set 可以理解為一個集合,這個集合中存放的是檔案描述符
fd_set集合可以通過一些巨集由人為來操作,比如:
FD_ZERO(fd_set*); 清空集合
FD_SET(int,fd_set*); 將一個檔案描述符加入集合中
FD_CLR(int,fd_set*); 將一個檔案描述符從集合中刪除
FD_ISSET(int,fd_set*)檢查集合中指定的檔案描述符是否可以讀寫
2> struct timeval是一個常用的結構體,用來代表時間值,有兩個成員
一個是秒數,另一個是毫秒。
select的引數具體解釋:
int maxfdp: 是一個整數值,是指集合中所有檔案描述符的範圍,
即所有檔案描述符的最大值加1
fd_set *readfds: 需要監視這些檔案描述符的讀變化,
根據timeout來判斷是否超時,有可讀的返回大於0
沒有可讀的返回0,錯誤異常返回負數
fd_set *writefds: 需要監視這些檔案描述符的寫變化
fd_set *errorfds: 需要監視這些檔案描述符的錯誤異常
struct timeval* timeout: 是select的超時時間,可以使select處於三種狀態
1> 若將NULL以形參傳入,select置於阻塞狀態
2> 若將時間設為0秒0毫秒,就變成一個純粹的非阻塞函式,
不管檔案描述符是否有變化,都立刻返回
3> timeout的值大於0,select在timeout時間內阻塞,超時之內有事件返回
超時之後立刻返回
2.提供一個完善的Linux/Unix/iOS客戶端
int sock = socket(AF_INET,SOCK_STREAM,0);
if(-1 == sock)
{
return false;
}
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr("192.168.0.103");
serv_addr.sin_port = htons(8888);
int r = connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
if (-1 == r)
{
connected = false;
printf("network connect fail\n");
return false;
}
//設定為非阻塞模式
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
struct fd_set fds,fdsErr;
struct timeval timeout={0,0}; //select等待3秒,3秒輪詢,要非阻塞就置0
int maxfdp = 1;
/* 假定已經建立UDP連線,具體過程不寫,簡單,當然TCP也同理,主機ip和port都已經給定,要寫的檔案已經開啟
sock=socket(...);
bind(...);
fp=fopen(...); */
while(1)
{
FD_ZERO(&fds); //每次迴圈都要清空集合,否則不能檢測描述符變化
FD_ZERO(&fdsErr);
FD_SET(sock,&fds); //新增描述符
FD_SET(sock,&fdsErr);
// FD_SET(fp,&fds); //同上
// maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1
maxfdp = sock+1;
switch(select(maxfdp,&fds,NULL,&fdsErr,&timeout)) //select使用
{
case -1: exit(-1);break; //select錯誤,退出程式
case 0:break; //再次輪詢
default:
if(FD_ISSET(sock,&fds)) //測試sock是否可讀,即是否網路上有資料
{
//read fd
char buffer[1024];
ssize_t len = read(sock,buffer,1024);
if(len > 0)
{
//
}
}
else if(FD_ISSET(sock,&fdsErr))
{
//close fd
close(sock);
connected = false;
}
// end if break;
}// end switch
}//end while
服務端的檔案傳輸部分可以直接用,把多程序改成多執行緒就行
socket的讀寫和客戶端一樣,可以採用select/epoll 實現
需要把收到read/recv的資料包存入一個全域性的佇列中
然後用多執行緒處理這些資料包
3. 多執行緒服務端
static void
create_thread(pthread_t *thread, void *(*start_routine) (void *), void *arg) {
if (pthread_create(thread,NULL, start_routine, arg)) {
fprintf(stderr, "Create thread failed");
exit(1);
}
}
static void *
thread_worker(void *p) {
struct message_queue * q = global_mq;
while (!quit) {
//取出
struct message* q = q->pop();
if (q == NULL) {
//file send
...
}
}
return NULL;
}
int main(int argc, char * argv[]) {
create_thread(&pid, thread_worker, 0);
}