天天看點

Liunx C 程式設計之多線程與Socketinclude include

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.一個程序可以建立的線程最大數量取決于系統實作

  1. 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

繼續閱讀