服務器源代碼:git
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/signal.h> #include <sys/wait.h> #include <errno.h> #include <time.h> #define PORT 1234 #define BACKLOG 2 #define MAXDATASIZE 1000 void process_cli(int connectfd,struct sockaddr_in client); void sig_handler(int s); int main() { int opt,listenfd,connectfd; pid_t pid; struct sockaddr_in server; struct sockaddr_in client; int sin_size; struct sigaction act; struct sigaction oact; act.sa_handler=sig_handler; sigemptyset(&act.sa_mask); act.sa_flags=0; printf("服務器實現者學號:20155208\n"); if(sigaction(SIGCHLD,&act,&oact)<0) { perror("Sigaction failed!\n"); exit(1); } if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("Creating socket failed.\n"); exit(1); } opt=SO_REUSEADDR; setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1) { perror("Bind error.\n"); exit(1); } if(listen(listenfd,BACKLOG)==-1) { perror("listen() error.\n"); exit(1); } sin_size=sizeof(struct sockaddr_in); while(1) { if((connectfd=accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) { if(errno==EINTR) continue; perror("accept() error.\n"); exit(1); } if((pid=fork())>0) { close(connectfd); continue; } else if(pid==0) { close(listenfd); process_cli(connectfd,client); exit(0); } else { printf("fork error.\n"); exit(1); } } close(listenfd); return 0; } void process_cli(int connectfd,struct sockaddr_in client) { int i,num; char recvbuf[MAXDATASIZE]; char sendbuf[MAXDATASIZE]; char cli_name[MAXDATASIZE]; time_t t; t=time(NULL); printf("客戶端IP:%s \n",inet_ntoa(client.sin_addr)); num=recv(connectfd,cli_name,MAXDATASIZE,0); if(num==0) { close(connectfd); printf("Client disconnected.\n"); return; } send(connectfd,(void *)&t,sizeof(time_t),0); while(num=recv(connectfd,recvbuf,MAXDATASIZE,0)) { recvbuf[num]='\0'; printf("當前時間:%s\n",ctime(&t)); //send(connectfd,(void *)&t,sizeof(time_t),0); } close(connectfd); } void sig_handler(int s) { pid_t pid; int stat; while((pid=waitpid(-1,&stat,WNOHANG))>0) printf("子進程 %d 關閉。\n",pid); return; }
客戶端源代碼:程序員
#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdlib.h> #include <time.h> #define PORT 1234 #define MAXDATASIZE 1000 void process(FILE *fp,int sockfd); char *getMessage(char *sendline,int len,FILE *fp); int main(int argc,char *argv[]) { int fd; struct hostent *he; struct sockaddr_in server; if(argc!=2) { printf("Usage: %s <IP Address>\n",argv[0]); exit(1); } if((he=gethostbyname(argv[1]))==NULL) { printf("gethostbyname error.\n"); exit(1); } if((fd=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket() error.\n"); exit(1); } bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr=*((struct in_addr *)he->h_addr); if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1) { perror("connect() error.\n"); exit(1); } process(stdin,fd); close(fd); return 0; } void process(FILE *fp,int sockfd) { char sendbuf[MAXDATASIZE]; char recvbuf[MAXDATASIZE]; int num; time_t t; t=time(NULL); printf("用戶名:\n"); if(fgets(sendbuf,MAXDATASIZE,fp)==NULL) { printf("lease enter your name,now you have exit.\n"); return; } send(sockfd,sendbuf,strlen(sendbuf),0); while(getMessage(sendbuf,MAXDATASIZE,fp)!=NULL) { send(sockfd,sendbuf,strlen(sendbuf),0); if((num=recv(sockfd,recvbuf,MAXDATASIZE,0))==0) { printf("Server no send you any data.\n"); return; } recvbuf[num]='\0'; printf("服務器消息:%s\n",ctime(&t)); } printf("Exit.\n"); } char *getMessage(char *sendline,int len,FILE *fp) { printf("請輸入請求:\n"); return(fgets(sendline,len,fp)); }
一個應用是由一個服務器進程和一個或者多個客戶端進程組成的web
客戶端和服務端是進程算法
對於一個主機而言,網絡只是又一種I/O設備數據庫
物理上而言,網絡是一個按照地理遠近組成的層次系統。編程
最底層是LAN(局域網) 適配器提供到網絡的物理接口 以太網段(電纜+集線器)瀏覽器
一臺主機能夠發送一段位,稱爲幀 每一個主機適配器都能看到這個幀,可是隻有目的主機實際讀取它 多個以太網段能夠鏈接成較大的局域網,稱爲橋接以太網 網橋比集線器更充分的利用了電纜帶寬 互聯網絡安全
在層次較高的級別中,多個不兼容的局域網能夠經過路由器鏈接起來,組成互聯網絡 每臺路由器對於它所鏈接到的每一個網絡都有一個適配器(端口) WAN(廣域網) 路由器能夠用來由各類局域網和廣域網構建互聯網絡服務器
網絡協議提供兩種基本能力:網絡
命名機制
傳送機制
封裝是關鍵
因特網的客戶端和服務端混合使用套接字接口函數和Unix I/O函數來進行通訊
因特網上的主機經過IP地址和域名來標識
TCP/IP其實是一個協議族
IP機制從某種意義上而言是不可靠的 TCP是一個構建在IP之上的複雜協議,提供了進程間可靠地全雙工鏈接
一個IP地址就是一個32位無符號整數
IP/TCP爲任意整數數據項定義了統一的網絡字節順序(大端字節順序)
對inet-aton的調用傳遞的是指向結構的指針,而對inet_ntoa的調用傳遞的是結構自己
域名集合造成一個層次結構,子樹稱爲子域
因特網應用程序經過調用gethostbyname函數和gethostbyaddr函數,從DNS數據庫中檢索任意的主機條目
gethostbyname函數:返回和域名name相關的主機條目 gethostbyaddr函數:返回和IP地址相關聯的主機條目 一個IP對多個域名,可供多個域名解析,但域名解析到的地址是一個對一個
點對點、全雙工、可靠
客戶端套接字地址中的端口是由內核自動分配的,稱爲臨時端口
服務端套接字地址中的端口一般是某個知名端口(HTTP:80)
套接字地址:(地址:端口)
套接字接口是一組函數,用以建立網絡應用
套接字地址結構
sinfamily成員是AFINET
sin_port成員是一個16位端口
sin_addr成員是32位的IP地址
IP地址和端口號老是以網絡字節順序(大端法)存放的
socket函數
客戶端和服務端使用socket函數來建立一個套接字描述符
connect函數
創建和服務器的鏈接
open_clientfd函數
將socket和connect函數包裝而成。客戶端能夠用它來和服務器創建鏈接 bind函數、listen函數、accept函數均被服務器用於和客戶端創建鏈接
open_listenfd函數
socket、bind和listen函數結合。用於服務器建立一個監聽描述符
1.協議
Web 客戶端和服務器之間的交互用的是一個基於文本的應用級協議,叫作 HTTP (超文本傳輸協議)
HTTP 是一個簡單的協議
一個 Web 客戶端(即瀏覽器) 打開一個到服務器的因特網鏈接,而且請求某些內容。服務器響應所請求的內容,而後關閉鏈接。瀏覽器讀取這些內容,並把它顯示在屏幕上
Web內容能夠用一種叫作 HTML(Hypertext Markup Language,超文本標記語言)的語言來編寫。一個 HTML 程序(頁)包含指令(標記),它們告訴瀏覽器如何顯示這頁中的各類文本和圖形對象
2.內容
對於Web客戶端和服務端而言,內容是與一個MIME類型相關的字節序列
Web服務器以兩種不一樣的方式向客戶端提供內容:
取一個磁盤文件,並將它的內容返回給客戶端。磁盤文件稱爲靜態內容 , 而返回文件給客戶端的過程稱爲服務靜態內容 運行一個可執行文件,並將它的輸出返回給客戶端。運行時可執行文件產生的輸出稱爲態內容 ,而運行程序並返回它的輸出到客戶端的過程稱爲服務動態內容 每條由Web服務器返回的內容都是和他管理的某個文件相關聯的。這些文件每個都有一個惟一的名字,叫作:URL
程序級併發——進程
函數級併發——線程 三種基本的構造併發程序的方法:
進程:每一個邏輯控制流是一個進程,由內核進行調度,進程有獨立的虛擬地址空間
I/O多路複用:邏輯流被模型化爲狀態機,全部流共享同一個地址空間
線程:運行在單一進程上下文中的邏輯流,由內核進行調度,共享同一個虛擬地址空間
構造併發程序最簡單的方法——用進程 經常使用函數以下:
fork exec waitpid 構造併發服務器 在父進程中接受客戶端鏈接請求,而後建立一個新的子進程來爲每一個新客戶端提供服務。
須要注意的事情: 1.父進程須要關閉它的已鏈接描述符的拷貝(子進程也須要關閉)
2.必需要包括一個SIGCHLD處理程序來回收僵死子進程的資源
3.父子進程之間共享文件表,可是不共享用戶地址空間,這個在之前的學習過程當中提到過
關於獨立地址空間
1.優勢:防止虛擬存儲器被錯誤覆蓋
2.缺點:開銷高,共享狀態信息才須要IPC機制
I/O多路複用技術基本思想:使用select函數要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。
select函數處理類型爲fd_set的集合,即描述符集合,並在邏輯上描述爲一個大小爲n的位向量,每一位b[k]對應描述符k,但當且僅當b[k]=1,描述符k才代表是描述符集合的一個元素。
描述符能作的三件事:
1.分配他們
2.將一個此種類型的變量賦值給另外一個變量
3.用FDZERO、FDSET、FDCLR和FDISSET宏指令來修改和檢查它們
何時能夠讀? 當且僅當一個從該描述符讀取一個字節的請求不會阻塞時
注意: 每次調用select函數時都須要更新讀集合
1、基於I/O多路複用的併發事件驅動服務器
事件驅動程序:將邏輯流模型化爲狀態機。
狀態機:
狀態
輸入事件
轉移 對於狀態機的理解,參考EDA課程中學習的狀態轉換圖的畫法和狀態機。
總體的流程是:
select函數檢測到輸入事件 add_client函數建立新狀態機 check_clients函數執行狀態轉移(在課本的例題中是回送輸入行),而且完成時刪除該狀態機。 幾個須要注意的函數:
init_pool:初始化客戶端池 add_client:添加一個新的客戶端到活動客戶端池中 check_clients:回送來自每一個準備好的已鏈接描述符的一個文本行 2、I/O多路複用技術的優劣
1.優勢
相較基於進程的設計,給了程序員更多的對程序程序的控制 運行在單一進程上下文中,因此每一個邏輯流均可以訪問該進程的所有地址空間,共享數據容易實現 可使用GDB調試 高效 2.缺點
編碼複雜 不能充分利用多核處理器 第三節 基於線程的併發編程
這種模式混合了以上兩種方法
線程:就是運行在進程上下文中的邏輯流。
每一個線程都有它本身的線程上下文:
一個惟一的整數線程ID——TID 棧 棧指針 程序計數器 通用目的寄存器 條件碼 1、線程執行模型
1.主線程
在每一個進程開始生命週期時都是單一線程——主線程,與其餘進程的區別僅有:它老是進程中第一個運行的線程。
2.對等線程
某時刻主線程建立,以後兩個線程併發運行。
每一個對等線程都能讀寫相同的共享數據。
3.主線程切換到對等線程的緣由:
主線程執行一個慢速系統調用,如read或sleep 被系統的間隔計時器中斷 切換方式是上下文切換
對等線程執行一段時間後會控制傳遞迴主線程,以此類推
4.線程和進程的區別
線程的上下文切換比進程快得多 組織形式: 進程:嚴格的父子層次 線程:一個進程相關線程組成對等(線程)池,和其餘進程的線程獨立開來。一個線程能夠殺死它的任意對等線程,或者等待他的任意對等線程終止。 2、Posix線程
Posix線程是C程序中處理線程的一個標準接口。基本用法是:
線程的代碼和本地數據被封裝在一個線程例程中 每一個線程例程都以一個通用指針爲輸入,並返回一個通用指針。 這裏須要提到一個萬能函數的概念。
萬能函數:
void func(void parameter) typedef void (uf)(void para)
即,輸入的是指針,指向真正想要傳到函數裏的數據,若是隻有一個就直接讓指針指向這個數據,若是是不少就將它們 放到一個結構體中,讓指針指向這個結構體。後面這個方法就是萬能函數的使用思想。 線程例程也是這樣的。
3、建立線程
1.建立線程:pthread_create函數
#include <pthread.h> typedef void *(func)(void *);
int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
成功返回0,出錯返回非0 建立一個新的線程,帶着一個輸入變量arg,在新線程的上下文運行線程例程f。
attr默認爲NULL
參數tid中包含新建立線程的ID
2.查看線程ID——pthread_self函數
#include <pthread.h>
pthread_t pthread_self(void);
返回調用者的線程ID(TID)
4、終止線程
1.終止線程的幾個方式:
隱式終止:頂層的線程例程返回 顯示終止:調用pthread_exit函數 *若是主線程調用,會先等待全部其餘對等線程終止,再終止主線程和整個進程,返回值爲pthread_return
某個對等線程調用Unix的exit函數,會終止進程與其相關線程 另外一個對等線程經過以當前線程ID做爲參數調用pthread_cancle來終止當前線程 2.pthread_exit函數
#include <pthread.h> void pthread_exit(void *thread_return);
若成功返回0,出錯爲非0 3.pthread_cancle函數
#include <pthread.h> void pthread_cancle(pthread_t tid);
若成功返回0,出錯爲非0
5、回收已終止線程的資源
用pthread_join函數:
#include <pthread.h> int pthread_join(pthread_t tid,void **thrad_return);
這個函數會阻塞,知道線程tid終止,將線程例程返回的(void*)指針賦值爲thread_return指向的位置,而後回收已終止線程佔用的全部存儲器資源
6、分離線程
在任何一個時間點上,線程是可結合的,或是分離的。
1.可結合的線程
可以被其餘線程收回其資源和殺死 被收回錢,它的存儲器資源沒有被釋放 每一個可結合線程要麼被其餘線程顯式的收回,要麼經過調用pthread_detach函數被分離 2.分離的線程
不能被其餘線程回收或殺死 存儲器資源在它終止時由系統自動釋放 3.pthread_detach函數
#include <pthread.h>
void pthread_detach(pthread_t tid);
若成功返回0,出錯爲非0 這個函數能夠分離可結合線程tid。
線程可以經過以pthreadself()爲參數的pthreaddetach調用來分離他們本身。
每一個對等線程都應該在他開始處理請求以前分離他自身,以使得系統能在它終止後回收它的存儲器資源。
7、初始化線程:pthread_once函數
#include <pthread.h> pthread_once_t once_control = PTHREAD_ONCE_INIT;
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
老是返回0
8、基於線程的併發服務器中的注意事項
1.調用pthread_create時,如何將已鏈接描述符傳遞給對等進程?
傳遞指針。
2.競爭問題
見第七節。
3.避免存儲器泄露
必須分離每一個線程,使它終止時它的存儲器資源能被收回。
第四節 多線程程序中的共享變量
一個變量是共享的,當且僅當多個線程引用這個變量的某個實例。
1、線程存儲器模型
須要注意的有:
寄存器從不共享,虛擬存儲器老是共享的。
2、將變量映射到存儲器
3、共享變量
變量v是共享的——當且僅當它的一個實例被一個以上的線程引用。
第五節 用信號量同步線程
通常而言,沒有辦法預測操做系統是否將爲你的線程選擇一個正確的順序。
1、進度圖
進度圖是將n個併發線程的執行模型化爲一條n維笛卡爾空間中的軌跡線,原點對應於沒有任何線程完成一條指令的初始狀態。
當n=2時,狀態比較簡單,是比較熟悉的二維座標圖,橫縱座標各表明一個線程,而轉換被表示爲有向邊
轉換規則:
合法的轉換是向右或者向上,即某一個線程中的一條指令完成 兩條指令不能在同一時刻完成,即不容許出現對角線 程序不能反向運行,即不能出現向下或向左 而一個程序的執行歷史被模型化爲狀態空間中的一條軌跡線。
線程循環代碼的分解:
H:在循環頭部的指令塊 L:加載共享變量cnt到線程i中寄存器%eax的指令。 U:更新(增長)%eax的指令 S:將%eax的更新值存回到共享變量cnt的指令 T:循環尾部的指令塊 幾個概念
臨界區:對於線程i,操做共享變量cnt內容的指令L,U,S構成了一個關於共享變量cnt的臨界區。 不安全區:兩個臨界區的交集造成的狀態 安全軌跡線:繞開不安全區的軌跡線 具體相關在操做系統課程中講的更爲詳細,好比:
臨界區使用原則(互斥條件)
有空讓進:若是臨界區空閒,則只要有進程申請就當即讓其進入; 無空等待:每次只容許一個進程處於臨界區; 多中擇一:當沒有進程在臨界區,而同時有多個進程要求進入臨界區,只能讓其中之一進入臨界區,其餘進程必須等待; 讓權等待:進入臨界區的進程,不能在臨界區內長時間阻塞等待某事件,使其它進程在臨界區外無限期等待; 不能限制進程的併發數量和執行進度。
2、信號量
信號量實現互斥的基本原理
兩個或多個進程經過傳遞信號進行合做,能夠迫使進程在某個位置暫時中止執行(阻塞等待),直到它收到一個能夠「向前推動」的信號(被喚醒); 將實現信號燈做用的變量稱爲信號量,常定義爲記錄型變量s,其一個域爲整型,另外一個域爲隊列,其元素爲等待該信號量的阻塞進程(FIFO)。 信號量定義:
type semaphore=record count: integer; queue: list of process end; var s:semaphore; 定義對信號量的兩個原子操做——P和V
P(wait)
wait(s) s.count :=s.count-1; if s.count<0 then begin 進程阻塞; 進程進入s.queue隊列; end;
V(signal)
signal(s) s.count :=s.count+1; if s.count ≤0 then begin 喚醒隊首進程; 將進程從s.queue阻塞隊列中移出; end; 須要注意的是,每一個信號量在使用前必須初始化。
3、使用信號量來實現互斥
1.基本思想
將每一個共享變量(或者一組相關的共享變量)與一個信號量s(初始爲1)聯繫起來,而後用P和V操做將相應的臨界區包圍起來。
2.幾個概念
二元信號量:用這種方式來保護共享變量的信號量叫作二元信號量,取值老是0或者1 互斥鎖:以提供互斥爲目的的二元信號量 加鎖:對一個互斥鎖執行P操做 解鎖;對一個互斥鎖執行V操做 計數信號量:被用做一組可用資源的計數器的信號量 禁止區:因爲信號量的不變性,沒有實際可能的軌跡可以包含禁止區中的狀態。
3.wait(s)/signal(s)的應用
進程進入臨界區以前,首先執行wait(s)原語,若s.count<0,則進程調用阻塞原語,將本身阻塞,並插入到s.queue隊列排隊; 注意,阻塞進程不會佔用處理機時間,不是「忙等」。直到某個從臨界區退出的進程執行signal(s)原語,喚醒它; 一旦其它某個進程執行了signal(s)原語中的s.count+1操做後,發現s.count ≤0,即阻塞隊列中還有被阻塞進程,則調用喚醒原語,把s.queue中第一個進程修改成就緒狀態,送就緒隊列,準備執行臨界區代碼。 以及
wait操做用於申請資源(或使用權),進程執行wait原語時,可能會阻塞本身; signal操做用於釋放資源(或歸還資源使用權),進程執行signal原語時,有責任喚醒一個阻塞進程。
3、利用信號量來調度共享資源
信號量有兩個做用:
實現互斥
調度共享資源
信號量分爲:互斥信號量和資源信號量。
互斥信號量用於申請或釋放資源的使用權,常初始化爲1;
資源信號量用於申請或歸還資源,能夠初始化爲大於1的正整數,表示系統中某類資源的可用個數。
1.信號量的物理意義
s.count >0表示還可執行wait(s)而不會阻塞的進程數(可用資源數)。每執行一次wait(s)操做,就意味着請求分配一個單位的資源。
當s.count ≤0時,表示已無資源可用,所以請求該資源的進程被阻塞。此時,s.count的絕對值等於該信號量阻塞隊列中的等待進程數。執行一次signal操做,就意味着釋放一個單位的資源。若s.count<0,表示s.queue隊列中還有被阻塞的進程,須要喚醒該隊列中的第一個進程,將它轉移到就緒隊列中。
2.常見問題
這裏的常見問題有生產者-消費者問題,和讀者-寫者問題,都是操做系統課程中詳細講述過的,再也不贅述。
第七節 其餘併發問題
1、線程安全性
一個線程是安全的,當且僅當被多個併發線程反覆的調用時,它會一直產生正確的結果。
四個不相交的線程不安全函數類以及應對措施:
不保護共享變量的函數——用P和V這樣的同步操做保護共享變量 保持跨越多個調用的狀態的函數——重寫,不用任何static數據。 返回指向靜態變量的指針的函數——①重寫;②使用加鎖-拷貝技術。 調用線程不安全函數的函數——參考以前三種
2、可重入性
當它們被多個線程調用時,不會引用任何共享數據。
1.顯式可重入的:
全部函數參數都是傳值傳遞,沒有指針,而且全部的數據引用都是本地的自動棧變量,沒有引用靜態或全劇變量。
2.隱式可重入的:
調用線程當心的傳遞指向非共享數據的指針。
3、在線程化的程序中使用已存在的庫函數
一句話,就是使用線程不安全函數的可重入版本,名字以_r爲後綴結尾。
4、競爭
1.競爭發生的緣由:
一個程序的正確性依賴於一個線程要在另外一個線程到達y點以前到達它的控制流中的x點。也就是說,程序員假定線程會按照某種特殊的軌跡穿過執行狀態空間,忘了一條準則規定:線程化的程序必須對任何可行的軌跡線都正確工做。
2.消除方法:
動態的爲每一個整數ID分配一個獨立的塊,而且傳遞給線程例程一個指向這個塊的指針
5、死鎖:
一組線程被阻塞了,等待一個永遠也不會爲真的條件。
解決死鎖的方法
1.不讓死鎖發生:
靜態策略:設計合適的資源分配算法,不讓死鎖發生---死鎖預防; 動態策略:進程在申請資源時,系統審查是否會產生死鎖,若會產生死鎖則不分配---死鎖避免。 2.讓死鎖發生:
進程申請資源時不進行限制,系統按期或者不按期檢測是否有死鎖發生,當檢測到時解決死鎖----死鎖檢測與解除。
代碼上傳截圖:
代碼行數(新增/積累) | 博客量(新增/積累 | 學習時間(新增/累積) | |
---|---|---|---|
目標 | 5000行 | 30篇 | 400小時 |
第一週 | 5/5 | 1/1 | 8/8 |
第二週 | 120/120 | 1/1 | 12/12 |
第三週 | 100/100 | 1/1 | 15/15 |
第四周 | 80/80 | 1/1 | 9/9 |
第五週 | 50/50 | 1/1 | 6/6 |
第六週 | 350/350 | 1/1 | 12/12 |
第七週 | 120/120 | 2/2 | 15/15 |
第八週 | 380/380 | 1/1 | 16/16 |
《深刻理解計算機系統V3》學習指導