C++网络编程快速入门(二):Linux下使用select演示简单服务端程序
目錄
- select參數(shù)解釋
- select使用規(guī)范
- select使用缺點(diǎn)
- 基本流程
- 實(shí)例代碼
- 通信效果演示
- 往期文章
select參數(shù)解釋
extern int select (int __nfds, fd_set *__restrict __readfds,fd_set *__restrict __writefds,fd_set *__restrict __exceptfds,struct timeval *__restrict __timeout);__nfds:一般設(shè)置為所有需要使用select函數(shù)檢測(cè)時(shí)間的fd中的最大值+1
__readfds:需要監(jiān)聽(tīng)可讀事件的fd集合
__writefds:需要監(jiān)聽(tīng)可寫(xiě)事件的fd集合
__exceptfds:需要監(jiān)聽(tīng)可寫(xiě)事件的fd集合
__timeout:超時(shí)時(shí)間,在這個(gè)設(shè)定的時(shí)間內(nèi)檢測(cè)這些fd事件,超過(guò)這個(gè)超時(shí)時(shí)間,select將立即返回
fd_set這個(gè)結(jié)構(gòu)體信息如下:
可以簡(jiǎn)化看做:
typedef struct {long int __fds_bits[16]; } fd_set;long int 占8字節(jié),也就是8 * 8 = 64個(gè)bit,所以__fds_bits數(shù)組總共就占64 * 16 = 1024個(gè)fd狀態(tài)。每個(gè)bit位0表示無(wú)事件,1表示有事件。
select使用規(guī)范
1、select在調(diào)用前后可能會(huì)修改readfds、writefds、exceptfds中的內(nèi)容,所以如果在下次調(diào)用時(shí)復(fù)用這些fd_set,則要在下次調(diào)用前使用FD_ZERO將fd_set清空,然后調(diào)用FD_SET將要檢測(cè)的fd添加到fd_set中
2、linux系統(tǒng)下select函數(shù)也會(huì)修改timeval結(jié)構(gòu)體的值,所以想要復(fù)用也必須給其重新設(shè)置值
3、select函數(shù)的timeval結(jié)構(gòu)體中的tv_sec和tv_usec如果都被設(shè)置為0,代表著檢測(cè)事件的總時(shí)間被設(shè)置為0,行為就變成了select檢測(cè)相關(guān)集合中的fd,如果沒(méi)有需要的事件,則立即返回
4、如果select函數(shù)的timeval參數(shù)設(shè)置為NULL,則select會(huì)一直阻塞下去,直到我們需要的事件被觸發(fā)
5、Linux下,select函數(shù)第一個(gè)參數(shù)必須設(shè)置為需要檢測(cè)事件fd中最大值+1,所以每次產(chǎn)生一個(gè)新fd都需要和maxfd作比較
select使用缺點(diǎn)
1、select函數(shù)需要將fd集合從用戶(hù)態(tài)拷貝到內(nèi)核態(tài),在fd較多時(shí)開(kāi)銷(xiāo)較大。并且每次檢測(cè)時(shí)也是在內(nèi)核中遍歷這個(gè)fd_set。
2、單個(gè)進(jìn)程能夠監(jiān)視的文件描述符數(shù)量上存在最大限制,linux上我們算過(guò)了,只有1024個(gè)
3、select函數(shù)每次調(diào)用之前都要對(duì)傳入的參數(shù)重新設(shè)定,比較麻煩
4、在linux上select函數(shù)的實(shí)現(xiàn)原理是底層的poll函數(shù),所以select和poll本質(zhì)上沒(méi)有區(qū)別
基本流程
實(shí)例代碼
#include <iostream> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <sys/time.h> #include <vector> #include <errno.h> #include <stdio.h> #include <string.h> using namespace std; int main() {// 創(chuàng)建一個(gè)監(jiān)聽(tīng)socketint listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1) {cout << "create listen error" << endl;return -1;}// 初始化服務(wù)器地址struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_add當(dāng)斷開(kāi)各個(gè)客戶(hù)端時(shí),服務(wù)端的select函數(shù)對(duì)各個(gè)客戶(hù)端fd進(jìn)行檢測(cè)時(shí),仍然會(huì)觸發(fā)可讀事件r = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr *)& bindaddr, sizeof(bindaddr)) == -1) {cout << "bind listen socket error" << endl;close(listenfd);return -1;}// 啟動(dòng)監(jiān)聽(tīng)if (listen(listenfd,SOMAXCONN) == -1) {cout << "listen error" << endl;close(listenfd);return -1;}// 存儲(chǔ)客戶(hù)端socket的數(shù)組vector<int> clientfds;int maxfd;while (true) {fd_set readset;// 對(duì)標(biāo)志位清零FD_ZERO(&readset);// 將監(jiān)聽(tīng)的socket加入到待檢測(cè)的可讀事件中// 第listenfd位被置為1FD_SET(listenfd, &readset);maxfd = listenfd;// 將客戶(hù)端socket加入到待檢測(cè)的可讀事件中for (int i = 0; i < clientfds.size(); i++) {if (clientfds[i] != -1) {FD_SET(clientfds[i], &readset);maxfd = max(maxfd, clientfds[i]);}}// 設(shè)置超時(shí)時(shí)間為1stimeval time;time.tv_sec = 1;time.tv_usec = 0;// 暫且只檢測(cè)可讀事件,不檢測(cè)可寫(xiě)和異常事件int ret = select(maxfd + 1, &readset, NULL, NULL, &time);if (ret == -1) {// 出錯(cuò)if (errno != EINTR)break;} else if (ret == 0) {// select函數(shù)超時(shí)continue;} else {// 檢測(cè)到某個(gè)socket有事件// 是否是監(jiān)聽(tīng)socket的可讀事件if (FD_ISSET(listenfd, &readset)) {// 如果是監(jiān)聽(tīng)socket的可讀事件,表示現(xiàn)在有新的連接到來(lái)struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);// 接受客戶(hù)端連接int clientfd = accept(listenfd, (struct sockaddr *)& clientaddr, &clientaddrlen);if (clientfd == -1) {cout << "client socket error" << endl;break;} else {cout << "accept a client connection , fd:" << clientfd << endl;clientfds.push_back(clientfd);}} else {// 從client socket上接受數(shù)據(jù)// 假設(shè)對(duì)端發(fā)送過(guò)來(lái)的數(shù)據(jù)長(zhǎng)度不超過(guò)63個(gè)字符char recvbuf[64];for (int i = 0; i < clientfds.size(); i++) {if (clientfds[i] != -1 && FD_ISSET(clientfds[i], &readset)) {memset(recvbuf, 0, sizeof(recvbuf));// 接受數(shù)據(jù)int length = recv(clientfds[i], recvbuf, 64, 0);if (length <= 0) {cout << "recv data error, clientfd:" << clientfds[i] << endl;close(clientfds[i]);// 不直接刪除該元素而是將位置元素標(biāo)記clientfds[i] = -1;continue;} else {cout << "clientfd:" << clientfds[i] << "recv data: " << recvbuf << endl;}}}}}}// 處理之后,關(guān)閉所有客戶(hù)端for (int i = 0; i < clientfds.size(); i++) {if (clientfds[i] != -1)close(clientfds[i]);}// 關(guān)閉監(jiān)聽(tīng)close(listenfd);return 0; }通信效果演示
這里不需要寫(xiě)客戶(hù)端程序,直接用nc命令模擬,指定一下服務(wù)端的ip地址和端口號(hào)就可以通信了,127.0.0.1就是系統(tǒng)的回環(huán)地址,直接是用于本機(jī)內(nèi)的socket的通信。這里我們開(kāi)了兩個(gè)客戶(hù)端,分別發(fā)送hello和hello world。
由于nc命令發(fā)送的數(shù)據(jù)是按換行符區(qū)分的,所以數(shù)據(jù)包最后一個(gè)都是以\n結(jié)束。
當(dāng)斷開(kāi)各個(gè)客戶(hù)端時(shí),服務(wù)端的select函數(shù)對(duì)各個(gè)客戶(hù)端fd進(jìn)行檢測(cè)時(shí),仍然會(huì)觸發(fā)可讀事件。
不過(guò)此時(shí)對(duì)這些fd進(jìn)行調(diào)用recv函數(shù)的話會(huì)返回0,表示對(duì)端關(guān)閉了連接。然后服務(wù)端這邊將fd進(jìn)行置-1,然后關(guān)閉連接即可。
往期文章
C++網(wǎng)絡(luò)編程快速入門(mén)(一):TCP網(wǎng)絡(luò)通信基本流程以及基礎(chǔ)函數(shù)使用
總結(jié)
以上是生活随笔為你收集整理的C++网络编程快速入门(二):Linux下使用select演示简单服务端程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: “岂念慕羣客”下一句是什么
- 下一篇: Linux网络故障排查命令(ifconf