Linux下的網絡編程指的是socket套接字編程,入門比較簡單。在學校裏學過一些皮毛,平時就是自學玩,沒有見識過真正的socket編程大程序,比較遺憾。總感受每次看的時候都有收穫,可是每次看完了以後,過段時間不看,從新拾起這些知識的時候又要從頭開始,因此,在這裏作個筆記也算是作個模板,之後能夠直接從某一個階段開始接着玩。。。linux
1. socket套接字介紹編程
socket機制其實就是包括socket, bind, listen, connect, accept等函數的方法,其經過指定的函數實現不一樣的協議(IP4,IP6等)的數據在不一樣層之間的傳輸和獲取等處理。其實我的理解socket就是處於應用層和TCP/IP協議之間的一箇中間層,具體的數據分析,重組,拆分等操做對於應用層的網絡編程者來講都是不可見的,這些都有協議棧內核實現,應用層的網絡編程會經過設置socket機制中建立socket時參數不一樣,而接收或者發送不一樣類型的數據。數組
對於TCP/IP在這裏就不過多的講,可是須要說起的是經典的TCP/IP參考模型是分爲4個層次:應用層,傳輸層,網絡互聯層,主機到網絡層。標準的套接字編程主要是指TCP和UDP的網絡編程,socket網絡編程的模式就是分server和client,經過server端首先創建,client端聯接進行通訊。網絡協議棧內核實現的功能主要就是在數據到達每一層時,給數據加上或者去掉協議包頭,或者進行校驗,數據重組,拆分等操做,最後獲得咱們想要的數據格式。緩存
下面簡單列一下TCP/IP參考模型中主要的協議類型(圖片來自Linux網絡編程)。服務器
圖1 TCP/IP 參考模型的層次結構網絡
標準套接字分爲TCP和UDP協議兩種不一樣type的工做流程,TCP網絡編程相對於UDP來講相對複雜,由於TCP是面向鏈接的服務,其中包括三次握手創建鏈接的過程,而UDP則是無鏈接的服務,下圖介紹了TCP服務使用socket套接字創建鏈接的過程,以及進行數據交互的過程。圖片來自http://blog.csdn.net/xrb66/article/details/6048399數據結構
圖2 TCP 創建socket通訊的流程多線程
TCP和UDP的網絡編程模式有兩種,一種是服務器模式,另外一種是客戶端模式,由於TCP是面向鏈接的服務,因此在socket機制當中,TCP的服務器模式比UDP的服務器模式多了listen,accept函數,TCP客戶端比UDP客戶端多了connect函數。下面是TCP和UDP網絡編程的兩種模式流程圖。下面將結合圖2,3,4介紹一下TCP socket的機制是如何實現的。socket
圖3 TCP 服務器端與客戶端通訊流程 函數
圖4 UDP服務器端和客戶端通訊流程
2. socket套接字基本函數介紹
2.1 建立socket套接字
int socket(int family, int type, int protocol);
功能介紹:
在Linux操做系統中,一切皆文件,這個你們都知道,我的理解建立socket的過程其實就是一個得到文件描述符的過程,固然這個過程會是比較複雜的。能夠從內核中找到建立socket的代碼,而且socket的建立和其餘的listen,bind等操做分離開來。socket函數完成正確的操做是返回值大於0的文件描述符,當返回小於0的值時,操做錯誤。一樣是返回一個文件描述符,可是會由於三個參數組合不一樣,對於數據具體的工做流程不一樣,對於應用層編程來講,這些也是不可見的。
參數說明:
從socket建立的函數能夠看出,socket有三個參數,family表明一個協議族,比較熟知的就是AF_INET,PF_PACKET等;第二個參數是協議類型,常見類型是SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET等;第三個參數是具體的協議,對於標準套接字來講,其值是0,對於原始套接字來講就是具體的協議值。
2.2 地址端口綁定函數bind
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
功能介紹:
bind函數主要應用於服務器模式一端,其主要的功能是將addrlen長度 struct sockaddr類型的myaddr地址與sockfd文件描述符綁定到一塊兒,在sockaddr中主要包含服務器端的協議族類型,網絡地址和端口號等。在客戶端模式中不須要使用bind函數。當bind函數返回0時,爲正確綁定,返回-1,則爲綁定失敗。
參數說明:
bind函數的第一個參數sockfd是在建立socket套接字時返回的文件描述符。
bind函數的第二個參數是struct sockaddr類型的數據結構,因爲struct sockaddr數據結構類型不方便設置,因此一般會經過對truct sockaddr_in進行地質結構設置,而後進行強制類型轉換成struct sockaddr類型的數據,下面是兩種類型數據結構的定義和對應關係圖。
typedef unsigned short sa_family_t; struct in_addr { __be32 s_addr; }; struct sockaddr { sa_family_t sa_family; /* address family, AF_xxx */ char sa_data[14]; /* 14 bytes of protocol address */ }; /* Structure describing an Internet (IP) socket address. */ #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ struct sockaddr_in { sa_family_t sin_family; /* Address family */ __be16 sin_port; /* Port number */ struct in_addr sin_addr; /* Internet address */ /* Pad to size of `struct sockaddr'. */ unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) - sizeof(unsigned short int) - sizeof(struct in_addr)]; };
圖5 struct sockaddr_in和struct sockaddr的映射關係
bind函數的第三個參數是指定struct sockaddr類型數據的長度,由於前面講過bind函數的第二個參數是經過設置一個較容易的數據結構,而後經過強制類型轉換成struct sockaddr,實際上,第二個參數具體的數據結構的長度會根據socket建立時,設置的family協議族的不一樣而不一樣,像AF_UNIX協議族的bind函數第二個參數的數據結構應該是struct sockaddr_un,其大小和struct sockaddr_in不一樣。
2.3 監聽本地端口listen
int listen(int sockfd, int backlog);
功能介紹:
剛開始理解listen函數會有一個誤區,就是認爲其操做是在等在一個新的connect的到來,其實不是這樣的,真正等待connect的是accept操做,listen的操做就是當有較多的client發起connect時,server端不能及時的處理已經創建的鏈接,這時就會將connect鏈接放在等待隊列中緩存起來。這個等待隊列的長度有listen中的backlog參數來設定。listen和accept函數是服務器模式特有的函數,客戶端不須要這個函數。當listen運行成功時,返回0;運行失敗時,返回值位-1.
參數說明:
sockfd是前面socket建立的文件描述符;backlog是指server端能夠緩存鏈接的最大個數,也就是等待隊列的長度。
2.4 接受網絡請求函數accept
int accept(int sockfd, struct sockaddr *client_addr, socklen_t *len);
功能介紹:
接受函數accept其實並非真正的接受,而是客戶端向服務器端監聽端口發起的鏈接。對於TCP來講,accept從阻塞狀態返回的時候,已經完成了三次握手的操做。Accept實際上是取了一個已經處於connected狀態的鏈接,而後把對方的協議族,網絡地址以及端口都存在了client_addr中,返回一個用於操做的新的文件描述符,該文件描述符表示客戶端與服務器端的鏈接,經過對該文件描述符操做,能夠向client端發送和接收數據。同時以前socket建立的sockfd,則繼續監聽有沒有新的鏈接到達本地端口。返回大於0的文件描述符則表示accept成功,不然失敗。
參數說明:
sockfd是socket建立的文件描述符;client_addr是本地服務器端的一個struct sockaddr類型的變量,用於存放新鏈接的協議族,網絡地址以及端口號等;第三個參數len是第二個參數所指內容的長度,對於TCP來講其值能夠用sizeof(struct sockaddr_in)來計算大小,說要說明的是accept的第三個參數要是指針的形式,由於這個值是要傳給協議棧使用的。
2.5 鏈接目標服務器函數connect
int connect(int sock_fd, struct sockaddr *serv_addr,int addrlen);
功能介紹:
鏈接函數connect是屬於client端的操做函數,其目的是向服務器端發送鏈接請求,這也是從客戶端發起TCP三次握手請求的開始,服務器端的協議族,網絡地址以及端口都會填充到connect函數的serv_addr地址當中。當connect返回0時說明已經connect成功,返回值是-1時,表示connect失敗。
參數說明:
connect的第一個參數是socket建立的文件描述符;第二個參數是一個struct sockaddr類型的指針,這個參數中設置的是要鏈接的目標服務器的協議族,網絡地址以及端口號;第三個參數表示第二個參數內容的大小,與accept不一樣,這個值不是一個指針。
在服務器端和客戶端創建鏈接以後是進行數據間的發送和接收,主要使用的接收函數是recv和read,發送函數是send和write。由於對於socket套接字來講,最終實際操做的是文件描述符,因此可使用對文件進行操做的接收和發送函數對socket套接字進行操做。對於UDP編程來講,其服務器端和客戶端之間沒有三次握手創建鏈接,因此服務器端沒有listen和accept函數,客戶端沒有connect函數。因此對於服務器端來講,沒有accept函數,因此使用recvfrom函數來獲取數據的同時得到客戶端的協議族,網絡地址以及端口號;對於客戶端來講,沒有connect函數,因此使用sendto函數發送數據的同時設置服務器端的協議族,網絡地址以及端口;同理若是recvfrom用在客戶端,則是接收服務器端數據和地址,sendto用在服務器端,則是發送到客戶端網絡地址以及端口數據。
2.6 接收數據函數recvfrom
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
功能介紹:
對於該函數主要的功能是,從客戶端或者服務器端接收數據以及發送方的地址信息存儲到本地的struct sockaddr類型參數變量當中,若是函數返回-1,所說明接收數據失敗,若是返回的是大於等於0的值,則說明函數接收到的數據的大小。由於能夠設置文件描述符的狀態爲阻塞模式,因此在沒有接收到數據時,recvfrom會一直處於阻塞狀態,直到有數據接收到。
參數說明:
sockfd是建立socket時的文件描述符;buf用於存儲接收到的數據緩衝區,接收的數據將放到這個指針所指向的內容的空間中;len是接收緩衝區的大小;from是指向struct sockaddr的指針,接收發送方地址信息;fromlen是表示第5個參數所指向內容的長度,可使用sizeof(struct sockaddr)來定義大小,不過由於是要傳給內核協議棧,因此使用了指針類型。
2.7 發送數據函數sendto
sizeof_t sendto(int sockfd, const void *buf, size_t len, int flag, const struct sockaddr *to, socklen_t tolen);
功能介紹:
sendto函數主要根據填充的接收方的地址信息向客戶端或者服務器端發送數據,接收方的地址信息會提早設置在struct sockaddr類型的參數指針中,當返回值-1時,代表發送失敗,當返回值大於等於0時,表示發送成功,而且發送數據的大小會經過返回值傳遞回來。
參數說明:
sockfd是有socket建立的文件描述符;buf是發送數據緩衝區,要發送的數據會放在這個指針指向的內容空間中;len是發送緩衝區的大小;to是一個struct sockaddr類型的指針,其指向地址的內容是接收方地址信息;tolen表示第5個參數指向的數據內容的長度,傳遞的是值,能夠用sizeof(struct sockaddr)計算。
3. linux線程介紹
經過socket機制創建起的鏈接,僅僅實現的是服務器端和客戶端之間的通訊,數據的傳輸。可是要使網絡編程實現性能更優的話,少不了使用線程,線程間通訊以及IO函數,接下來就簡單講一下線程,線程間通訊,以及IO函數中的select函數。
Linux下的線程,線程是進程中的一個運行單元,進程fork子進程的過程是對父進程進程copy的過程,而後牢牢改變子進程自己的一些變量,以後各自的進程運行屬於本身進程空間的內容;而線程的建立則否則,線程建立在進程中有本身固定的建立函數,在同一個進程中建立的全部線程會共用所在進程的全局變量,信號句柄,文件描述符和當前的目錄狀態,可是每一個線程又會有屬於本身的線程棧等私有的屬性。進程得到的使用資源被分給了每一個線程,除公共部分外每一個線程之間的運行又是相對獨立的。
Linux下線程的基本函數:
3.1 線程建立函數pthread_create
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);
功能介紹:
該函數是用於在進程中建立線程,線程在進程中建立有固定的形式。我的理解,線程的建立就是圈起了一段代碼做爲一個線程,這段被圈起來的函數做爲線程函數,線程開始運行就是從線程函數開始運行,線程函數也有固定的格式,由於格式固定,線程的建立把單獨做爲參數的線程函數和線程函數參數整合到一塊兒,造成一個線程。固然在建立的同時,會設置當前線程的屬性,以及用於操做的線程標識符。
參數說明:
thread:第一個參數是一個pthread_t類型的線程標識符,能夠經過操做該標識符,實現對線程的操做;
attr:第二個參數是用來設置線程的屬性,包括線程優先級等屬性;
start_routine:第三個參數是指當線程成功建立後,開始運行的一個單元,該單元須要本身編寫,通常會使用無限循環來實現;
arg:第四個參數是第三個參數線程函數運行時傳入的參數,爲了防止每一個線程函數輸入參數不一樣而難以操做,因此線程建立講二者分開,這樣更靈活,便於操做。
3.2 線程結束函數pthread_join和pthread_exit
3.2.1 線程函數結束pthread_exit
void pthread_exit(void *retval);
功能介紹:
該函數主要的功能是從被圈起來的線程函數中退出,退出過程當中會經過函數的參數指針帶出一個對象,當等待線程結束函數pthread_join的第二個參數不是NULL時,會傳給這個參數作相應的處理。
參數說明:
函數的參數是一個指針,經過該指針能夠傳遞出當前進程結束時的相關信息,這個值會被pthread_join捕捉到。
3.2.2 等待線程結束pthread_join
int pthread_join(pthread_t th,void **return_value);
功能介紹:
函數主要功能是等待線程結束,pthread_exit是主動結束線程,該函數是被動等待線程結束。函數會處於等待狀態,若是函數的第二個參數沒有設置爲NULL,則會捕捉到從exit傳遞迴的信息。
參數說明:
第一個參數是要等待的線程的標識符,有phread_create函數第一個參數指定其值是多少;第二個參數是一個二維指針,用於等待從pthread_exit返回值。固然,若是不適用pthread_exit結束線程函數的話,線程函數結束,也就是調用函數的線程結束。當線程函數運行結束時,該函數用於回收線程的資源。
對於講理論來講,你們每每更喜歡實例,下面是一個線程的小例子。
1 #include <stdio.h> 2 #include <string.h> 3 #include <pthread.h> 4 5 pthread_t pth[2]; 6 7 void *print_message(void *argv) 8 { 9 printf("This is in thread %x!\n", *((pthread_t *)argv)); 10 11 if(&pth[0] == argv) 12 { 13 sleep(1); 14 pthread_exit("1 thread exit!"); 15 } 16 else 17 { 18 sleep(10); 19 pthread_exit("2 thread exit!"); 20 } 21 } 22 23 int main(void) 24 { 25 void *returnValue; 26 27 printf("This is in main function BEFORE pthread create!\n"); 28 pthread_create(&pth[0], NULL, &print_message, &pth[0]); 29 pthread_create(&pth[1], NULL, &print_message, &pth[1]); 30 printf("This is in main function AFTER pthread create %x!\n", pth[0]); 31 pthread_join(pth[0], &returnValue); 32 printf("This is in main function AFTER pthread join 1\n"); 33 pthread_join(pth[1], &returnValue); 34 printf("This is in main function returnValue=%s\n", returnValue); 35 printf("This is in main function AFTER pthread join\n"); 36 return 0; 37 }
下面是運行結果:
這個只是運行結果,其實一些動態的東西,也看不到,由於pthread_join是阻塞等待線程結束的,因此說這個代碼是線程1等待一秒首先結束線程運行,pthread_join會捕捉到線程結束,線程2會在線程1結束運行後約9s而後結束運行,這時候線程2的pthread_join纔會捕捉到線程結束並釋放資源。因此若是是使用多線程而且在同一個地方統一使用pthread_join釋放資源時,最好先釋放首先結束運行的線程,而後在釋放後結束的線程。不然的話若是使用pthread_join先釋放後結束運行的線程,先結束運行的線程資源由於等待前面pthread_join結束而得不到釋放。
由於線程是在進程中建立的,線程公用進程中的資源,因此線程資源的釋放很是重要。主要的線程資源釋放的方法有下面三種:
1) 在線程函數中調用pthread_detach(pthread_self()),主動釋放線程資源;
2) 向上面介紹的pthread_join函數,被動釋放線程資源;
3) 經過設置線程屬性中的__detachstate屬性,在線程函數運行完,或者pthread_exit退出時,自動釋放線程資源,設置線程屬性經過下面方式,
pthread_attr_t att; //線程屬性
pthread_attr_init(&att);//初始化線程屬性
pthread_attr_setdetachstate(&att, PTHREAD_CREATE_DETACHED);//設置線程屬性
pthread_create( pthread_t *pthread, &att, void *(*thread_function)(void *), (void *argv)); //創建線程
3.3 線程的屬性
線程建立函數pthread_create的第二個參數是指線程的屬性,當該參數設置爲NULL時,表示使用了線程的默認屬性。其實咱們能夠經過設置第二個參數來設置線程的屬性。線程屬性的改變有屬於本身的結構體和函數。
線程屬性的結構體
typedef struct __pthread_attr_s { int __detachstate; /*線程的終止狀態*/ int __schedpolicy; /*調度優先級*/ int __sched_param __shedparam; /*參數*/ int __inheritsched; /*繼承*/ int __scope; /*範圍*/ int __guardsize; /*保證尺寸*/ int __stackaddr_set /*運行棧*/ void *__stackaddr; /*線程運行棧地址*/ size_t __stacksize; /*線程運行棧大小*/ }pthread_attr_t;
線程主要的屬性對象包括上面提到的這幾種,線程的屬性不能夠直接設置,須要經過特定的函數來實現,能夠經過函數對上面這幾種線程屬性進行修改。而且線程屬性的修改要在建立線程以前完成。線程屬性的初始化函數爲pthread_attr_init,經過以下所示的函數能夠說明線程的屬性參數均可以作修改,須要再次強調的是這些參數的修改須要在建立線程以前完成,將修改後的屬性參數經過pthead_create的第二個參數傳入到線程中,具體函數以下:(具體如何使用可參考http://blog.csdn.net/hudashi/article/details/7709413)
1). 線程優先級設置屬性,須要先得到線程當前屬性優先級,而後經過設置操做修改屬性優先級
int pthread_attr_getschedparam (pthread_attr_t* attr, struct sched_param* param);
int pthread_attr_setschedparam (pthread_attr_t* attr, struct sched_param* param);
2). 設置線程範圍屬性
int pthread_attr_setscope (pthread_attr_t* attr, int scope);
3). 設置線程終止狀態屬性
int pthread_attr_setdetachstate (pthread_attr_t* attr, int detachstate);
4). 設置線程保護區大小屬性
int pthread_attr_setguardsize(pthread_attr_t* attr,size_t guardsize);
5). 設置線程繼承調度屬性
int pthread_attr_setinheritsched(pthread_attr_t* attr, int inheritsched);
6). 設置線程棧基址以及堆棧的最小尺寸大小
int pthread_attr_setstack(pthread_attr_t* attr, void* stackader,size_t stacksize);
7). 設置線程棧基址屬性
int pthread_attr_setstackaddr(pthread_attr_t* attr, void* stackader);
8). 設置線程棧的大小屬性
int pthread_attr_setstacksize(pthread_attr_t* attr, size_t stacksize);
4. 線程間通訊之信號量
線程的信號量主要就是實現對公共資源的一種控制管理。當公共資源增長時,信號量的值增長;當公共資源減小時,信號量的值減小;只有信號量的值大於0時,才能訪問信號量所表明的公共資源。其實功能和以前的ucos的信號量功能相似。
4.1 線程信號量初始化sem_init
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能介紹:
該函數主要的功能是建立一個信號量,設置該信號量的值,而且設置信號量的使用範圍。信號量建立成功後,能夠對其進行加減操做。
參數說明:
第一個參數sem是一個指向信號量結構的指針,當信號量初始化成功後,能夠的這個信號量指針進行加減操做;第二個參數表示信號量的共享屬性,當其值不爲0時,信號量能夠在進程間共享,若是等於0,則只能在同一個進程中的多個線程間共享;第三個參數用於設置信號量初始化時候的值。
4.2 線程信號量增長函數sem_post
int sem_post(sem_t *sem);
功能介紹:
該函數用於增長信號量的值,每次增長值爲1。當有線程在等待該信號量,則等待信號量返回,不增長信號量的值。
參數說明:
sem參數是初始化時候建立的信號量結構體,用於記錄信號量值得參數。
4.3 線程信號量減小函數sem_wait
int sem_wait(sem_t *sem);
功能介紹:
該函數用於減小信號量的值,每次減小值爲1。當信號量的值爲0,則線程會阻塞一直等待信號量的值大於0爲止,當值爲0時,不在減小。
參數說明:
sem參數是初始化時候建立的信號量結構體。
4.4 線程信號量的銷燬函數sem_destroy
int sem_destroy(sem_t *sem);
功能介紹:
函數用於釋放建立的信號量。
參數說明:
sem參數是初始化時候建立的信號量結構體。
使用線程的信號量進行通訊,能夠有效的對線程資源進行合理的分配,同時可使線程以一個合理的方式進行調度。下面的代碼是一個小小的實例,用很常規的方法來使用信號量,當線程得到得到信號量以後,在處理完相應的操做以後會主動的釋放掉信號量。其實能夠根據信號量的wait和post本身設計使用信號量。不過須要注意的是在wait和post函數分開使用時,可能會由於線程優先級等問題,post和wait被調用的次數不是對等的,這時候這時候可能出現的問題是不可控的,在這種方式設計程序時,須要把大部分的狀況考慮進去,固然可能出現的狀況的種數也是從0到1質變,從1到n量變得過程。
1 #include <stdio.h> 2 #include <string.h> 3 #include <semaphore.h> 4 #include <pthread.h> 5 6 sem_t sem; 7 int semValue; 8 9 void *pthread_wait(void *argv) 10 { 11 while(1) 12 { 13 sem_wait(&sem); 14 sem_getvalue(&sem, &semValue); 15 printf("This is in phtread WAIT function! sem = %d\n", semValue); 16 sem_post(&sem); 17 sleep(1); 18 } 19 pthread_exit("exit wait pthread!\n"); 20 } 21 22 void *pthread_post(void *argv) 23 { 24 while(1) 25 { 26 sem_wait(&sem); 27 sem_getvalue(&sem, &semValue); 28 printf("This is in phtread POST function! sem = %d\n", semValue); 29 sem_post(&sem); 30 sleep(1); 31 } 32 pthread_exit("exit post pthread!"); 33 } 34 35 int main(void) 36 { 37 pthread_t pt[2]; 38 void *ret; 39 40 sem_init(&sem,0,2); 41 pthread_create(&pt[0], NULL, &pthread_wait, NULL); 42 pthread_create(&pt[1], NULL, &pthread_post, NULL); 43 pthread_join(pt[0], &ret); 44 printf("return value %s", ret); 45 pthread_join(pt[1], &ret); 46 printf("return value %s", ret); 47 48 return 0; 49 }
上面代碼的運行結果以下,由於是線程採用了無限循環的方式,因此pthread_exit和pthread_join不會執行到。
若是將上面代碼中的第26行註釋掉,運行結果以下,能夠看到信號量的值一直在增長,就如上面提到的由於sem_wait函數和sem_post函數不能同時使用時,會出現一些不可控的運行結果。也能夠利用這種狀況,設計程序。
5. 線程間通訊之互斥鎖
線程互斥鎖主要的功能是在一段時間內,只容許一個線程對一段代碼或者資源進行訪問的機制。當有一個線程得到互斥鎖後,其餘線程若是想得到互斥鎖就會被阻塞,直到佔有互斥鎖的線程釋放互斥鎖爲止。線程的互斥主要包含的函數有以下幾個:
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_mutex_destory(pthread_mutex_t *mutex);
功能介紹:
pthread_mutex_init函數主要的功能是初始化一個互斥鎖,而且設置該互斥鎖的屬性;pthread_mutex_lock主要的功能是給互斥鎖上鎖,若是一個線程給互斥鎖上鎖以後,其餘線程想得到該鎖的使用權,必需要阻塞等待,直到佔有互斥鎖的線程釋放該鎖;函數是pthread_mutex_lock函數的非阻塞版本。若是mutex參數所指定的互斥鎖已經被鎖定的話,調用pthread_mutex_trylock函數不會阻塞當前線程,而是當即返回一個值來描述互斥鎖的情況;pthread_mutex_unlock給互斥鎖解鎖,線程調用該函數以後會釋放掉已經上鎖的互斥鎖;pthread_mutex_destory是init建立的互斥鎖釋放掉。
參數說明:
pthread_mutex_t是互斥鎖機制內核私有的數據結構,用於實現互斥鎖機制必不可少的元素。上述函數已經很好的詮釋瞭如何使用該參數。
pthread_mutexattr_t是用來描述線程互斥鎖屬性的結構體,初始化時,若是屬性參數設置爲NULL的話,則表示使用默認設置。
我的感受互斥鎖就是信號量的值爲1時候的一種特殊狀況,只是給互斥鎖加了一些限制,而且沒有設置值而已。互斥鎖中的lock至關於信號量中的wait,而unlock至關於post操做。獲取互斥鎖而且lock上鎖以後,只能經過unlock解鎖才能夠從新獲取互斥鎖,互斥鎖主要的功能就是對線程資源進行保護,同一個時間只有一個線程能夠得到互斥鎖,對資源進行使用,只有互斥鎖被釋放,其餘的線程才能夠從新得到互斥鎖。不想信號量同樣,能夠經過設置信號量的值來實現多個線程能夠同時操做使用線程資源。下面是一個互斥鎖的實例。
1 #include <stdio.h> 2 #include <string.h> 3 #include <pthread.h> 4 5 pthread_mutex_t mutex; 6 int mutexCounter; 7 8 void *pthread_mutex_LOCK(void *argv) 9 { 10 while(1) 11 { 12 pthread_mutex_lock(&mutex); 13 mutexCounter++; 14 printf("This is in phtread mutex LOCK function! Counter = %d\n", mutexCounter); 15 pthread_mutex_unlock(&mutex); 16 sleep(1); 17 } 18 pthread_exit("exit wait pthread!\n"); 19 } 20 21 void *pthread_mutex_UNLOCK(void *argv) 22 { 23 while(1) 24 { 25 pthread_mutex_lock(&mutex); 26 mutexCounter--; 27 printf("This is in phtread mutex UNLOCK function! Counter = %d\n", mutexCounter); 28 pthread_mutex_unlock(&mutex); 29 sleep(1); 30 } 31 pthread_exit("exit post pthread!"); 32 } 33 34 int main(void) 35 { 36 pthread_t pt[2]; 37 void *ret; 38 39 pthread_mutex_init(&mutex, NULL); 40 pthread_create(&pt[0], NULL, &pthread_mutex_LOCK, NULL); 41 pthread_create(&pt[1], NULL, &pthread_mutex_UNLOCK, NULL); 42 pthread_join(pt[0], &ret); 43 printf("return value %s", ret); 44 pthread_join(pt[1], &ret); 45 printf("return value %s", ret); 46 47 return 0; 48 }
上面代碼的運行結果以下所示,按邏輯來講應該是一個LOCK函數,一個UNLOCK函數交替運行,但實際的運行結果倒是以下圖所示,主要的緣由是在運行完兩個函數以後,都處於sleep等待狀態,由於處理器運行速度太快,兩個函數完成等待的時間相同,這時候線程的調度就不是按照原來的邏輯了。因此在線程函數處理內容較少時,要注意時序對對編程邏輯的影響。
6. IO複用之select函數
select函數用於IO複用,它用於監視多個文件描述符集合,看規定時間內有沒有事件產生。
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
功能介紹:
該函數主要的功能是對須要操做的文件描述符集合進行查詢,目標文件描述符中有能夠讀,寫或者異常錯誤操做的狀況時,會返回一個大於0的整數值,表示能夠對該文件描述符進行操做。select函數返回0,表示超時;返回-1,表示發生錯誤;返回大於0的整數值,表示有符合要求的文件描述事件產生。當不須要監視某種文件描述符時,設置參數爲NULL。
參數說明:
nfds:是一個整型變量,其值是加入到後面三個文件描述符集合中的最大文件描述符的值加1。
readfds:可讀文件描述符集合,經過FD_SET向該文件描述符集合中加入須要監視的目標文件描述符,當有符合要求的文件描述符時,select會返回一個大於0的值,同時會把原來集合中的不可讀的文件描述符清掉,若是想在次監視可讀文件描述,須要從新FD_SET。
writefds:可寫文件描述符集合,一樣經過FD_SET函數向結合中加入須要被監視的目標文件描述符,select返回時,一樣會把不可寫文件描述符清掉,若是須要從新監視文件描述符,須要從新FD_SET設置。
exceptfds:該描述符集合是用於監視文件描述符集合中的任何文件是否發生錯誤。
timeout:用於設置超時的最長等待時間,若是在該規定時間內沒有返回一個大於0的值,則返回0,表示超時。若是超時間設置爲NULL,表示阻塞等待,直到符合條件的文件描述符在集合中出現,當timeout的值爲0時,select會當即返回。
timeout的數據結構以下:
struct timeval { time_t tv_sec; /*秒*/ long tv_usec; /*微秒*/ };
有4個宏能夠操做文件描述符集合:
FD_ZERO: 用於清空文件描述符集合,FD_ZERO(&fds)。
FD_SET:向某個文件描述符結合中加入文件描述符, FD_SET(fd, &fds)。
FD_CLR:從某個文件描述符結合中取出某個文件描述符, FD_CLR(fd, &fds)。
FD_ISSET:測試某個文件描述符是否在某個文件描述符集合中, FD_ISSET(fd, &fds)。
下面是本實用socket編程,而且利用select IO實現的一個server和client實時通訊的例子,爲了顯示更直觀,加了一些打印以及接收數據上的操做,程序還有bug,但願閱讀的人不要介意,或者自行修改。代碼以下,能夠做爲學習socket和select的一個實例。
server.c文件源碼以下:
1 #include <stdio.h> 2 #include <string.h> 3 #include <sys/select.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <time.h> 7 8 #define SPORT 8888 9 #define BACKLOG 5 10 #define SIZE 100 11 12 int main(void) 13 { 14 int sockfd, clientfd; 15 struct sockaddr_in sockServer, sockClient; 16 struct timeval tv; 17 fd_set readfds, writefds; 18 int readlen, writelen; 19 char buffer[SIZE]; 20 21 sockfd = socket(AF_INET, SOCK_STREAM, 0); 22 if(sockfd < 0) 23 { 24 perror("create socket failed!\n"); 25 return -1; 26 } 27 28 bzero(&sockServer, 0); 29 sockServer.sin_family = AF_INET; 30 sockServer.sin_port = htons(SPORT); 31 sockServer.sin_addr.s_addr = htonl(INADDR_ANY); 32 33 if(bind(sockfd, (struct sockaddr *)&sockServer, sizeof(struct sockaddr_in)) < 0) 34 { 35 perror("bind socket failed!\n"); 36 return -1; 37 } 38 39 if(listen(sockfd, BACKLOG) < 0) 40 { 41 perror("listen failed!\n"); 42 } 43 44 printf("Server is listening ......\n"); 45 46 while(1) 47 { 48 int len = sizeof(struct sockaddr_in); 49 int ret; 50 time_t timet; 51 52 clientfd = accept(sockfd, (struct sockaddr *)&sockClient, &len); 53 if(clientfd < 0) 54 { 55 perror("accept failed!\n"); 56 return -1; 57 } 58 59 for(;;) 60 { 61 FD_ZERO(&readfds); 62 FD_SET(1, &readfds); 63 FD_SET(clientfd, &readfds); 64 tv.tv_usec = 0; 65 tv.tv_sec = 60; 66 67 ret = select(clientfd+1, &readfds, NULL, NULL, &tv); 68 switch(ret) 69 { 70 case 0: 71 printf("select timeout!\n"); 72 break; 73 case -1: 74 perror("select return failed!\n"); 75 goto closesocket; 76 default: 77 if(FD_ISSET(clientfd, &readfds) > 0) 78 { 79 memset(buffer, 0, SIZE); 80 readlen = read(clientfd, buffer, SIZE); 81 if(readlen < 0) 82 { 83 perror("read data failed!\n"); 84 goto closesocket; 85 } 86 time(&timet); 87 printf("Opposite: %d %s", clientfd, ctime(&timet)); 88 strcat(buffer, "\n"); 89 writelen = write(0, buffer, readlen+1); 90 if(writelen < 0) 91 { 92 perror("write data failed!\n"); 93 goto closesocket; 94 } 95 } 96 if(FD_ISSET(1, &readfds) > 0) 97 { 98 time(&timet); 99 printf("Owner: %d %s\n", sockfd, ctime(&timet)); 100 memset(buffer, 0, SIZE); 101 readlen = read(1, buffer, SIZE); 102 if(readlen < 0) 103 { 104 perror("read data failed!\n"); 105 goto closesocket; 106 } 107 writelen = write(clientfd, buffer, readlen); 108 if(writelen < 0) 109 { 110 perror("write data failed!\n"); 111 goto closesocket; 112 } 113 } 114 } 115 } 116 closesocket: 117 close(clientfd); 118 } 119 close(sockfd); 120 121 return 0; 122 }
client.c文件源碼以下:
1 #include <stdio.h> 2 #include <string.h> 3 #include <sys/select.h> 4 #include <sys/socket.h> 5 #include <netinet/in.h> 6 #include <time.h> 7 8 #define SPORT 8888 9 #define SIZE 100 10 11 int main(void) 12 { 13 int sockfd, clientfd; 14 struct sockaddr_in sockServer; 15 struct timeval tv; 16 fd_set readfds; 17 int readlen, writelen; 18 char buffer[SIZE]; 19 time_t timet; 20 21 sockfd = socket(AF_INET, SOCK_STREAM, 0); 22 if(sockfd < 0) 23 { 24 perror("create socket failed!\n"); 25 return -1; 26 } 27 28 bzero(&sockServer, 0); 29 sockServer.sin_family = AF_INET; 30 sockServer.sin_port = htons(SPORT); 31 sockServer.sin_addr.s_addr = htonl(INADDR_ANY); 32 33 if(connect(sockfd, (struct sockaddr *)&sockServer, sizeof(struct sockaddr_in)) < 0) 34 { 35 perror("connect failed!\n"); 36 close(sockfd); 37 } 38 39 while(1) 40 { 41 int ret; 42 43 FD_ZERO(&readfds); 44 FD_SET(1, &readfds); 45 FD_SET(sockfd, &readfds); 46 tv.tv_usec = 0; 47 tv.tv_sec = 60; 48 49 ret = select(sockfd+1, &readfds, NULL, NULL, &tv); 50 switch(ret) 51 { 52 case 0: 53 printf("select timeout!\n"); 54 break; 55 case -1: 56 perror("select return failed!\n"); 57 goto closesocket; 58 default: 59 if(FD_ISSET(sockfd, &readfds) > 0) 60 { 61 memset(buffer, 0, SIZE); 62 readlen = read(sockfd, buffer, SIZE); 63 if(readlen < 0) 64 { 65 perror("read data failed!\n"); 66 goto closesocket; 67 } 68 time(&timet); 69 printf("Opposite: %s %s", "Server", ctime(&timet)); 70 strcat(buffer, "\n"); 71 writelen = write(0, buffer, readlen + 1); 72 if(writelen < 0) 73 { 74 perror("write data failed!\n"); 75 goto closesocket; 76 } 77 } 78 if(FD_ISSET(1, &readfds) > 0) 79 { 80 time(&timet); 81 printf("Owner: %d %s\n", sockfd, ctime(&timet)); 82 memset(buffer, 0, SIZE); 83 readlen = read(1, buffer, SIZE); 84 if(readlen < 0) 85 { 86 perror("read data failed!\n"); 87 goto closesocket; 88 } 89 writelen = write(sockfd, buffer, readlen); 90 if(writelen < 0) 91 { 92 perror("write data failed!\n"); 93 goto closesocket; 94 } 95 } 96 } 97 98 closesocket: 99 close(clientfd); 100 } 101 close(sockfd); 102 103 return 0; 104 }
運行結果以下所示: