Liunx C 程式設計之多執行緒與Socket

東小東發表於2019-08-06

多執行緒

pthread.h是linux特有的標頭檔案,POSIX執行緒(POSIX threads),簡稱Pthreads,是執行緒的POSIX標準。該標準定義了建立和操縱執行緒的一整套API。在類Unix作業系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作為作業系統的執行緒。Windows作業系統也有其移植版pthreads-win32。

建立執行緒
1.pthread_create 建立一個新執行緒並使之執行起來。該函式可以在程式的任何地方呼叫包括執行緒內,執行緒是沒有依賴關係的。
2.一個程式可以建立的執行緒最大數量取決於系統實現
3. pthread_create引數:
        thread:返回一個不透明的,唯一的新執行緒識別符號。
        attr:不透明的執行緒屬性物件。可以指定一個執行緒屬性物件,或者NULL為預設值。
        start_routine:執行緒將會執行一次的C函式。
        arg: 傳遞給start_routine單個引數,傳遞時必須轉換成指向void的指標型別。沒有引數傳遞時,可設定為NULL。

pthread_create (threadid,attr,start_routine,arg)

結束執行緒
1.結束執行緒的方法有一下幾種:
       執行緒從主執行緒(main函式的初始執行緒)返回。
       執行緒呼叫了pthread_exit函式。
       其它執行緒使用 pthread_cancel函式結束執行緒。
       呼叫exec或者exit函式,整個程式結束。

2.如果main()在其他執行緒建立前用pthread_exit()退出了,其他執行緒將會繼續執行。否則,他們會隨著main的結束而終止。

pthread_exit (status)
int pthread_cancel(pthread_t threadid);

等待執行緒狀態

pthread_join (threadid,status)  

例子:

 1 #include <stdio.h>
 2 #include <pthread.h> //liunx執行緒標頭檔案
 3 #include <stdlib.h>
 4 //執行緒
 5 void *thread1_proc(void *arg)
 6 {
 7     int i=*(int *)arg;    //取出內容 
 8     free(arg);//釋放空間 
 9     while(i<105)
10     {
11         printf("thread1:%-5d",i);
12         sleep(2);//延時等待兩秒 
13         i++;
14     }
15     printf("Thread1 finished!\n");
16     pthread_exit(NULL);//終止當前執行緒
17 }
18 void main()
19 {
20     pthread_t thread1;
21     int *ixi=(int *)malloc(sizeof(int));//在堆中申請一塊內容 
22     *ixi=100; //存在內容 
23     if(pthread_create(&thread1,NULL,thread1_proc,(void *)ixi)!=0)//建立執行緒1並傳遞引數 
24         perror("Create thread failed:");//建立錯誤時執行
25     //終止當前執行緒,此時會子執行緒會執行完畢,相當於在此處join所有子執行緒一樣 
26     pthread_exit(NULL);//(1)結束主
27     // pthread_join(thread1,NULL);//(2)可替換上一條
28     printf("主執行緒已經退出,本條不執行"); //(1)不執行,(2)執行該條
29 }

多執行緒共享資源

共享資源時可能會出現操作未完成而被另一個執行緒打破,造成資源存取異常

定義變數

#include <pthread.h> 
pthread_mutex_t lockx;

初始化

pthread_mutex_init(&lockx,NULL);

上鎖與解鎖

pthread_mutex_lock(&lockx);//上鎖 
//獨立資源
//程式碼塊 
pthread_mutex_unlock(&lockx);//解鎖

訊號量
實現循序控制
定義變數

#include <semaphore.h>
sem_t can_scanf;

初始化

sem_init(&can_scanf,0,1);

PV操作

sem_wait(&can_scanf);//等待訊號量置位並進行減一操作
sem_post(&can_scanf); //訊號量加一 操作

例子
主執行緒負責從鍵盤獲取兩個整數,子執行緒1負責對這兩個整數完成求和運算並把結果列印出來,子執行緒2負責對這兩個整數完成乘法運算並列印出來。三個執行緒要求遵循如下同步順序:
1.主執行緒獲取兩個數;
2.子執行緒1計算;
3.子執行緒2計算;
4.轉(1)

 1 #include <stdio.h>
 2 #include <semaphore.h>
 3 #include <pthread.h>
 4 #include <stdlib.h>
 5 sem_t can_add;//能夠進行加法計算的訊號量
 6 sem_t can_mul;//能夠進行輸入的訊號量 
 7 sem_t can_scanf;//能夠進行乘法計算的訊號量
 8 int x,y;
 9 void *thread_add(void *arg)//加法執行緒入口函式
10 {
11     while(1)
12     {
13         sem_wait(&can_add);
14         printf("%d+%d=%d\n",x,y,x+y);
15         sem_post(&can_mul);
16     }
17 }
18 void *thread_mul(void *arg)//乘法執行緒入口函式
19 {
20     while(1)
21     {
22         sem_wait(&can_mul);
23         printf("%d*%d=%d\n",x,y,x*y);
24         sem_post(&can_scanf);
25     }
26 }
27 int main()
28 {
29     pthread_t tid;
30     int arg[2];
31     //訊號量初始化 
32     sem_init(&can_scanf,0,1);
33     sem_init(&can_add,0,0);
34     sem_init(&can_mul,0,0);
35     if(pthread_create(&tid,NULL,thread_add,NULL)<0)
36     {
37         printf("Create thread_add failed!\n");
38         exit(0);
39     }
40     if(pthread_create(&tid,NULL,thread_mul,NULL)<0)
41     {
42         printf("Create thread_mul failed!\n");
43         exit(0);
44     }
45     while(1)
46     {
47         sem_wait(&can_scanf);//等待訊號量置位並進行減一操作 
48         printf("Please input two integers:");
49         scanf("%d%d",&x,&y);
50         sem_post(&can_add);//訊號量加一 操作 
51     }        
52 }

Socket程式設計

資料包的傳送
(1)TCP(write/send)
基於流的,沒有資訊邊界,所以傳送的包的大小沒有限制;但由於沒有資訊邊界,就得要求要求應用程式自己能夠把邏輯上的包分割出來。
(2)UDP(sendto)
基於包的,應用層的包在由下層包的傳輸過程中因儘量避免有分片——重組的發生,否則丟包的概率會很大,在乙太網中,MTU的大小為46——1500位元組,除掉IP層頭、udp頭,應用層的UDP包不要超過1472位元組。鑑於Internet上的標準MTU值為576位元組,所以我建議在進行Internet的UDP程式設計時。 最好將UDP的資料長度控制在548位元組(576-8-20)以內.


資料包的接收
(1)TCP(read/recv)
如果協議棧緩衝區實際收到的位元組數大於所請求的位元組數,則返回實際要讀取的位元組數,剩餘未讀取的位元組數下次再讀;
如果協議棧緩衝區實際收到的位元組數小於所請求的位元組數,則返回所能提供的位元組數;
(2)UDP(recvfrom)
如果協議棧緩衝區實際收到的位元組數大於所請求的位元組數,在linux下會對資料包進行截段,並丟棄剩下的資料;
如果協議棧緩衝區實際收到的位元組數小於所請求的位元組數,則返回所能提供的位元組數;


注意點
當傳送函式返回時,並不表示資料包已經到達了目標計算機,僅僅說明待傳送的資料包被協議棧給接收了;
UDP的資料包要麼被接收,要麼丟失;TCP的資料包一定會無差錯按序交付給對方

TCP

服務端
1、連線WiFi或者開啟AP,使模組接入網路
2、socket 建立一個套接字
socket可以認為是應用程式和網路之間資訊傳輸通道,所以TCP程式設計服務端、客戶端的第一步就是要建立這個資訊傳輸的通道,主要通過socket函式完成。

3、 Bind socket資訊
給在第一步中所建立的socket顯式指定其ip地址和埠號(bind)
其中結構體為:

//設定server的詳情資訊
struct sockaddr_in server_addr,client_addr;
u32_t sock_size=sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(2351); ////繫結本機的所有IP地址htonl(INADDR_ANY),確定某個inet_addr(“172.16.4.1”)
server_addr.sin_addr.s_addr =htonl(INADDR_ANY); 
bind(connect_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

4、 listen確定請求佇列的最大值

5、 accept等待接入
此函式為所有網路函式中最難理解的一個函式,它的呼叫將意味著服務端開始處理外來請求,如果沒有外來請求(也就是沒有listen到請求進來)預設情況下則阻塞;當有外來請求時會新產生一個soket,並返回其描述符,應用程式將在這個新的socket上和請求者進行會話(讀、寫該socket),原套接字sockfd則繼續偵聽

6、 send
當send返回時,並不是表示資料已經傳送到了對方,而僅僅表示資料已經到了協議棧的緩衝區中。最後一個值在ESP32中不可用

 

7、 recv
預設情況下,當沒有可接收的資料時則阻塞,引數len表示最多接收多少個位元組數, 成功的接受的位元組數完全可以小於len。最後一個值在ESP32中不可用

客戶端
1、連線WiFi或者開啟AP,使模組接入網路
2、socket 建立一個套接字,參考伺服器
3、是指向服務端發起連線請求(請求成功的前提是服務端已經進入了accept狀態)
結構體引數

//設定server的詳情資訊
struct sockaddr_in server_addr,client_addr;
u32_t sock_size=sizeof(struct sockaddr_in);
server_addr.sin_family = AF_INET; //IPV4
server_addr.sin_port = htons(2351); ////繫結本機的所有IP地址htonl(INADDR_ANY),確定某個inet_addr(“172.16.4.1”)
server_addr.sin_addr.s_addr = inet_addr("192.168.43.21"); 
int ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//連線伺服器

4、recv 和 send

 伺服器示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <sys/socket.h>
 5 #include <arpa/inet.h>
 6 #include <netinet/in.h>
 7 #include <string.h>
 8 #define MAXCONN 8
 9 int main()
10 {
11     int listen_fd,comm_fd;
12     int ret;
13     int i=1;
14     struct sockaddr_in server_addr,client_addr;
15     int sock_size=sizeof(struct sockaddr_in);
16     listen_fd=socket(AF_INET,SOCK_STREAM,0);//建立一個socket,引數(IPV4,TCP,0)
17     if(listen_fd<0)
18     {
19         perror("Failed to create socket:");
20         return -1;
21     }
22     bzero(&server_addr,sock_size);//清零server_addr
23     server_addr.sin_family=AF_INET;//IPV4
24     server_addr.sin_port=htons(8000);//
25     server_addr.sin_addr.s_addr=INADDR_ANY;//繫結主機全部網路地址
26     setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));//設定套接字關聯的選 項
27     ret=bind(listen_fd,(struct sockaddr*)&server_addr,sock_size);//網路主機繫結
28     if(ret==0)
29     {
30         printf("Bind Successfully!\n");
31     }
32     ret=listen(listen_fd,MAXCONN);//確定最大監聽數
33     if(ret==0)
34     {
35         printf("Listen Successfully!\n");
36     }
37     while((comm_fd=accept(listen_fd,(struct sockaddr*)&client_addr,&sock_size))>=0)//阻塞並等待接入
38     {
39         char ipaddr[16];
40         inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,16);//網路地址符轉換
41         printf("連線進入:%s\n",ipaddr);
42         while(1)
43         {
44             char buff[512];
45             int count;
46             count=read(comm_fd,buff,511);//讀資料,接收
47             if(count>0)//判斷接收的位元組數是否大於零
48             {
49                 buff[count]=0;//截斷字串
50                 printf("收到來自 %s 的資料:%s\n",ipaddr,buff);
51                 if(strncmp(buff,"quit",4)==0)//判斷退出條件
52                 {
53                     printf("%s已經退出退出,等待下一個連線\n",ipaddr);
54                     break;//退出此個連線,進行下一個連線接入
55                 }
56                 write(comm_fd,buff,count);//寫資料,傳送
57             }
58             else
59             {
60                 printf("A talking is over!\n");
61                 break;  //客戶端斷開                  
62             }
63         }
64     }
65     close(listen_fd);//關閉連線
66     return 0;
67     
68 }

客戶端示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <sys/socket.h>
 5 #include <arpa/inet.h>
 6 #include <netinet/in.h>
 7 #include <string.h>
 8 #include <string.h>
 9 int main(int argc,char **argv)
10 {
11     int client_fd;
12     int ret;
13     int count;
14     struct sockaddr_in server_addr;
15     char buf[512];
16     char recv_buf[512];
17     int sock_size=sizeof(struct sockaddr_in);
18     if(argc<2)
19     {
20         printf("Usage:./client serverip\n");
21         return 0;
22     }
23     bzero(&server_addr,sock_size);//清零server_addr
24     client_fd=socket(AF_INET,SOCK_STREAM,0);//建立一個socket,引數(IPV4,TCP,0)
25     server_addr.sin_family=AF_INET;
26     server_addr.sin_port=htons(8000);
27     server_addr.sin_addr.s_addr=inet_addr(argv[1]);
28     ret=connect(client_fd,(struct sockaddr*)&server_addr,sock_size);//連線伺服器
29     if(ret<0)
30     {
31         perror("Failed to connect:");
32         return -1;
33     }
34     printf("Connect successfully!\n");
35     while(1)
36     {    printf("請輸入要傳送的內容:");
37         fgets(buf,512,stdin);//從鍵盤獲取字串
38         ret=write(client_fd,buf,strlen(buf));//寫資料,傳送
39         if(ret<=0)
40             break;
41         if(strncmp(buf,"quit",4)==0){
42             printf("程式退出\n");
43             break;    
44         }
45         count=read(client_fd,recv_buf,511);//讀資料,接收
46         if(count>0)
47         {
48             recv_buf[count]=0;//截斷接收的字串
49             printf("Echo:%s\n",recv_buf);
50         }    
51         else
52         {
53             break;//伺服器斷開
54         }
55     }
56     close(client_fd);//關閉連線
57     return 0;
58     
59 }

UDP

伺服器
1、 建立socket
2、 呼叫函式設定udp播

int  setsockopt(int  s,  int level,  int  optname, const void *optval, socklen_t  optlen);
標頭檔案:<sys/socket.h>
level : 選項級別(例如SOL_SOCKET)
optname : 選項名(例如SO_BROADCAST)
optval : 存放選項值的緩衝區的地址
optlen : 緩衝區長度
返回值:成功返回0   失敗返回-1並設定errno

3、 繫結伺服器資訊bind
4、 資料收發
資料傳送

int  sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen);
返回:大於0-成功傳送資料長度;-1-出錯;
UDP套接字使用無連線協議,因此必須使用sendto函式,指明目的地址;
msg:傳送資料緩衝區的首地址;
len:緩衝區的長度;
flags:傳輸控制標誌,通常為0;
to:傳送目標;
tolen: 地址結構長度——sizeof(struct sockaddr)

資料接收

int  recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);
返回:大於0——成功接收資料長度;-1——出錯;
buf:接收資料的儲存地址;
len:接收的資料長度
flags:是傳輸控制標誌,通常為0;
from:儲存傳送方的地址
fromlen: 地址結構長度。

伺服器示例

 1 #include <sys/socket.h>
 2 #include <netinet/in.h>
 3 #include <arpa/inet.h>
 4 #include <string.h>
 5 #include <stdio.h>
 6 int main()
 7 {
 8     int sockfd;
 9     int ret;
10     char buff[512];
11     char ipaddr[16];
12     struct sockaddr_in server_addr,client_addr;
13     int i=1;
14     int sock_size=sizeof(struct sockaddr_in);
15     sockfd=socket(AF_INET,SOCK_DGRAM,0);
16     if(sockfd<0)
17     {
18         perror("Failed to socket:");
19         return -1;
20     }
21     bzero(&server_addr,sock_size);
22     server_addr.sin_family=AF_INET;//伺服器相關引數設定
23     server_addr.sin_port=htons(6000);
24     server_addr.sin_addr.s_addr=INADDR_ANY;
25     setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&i,sizeof(int));
26     if(bind(sockfd,(struct sockaddr*)&server_addr,sock_size)<0)//等待客戶端接入,阻塞
27     {
28         perror("Failed to bind:");
29         return -1;
30     }
31     while(1)
32     {
33         ret=recvfrom(sockfd,buff,512,0,(struct sockaddr*)&client_addr,&sock_size);//收到資料包
34         if(ret>0)
35         {
36             buff[ret]=0;
37             inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,ipaddr,16);//網路地址符轉換
38             printf("Receive a string from %s:%d,data:%s\n",ipaddr,client_addr.sin_port,buff);
39             if(strncmp(buff,"exit",4)==0){//退出
40               printf("Socket server exit ");
41               close(sockfd);//關閉socket    
42               break;    
43             }
44             sendto(sockfd,buff,ret,0,(struct sockaddr*)&client_addr,sock_size);
45         }
46     }
47     close(sockfd);    
48 }

客戶端示例

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <fcntl.h>
 4 #include <sys/socket.h>
 5 #include <arpa/inet.h>
 6 #include <netinet/in.h>
 7 #include <string.h>
 8 #include <strings.h>
 9 int main(int argc,char **argv)
10 {
11     int client_fd;
12     int ret;
13     int count;
14     struct sockaddr_in server_addr,sock_addr;
15     char buf[512];
16     char recv_buf[512];
17     int sock_size=sizeof(struct sockaddr_in);
18     if(argc<2)
19     {
20         printf("Usage:./udpclient serverip\n");
21         return 0;
22     }
23     client_fd=socket(AF_INET,SOCK_DGRAM,0);
24     bzero(&server_addr,sock_size);
25     server_addr.sin_family=AF_INET;
26     server_addr.sin_port=htons(6000);
27     server_addr.sin_addr.s_addr=inet_addr(argv[1]);
28     while(1)
29     {   
30         printf("In:");
31         fgets(buf,512,stdin);
32         ret=sendto(client_fd,buf,strlen(buf),0,(struct sockaddr*)&server_addr,sock_size);
33         if(ret<0)
34         {
35             perror("Failed to sendto:");
36             break;
37         }
38         if(strncmp(buf,"exit",4)==0)
39             break;    
40         count=recvfrom(client_fd,recv_buf,512,0,(struct sockaddr*)&sock_addr,&sock_size);
41         if(count>0)
42         {
43             recv_buf[count]=0;
44             printf("Echo:%s\n",recv_buf);
45         }    
46         else
47         {
48             perror("Failed to recvfrom:");
49             break;
50         }
51     }
52     close(client_fd);
53     return 0;
54     
55 }

參考:

https://www.cnblogs.com/mywolrd/archive/2009/02/05/1930707.html

物聯網閘道器開發技術(羅老師)

相關文章