日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 运维知识 > linux >内容正文

linux

Linux下的TCP/IP编程----IO复用及IO复用服务端

發(fā)布時(shí)間:2023/11/30 linux 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux下的TCP/IP编程----IO复用及IO复用服务端 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

http://blog.csdn.net/wqc_csdn/article/details/51583901

在之前我們實(shí)現(xiàn)的并發(fā)服務(wù)端時(shí)通過(guò)床將多個(gè)進(jìn)程來(lái)實(shí)現(xiàn)的,這種并實(shí)現(xiàn)并發(fā)的方式簡(jiǎn)單方便,但是進(jìn)程的創(chuàng)建和銷(xiāo)毀是很消耗系統(tǒng)資源的,在訪問(wèn)量大時(shí)服務(wù)器很容易出現(xiàn)資源不夠用的情況。除此之外,由于每個(gè)進(jìn)程有獨(dú)立的內(nèi)存空間,所以進(jìn)程間的通訊也相對(duì)比較復(fù)雜。因此我們可以考慮通過(guò)另一種方式來(lái)實(shí)現(xiàn)服務(wù)端的并發(fā)服務(wù)——IO復(fù)用。

復(fù)用:

復(fù)用在通訊領(lǐng)域很常見(jiàn),一般常見(jiàn)”頻分復(fù)用”,”時(shí)分復(fù)用”等名詞。其實(shí)復(fù)用就是在一個(gè)通信頻道內(nèi)傳遞多個(gè)數(shù)據(jù)(信號(hào))的技術(shù)。以頻分復(fù)用為例:其實(shí)就是在一個(gè)通信信道內(nèi),發(fā)送端通過(guò)把信息加載在不同頻率的波段上進(jìn)行發(fā)送,而接受端在接受到波時(shí)通過(guò)濾波裝置把各中頻率的波進(jìn)行分離,以此達(dá)到提高通信信道利用率的目的。

IO復(fù)用:

IO復(fù)用其實(shí)也是通過(guò)對(duì)IO描述符的復(fù)用來(lái)減少進(jìn)程的創(chuàng)建,使得服務(wù)端始終只有一個(gè)進(jìn)程,從而節(jié)省了系統(tǒng)資源,提高效率。


select()函數(shù)是最具有代表性的實(shí)現(xiàn)復(fù)用服務(wù)端的方法,它可以將多個(gè)文件描述符集中到一起進(jìn)行統(tǒng)一監(jiān)視,當(dāng)監(jiān)視到有文件描述符需要輸入或者是輸出時(shí)就選擇該接口進(jìn)行通訊,通訊完成之后就回到之前監(jiān)視的狀態(tài)。

監(jiān)視內(nèi)容:是否存在套接字接受數(shù)據(jù)?無(wú)需阻塞傳輸數(shù)據(jù)的套接字有哪些?哪些套接字發(fā)生了異常?

int select(int maxfd,fd_set *read_set, *write_set,fd_set *except_set, const struct timeval *timeout)選擇描述符進(jìn)行通訊:

  • maxfd(監(jiān)視數(shù)量):監(jiān)視對(duì)象文件描述符數(shù)量

  • read_set(讀取文件描述符集合的地址):將所有關(guān)注”是否存在待讀取數(shù)據(jù)”的文件描述符注冊(cè)到fd_set集合中,并傳遞地址值。也就是說(shuō)select()函數(shù)會(huì)監(jiān)視這個(gè)集合里邊的文件描述符是是否有待讀取的數(shù)據(jù),沒(méi)有要監(jiān)聽(tīng)的描述符時(shí)傳0

  • write_set(寫(xiě)入文件描述符集合的地址):將所有關(guān)注”是否可傳輸無(wú)阻塞數(shù)據(jù)”的文件描述符注冊(cè)到fd_set集合中,并傳遞地址值。也就是說(shuō)select()函數(shù)會(huì)監(jiān)視這個(gè)集合里邊的文件描述符是否能發(fā)送無(wú)阻塞數(shù)據(jù),沒(méi)有要監(jiān)聽(tīng)的描述符時(shí)傳0

  • except_set(發(fā)生異常文件描述符集合的地址):將所有關(guān)注”是否可發(fā)生異常”的文件描述符注冊(cè)到fd_set集合中,并傳遞地址值。也就是說(shuō)select()函數(shù)會(huì)監(jiān)視這個(gè)集合里邊的文件描述符是否發(fā)生異常,沒(méi)有要監(jiān)聽(tīng)的描述符時(shí)傳0

  • timeout(超時(shí)):位防止無(wú)限進(jìn)入阻塞狀態(tài),設(shè)置一個(gè)超時(shí)信息

發(fā)生錯(cuò)誤時(shí)返回-1,超時(shí)時(shí)返回0,當(dāng)所關(guān)注的事件發(fā)生時(shí),返回所發(fā)生事件的文件描述符數(shù)量

select()函數(shù)的使用比較復(fù)雜,大體分為三步:

  • 參數(shù)設(shè)置:

    • 設(shè)置文件描述符:使用select()函數(shù)能同時(shí)監(jiān)聽(tīng)多個(gè)文件描述符,首先要使用fd_set類(lèi)型將這些文件描述符按照分類(lèi)(接收,傳輸,異常)集中起來(lái)。

      fd_set是一個(gè)存有0和1的位數(shù)組。從下標(biāo)0開(kāi)始,一直到下標(biāo)為當(dāng)前文件描述符的最大序號(hào)為止,依次表示該文件描述符是否被監(jiān)聽(tīng),例如fd_set 變量fds[0]中的值為1時(shí)表示文件描述符0(標(biāo)準(zhǔn)的輸入流)被監(jiān)聽(tīng)。?
      針對(duì)fd_set的操作都是以位為單位的,為此專(zhuān)門(mén)編寫(xiě)了用于fd_set讀寫(xiě)的宏定義:

    • FD_ZERO(fd_set *fdset):將fd_set的所有位初始化為0

    • FD_SET(int fd,fd_set *fdset):在fd_set中注冊(cè)文件描述符fd的信息

    • FD_CLR(int fd,fd_set *fdset):從fd_set中清除文件描述符fd的信息

    • FD_ISSET(int fd,fd_set *fdset):查詢(xún)fd_set中是否包含文件描述符fd的信息

    • 指定監(jiān)聽(tīng)范圍:指定監(jiān)聽(tīng)文件描述符的范圍,其實(shí)也就是fd_set中的文件描述符數(shù)量,由于每次新創(chuàng)建一個(gè)文件描述符時(shí)都會(huì)自動(dòng)加1,所以要傳入的值為最大的文件描述符+1(加一是由于文件描述符的標(biāo)號(hào)從0開(kāi)始)。

    • 設(shè)置超時(shí):由于當(dāng)文件描述符沒(méi)有狀態(tài)的改變時(shí)select()函數(shù)會(huì)始終處于阻塞狀態(tài),設(shè)置超時(shí)時(shí)間就是為了防止無(wú)限制的等待。即使文件描述符沒(méi)有發(fā)生變化,只要過(guò)了指定時(shí)間,函數(shù)會(huì)返回0。這樣在函數(shù)調(diào)用時(shí)能知道當(dāng)前的狀態(tài)。

      結(jié)構(gòu)體timeval用于保存設(shè)置的超時(shí)時(shí)間,每次在調(diào)用select()函數(shù)之前都要重新設(shè)置超時(shí)時(shí)間,其結(jié)構(gòu)體如下:

      struct timeval{long tv_sec;//秒數(shù)long tv_usec://毫秒數(shù) }
      • 1
      • 2
      • 3
      • 4
  • 調(diào)用select()函數(shù):監(jiān)聽(tīng)注冊(cè)的文件描述符的狀態(tài),當(dāng)有狀態(tài)發(fā)生變化,或者時(shí)超時(shí)時(shí)返回結(jié)果。

  • 查看調(diào)用結(jié)果:當(dāng)select()函數(shù)返回值是大于0的整數(shù)時(shí)說(shuō)明是所監(jiān)聽(tīng)的文件描述符的狀態(tài)發(fā)生了變化,這時(shí)我們可以通過(guò)之前的fd_set變量來(lái)查看變化的結(jié)果。

    當(dāng)select()函數(shù)調(diào)用完之后向其傳入的fd_set變量將發(fā)生變化,原來(lái)為1的所有位均變?yōu)?,但是發(fā)生變化的文件描述符對(duì)應(yīng)位除外,因此可以認(rèn)為值仍為1的位置上的文件描述符發(fā)生了變化。


  • 至此關(guān)于select()函數(shù)的介紹就結(jié)束了,用起來(lái)比較復(fù)雜,我們梳理一遍使用過(guò)程:

  • 準(zhǔn)備工作:

    • 為select()設(shè)置要監(jiān)視的文件描述符集合,使用函數(shù)庫(kù)提供的關(guān)于fd_set的宏定義設(shè)置fd_set

    • 為select()設(shè)置監(jiān)視范圍,即當(dāng)前最大文件描述符+1

    • 位select(0設(shè)置超時(shí)時(shí)間,把秒數(shù)填入timeval結(jié)構(gòu)體的tv_sec成員中,把毫秒數(shù)填入timeval結(jié)構(gòu)體的tv_usec成員中,每次在調(diào)用select()函數(shù)之前都要重新設(shè)置超時(shí)時(shí)間。

  • 調(diào)用select()函數(shù)

  • 查看調(diào)用結(jié)果:根據(jù)fd_set調(diào)用前后的變化來(lái)確定發(fā)生變化的文件描述符,調(diào)用之后fd_set中值為1的位所對(duì)應(yīng)的文件描述符狀態(tài)發(fā)生了變化

  • 調(diào)用發(fā)生變化的文件描述符進(jìn)行相應(yīng)的操作


  • 在大體了解了select()函數(shù)的使用過(guò)程之后我們就可以嘗試著進(jìn)行一下簡(jiǎn)單的應(yīng)用:

    #include<stdio.h> #include<unistd.h> #include<sys/time.h> #include<sys/select.h>#define BUFF_SIZE 30int main(){//聲明文件描述符集合fd_set read_set;fd_set temp_set;//保存函數(shù)的返回結(jié)果int select_res;//字符串長(zhǎng)度int str_len;//字符緩沖char buff[BUFF_SIZE];//超時(shí)時(shí)間結(jié)構(gòu)體struct timeval time_out;//初始化fd_set,所有位都置0FD_ZERO(&read_set);//設(shè)置fd_set,使其監(jiān)視文件描述符為0的文件描述符(系統(tǒng)的標(biāo)準(zhǔn)輸入流)FD_SET(0,&read_set);while(1){temp_set = read_set;//設(shè)置超時(shí)時(shí)間time_out.tv_sec = 5;time_out.tv_usec = 0;//調(diào)用select()函數(shù)select_res = select(1,&temp_set,0,0,&time_out);//根據(jù)返回值來(lái)判斷是否變化if(select_res == -1){puts("select() error");break;}else if(select_res == 0){puts("select() timeout");}else{//檢查是否含有要查詢(xún)的描述符if(FD_ISSET(0,&temp_set)){//從文件描述符為0的流中讀取數(shù)據(jù)str_len = read(0,buff,BUFF_SIZE);buff[str_len] = 0;printf("message from console : %s ",buff);}}}return 0; }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    IO復(fù)用的服務(wù)端:

    /*************************************************************************> File Name: echo_select_server.c> Author: xjhznick> Mail: xjhznick@gmail.com > Created Time: 2015年03月26日 星期四 14時(shí)03分40秒> Description:使用select函數(shù)實(shí)現(xiàn)I/O復(fù)用服務(wù)器端************************************************************************/#include<stdio.h> #include<stdlib.h> #include<string.h> #include<unistd.h> #include<arpa/inet.h> #include<sys/socket.h> #include<sys/time.h> #include<sys/select.h>void error_handling(char *message);#define BUFF_SIZE 32int main(int argc, char *argv[]) {int server_sock;int client_sock;struct sockaddr_in server_addr;struct sockaddr_in client_addr;socklen_t client_addr_size;char buff[BUFF_SIZE];fd_set reads, reads_init;struct timeval timeout, timeout_init;int str_len, i, fd_max, fd_num;if(argc!=2){ //命令行中啟動(dòng)服務(wù)程序僅限一個(gè)參數(shù):端口號(hào)printf("Usage : %s <port>\n", argv[0]);exit(1);}//調(diào)用socket函數(shù)創(chuàng)建套接字server_sock = socket(PF_INET, SOCK_STREAM, 0);if(-1 == server_sock){error_handling("socket() error.");}memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));//調(diào)用bind函數(shù)分配IP地址和端口號(hào)if( -1 == bind( server_sock, (struct sockaddr*)&server_addr, sizeof(server_addr)) ){error_handling("bind() error");}//監(jiān)聽(tīng)端口的連接請(qǐng)求,連接請(qǐng)求等待隊(duì)列size為5if( -1 == listen(server_sock, 5) ){error_handling("listen() error");}//register fd_set varFD_ZERO(&reads_init);FD_SET(server_sock, &reads_init);//monitor socket: server_sockFD_SET(0, &reads_init);// stdin also worksfd_max = server_sock;//timeout_init.tv_sec = 5;timeout_init.tv_usec= 0;while(1){//調(diào)用select之后,除發(fā)生變化的文件描述符對(duì)應(yīng)的bit,其他所有位置0,所以需用保存初值,通過(guò)復(fù)制使用reads = reads_init;//調(diào)用select之后,timeval成員值被置為超時(shí)前剩余的時(shí)間,因此使用時(shí)也需要每次用初值重新初始化timeout = timeout_init;fd_num = select(fd_max+1, &reads, NULL, NULL, &timeout);if(fd_num < 0){fputs("Error select()!", stderr);break;}else if(fd_num == 0){puts("Time-out!");continue;}for(i=0; i<=fd_max; i++){if(FD_ISSET(i, &reads)){if(i == server_sock){//connection request!//接受連接請(qǐng)求client_addr_size = sizeof(client_addr);client_sock = accept( server_sock, (struct sockaddr*)&client_addr, &client_addr_size );//accept函數(shù)自動(dòng)創(chuàng)建數(shù)據(jù)I/0 socketif(-1 == client_sock){error_handling("accept() error");//健壯性不佳,程序崩潰退出} else{//注冊(cè)與客戶(hù)端連接的套接字文件描述符FD_SET(client_sock, &reads_init);if(fd_max < client_sock) fd_max = client_sock;printf("Connected client : %d\n", client_sock);}}else{//read message!str_len = read(i, buff, BUFF_SIZE);if(str_len){//echo to clientbuff[str_len] = 0;printf("Message from client %d: %s", i, buff);write(i, buff, str_len);}else{ //close connectionFD_CLR(i, &reads_init);close(i);printf("Disconnected client %d!\n", i);}}//end of i==server_sock}//end of if(FD_ISSET)}//end of for}//end of while//斷開(kāi)連接,關(guān)閉套接字close(server_sock);return 0; }void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(EXIT_FAILURE); }

    總結(jié)

    以上是生活随笔為你收集整理的Linux下的TCP/IP编程----IO复用及IO复用服务端的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。