Liunx C 程式設計之多線程與Socket
多線程
pthread.h是linux特有的頭檔案,POSIX線程(POSIX threads),簡稱Pthreads,是線程的POSIX标準。該标準定義了建立和操縱線程的一整套API。在類Unix作業系統(Unix、Linux、Mac OS X等)中,都使用Pthreads作為作業系統的線程。Windows作業系統也有其移植版pthreads-win32。
建立線程
1.pthread_create 建立一個新線程并使之運作起來。該函數可以在程式的任何地方調用包括線程内,線程是沒有依賴關系的。
2.一個程序可以建立的線程最大數量取決于系統實作
- 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
2 #include //liunx線程頭檔案
3 #include
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_mutex_t lockx;
初始化
pthread_mutex_init(&lockx,NULL);
上鎖與解鎖
pthread_mutex_lock(&lockx);//上鎖
//獨立資源
//代碼塊
pthread_mutex_unlock(&lockx);//解鎖
信号量
實作循序控制
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)
2 #include
4 #include
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=%dn",x,y,x+y);
15 sem_post(&can_mul);
16 }
18 void thread_mul(void arg)//乘法線程入口函數
20 while(1)
21 {
22 sem_wait(&can_mul);
23 printf("%d%d=%dn",x,y,xy);
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中不可用
用戶端
2、socket 建立一個套接字,參考伺服器
3、是指向服務端發起連接配接請求(請求成功的前提是服務端已經進入了accept狀态)
結構體參數
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
伺服器示例
5 #include
6 #include
7 #include
8 #define MAXCONN 8
9 int main()
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("連接配接進入:%sn",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 的資料:%sn",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 }
用戶端示例
8 #include
9 int main(int argc,char **argv)
11 int client_fd;
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 serveripn");
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:%sn",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);
頭檔案:
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: 位址結構長度。
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;
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:%sn",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 }
14 struct sockaddr_in server_addr,sock_addr;
20 printf("Usage:./udpclient serveripn");
23 client_fd=socket(AF_INET,SOCK_DGRAM,0);
24 bzero(&server_addr,sock_size);
26 server_addr.sin_port=htons(6000);
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:%sn",recv_buf);
45 }
46 else
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物聯網網關開發技術(羅老師)
原文位址
https://www.cnblogs.com/dongxiaodong/p/11309140.html