1.select限制
-
用select實現的併發服務器,能達到的併發數,受到2個方面的限制
(1)一個進程能打開的最大文件描述符的限制。這個可以通過調整內核參數實現。
(2)select中的fd_set集合容量的限制(FD_SETSIZE)。這需要重新編譯內核。
FD_SETSIZE是在頭文件中定義的,而且需要重新編譯內核 -
方法1,使用命令:
解決一個進程能打開的最大文件描述符的限制
ulimit -n 2048,進程打開的文件描述符的數量
- 方法2,編程解決:
解決一個進程能打開的最大文件描述符的限制
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while (0);
//僅僅改變當前進程最大文件描述符的限制
int main(void)
{
//man getrlimit
struct rlimit rl;
if (getrlimit(RLIMIT_NOFILE, &rl) <0)
ERR_EXIT("getrlimit"):
printf("%d\n", (int)rl.rlim_max);
rl.rlim_cur = 2048;
rl.rlim_max = 2048;
if (setrlimit(RLIMIT_NOFILE, &rl) <0)
ERR_EXIT("setrlimit"):
if (getrlimit(RLIMIT_NOFILE, &rl) <0)
ERR_EXIT("getrlimit"):
printf("%d\n", (int)rl.rlim_max);
return 0;
}
- eg:select併發量的測試
客戶端代碼:NetworkProgramming-master (1)\LinuxNetworkProgramming\P17\P17echocli.c
//
// Created by wangji on 19-8-6.
//
// socket編程 8 select模型
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
using namespace std;
struct packet
{
int len;
char buf[1024];
};
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
//客戶端僅作連接
int main(int argc, char** argv) {
int count = 0;
while(1)
{
// 1. 創建套接字
int sockfd;
if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)
{
sleep(4);//當客戶端fd超過上限,先sleep4s再退出來,推遲close
ERR_EXIT("socket");
}
// 2. 分配套接字地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
// servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1", &servaddr.sin_addr);
// 3. 請求鏈接
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
ERR_EXIT("connect");
}
struct sockaddr_in localaddr;
socklen_t addrlen = sizeof localaddr;
if (getsockname(sockfd, (struct sockaddr*)&localaddr, &addrlen) < 0)
{
ERR_EXIT("getsockname");
}
printf("id = %s, ", inet_ntoa(localaddr.sin_addr));
printf("port = %d\n", ntohs(localaddr.sin_port));
printf("count = %d\n", ++count);
}
return 0;
}
- eg:select併發量的測試
服務端代碼:NetworkProgramming-master (1)\LinuxNetworkProgramming\P17\P17echoserv.c
//
// Created by wangji on 19-8-7.
//
// socket編程 9
#include <iostream>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
using namespace std;
struct packet
{
int len;
char buf[1024];
};
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; // 剩餘字節數
ssize_t nread;
char *bufp = (char*) buf;
while (nleft > 0)
{
nread = read(fd, bufp, nleft);
if (nread < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
} else if (nread == 0)
{
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;
while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
}
else if (nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看傳入消息
if (ret == -1 && errno == EINTR)
{
continue;
}
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = (char*)buf; // 當前指針位置
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, buf, nleft);
if (ret < 0)
{
return ret;
}
else if (ret == 0)
{
return ret;
}
nread = ret;
int i;
for (i = 0; i < nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i+1);
if (ret != i+1)
{
exit(EXIT_FAILURE);
}
return ret;
}
}
if (nread > nleft)
{
exit(EXIT_FAILURE);
}
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
{
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}
void echo_srv(int connfd)
{
char recvbuf[1024];
// struct packet recvbuf;
int n;
while (1)
{
memset(recvbuf, 0, sizeof recvbuf);
int ret = readline(connfd, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
writen(connfd, recvbuf, strlen(recvbuf));
}
}
void handle_sigpipe(int sig)
{
printf("recv a sig=%d\n", sig);
}
int main(int argc, char** argv) {
int count = 0;
//測試服務端收到了SIGPIPE信號
signal(SIGPIPE, SIG_IGN);
signal(SIGPIPE, handle_sigpipe);
// 1. 創建套接字
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
// 2. 分配套接字地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
int on = 1;
// 確保time_wait狀態下同一端口仍可使用
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0) {
ERR_EXIT("setsockopt");
}
// 3. 綁定套接字地址
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
ERR_EXIT("bind");
}
// 4. 等待連接請求狀態
if (listen(listenfd, SOMAXCONN) < 0) {
ERR_EXIT("listen");
}
// 5. 允許連接
struct sockaddr_in peeraddr;
socklen_t peerlen;
// 6. 數據交換
int nready;//檢測到的事件個數
int maxfd = listenfd;//默認套接口1,2,3已經打開了,所以listenfd爲3
fd_set rset;
fd_set allset;
FD_ZERO(&rset);
FD_ZERO(&allset);
FD_SET(listenfd, &allset);
int connfd;
int i;
int client[FD_SETSIZE];//rset集合中最大容量爲FD_SETSIZE
int maxi;//最大空閒位置初始值爲0
for (i = 0; i < FD_SETSIZE; i++)
{
client[i] = -1;//等於-1表示空閒的
}
while (1)
{
rset = allset;//若沒有把所有感興趣的fd保存至allset中,那麼下一次select,rset裏面其實是已經改變過的fd的集合
//rset只保留當前改變事件的IO集合,並沒有監聽所有的套接口
nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (nready == -1)
{
if (errno == EINTR)//被信號中斷,還可以執行
{
continue;
}
ERR_EXIT("select");
}
if (nready == 0)
{
continue;
}
//監聽套接口發生可讀事件,意味着對方connect已經完成,這邊已完成連接隊列的條目不爲空
//此時,調用accept方法就不再阻塞
if (FD_ISSET(listenfd, &rset))//rset是輸入輸出參數:輸出參數表示:哪些fd產生了事件,輸入參數表示:我們關心哪些文件描述符fd
{
peerlen = sizeof(peeraddr);
//peerlen是輸入輸出參數,要有初始值,返回的是對方地址的長度
connfd = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen);
if (connfd == -1)
{
ERR_EXIT("accept");
}
for (i = 0; i < FD_SETSIZE; i++)
{
if (client[i] < 0)
{
client[i] = connfd;//將connfd保存到client中的空閒位置
if (i > maxi)//最大不空閒位置可能發生改變
maxi = i;//最大空閒位置發生了改變
break;
}
}
if (i == FD_SETSIZE)//找不到空閒位置
{
fprintf(stderr, "too many clients\n");
exit(EXIT_FAILURE);
}
printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));
printf("port = %d\n", ntohs(peeraddr.sin_port));
printf("count = %d\n", c++count);
FD_SET(connfd, &allset);//將connfd加入到allset集合,以便下次關心connfd的可讀事件
if (connfd > maxfd)//更新maxfd
maxfd = connfd;
if (--nready <= 0)//說明檢測的事件已經處理完畢,沒必要往下走
{
continue;
}
}
//connfd產生事件
for (i = 0; i <= maxi; ++i)
{
connfd = client[i];
if (connfd == -1)
{
continue;
}
if (FD_ISSET(connfd, &rset))//已連接套接口有可讀事件
{
char recvbuf[1024] = {0};
int ret = readline(connfd, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
FD_CLR(connfd, &allset);//從allset中清除
// Max--;
//補充(未做):實際上,還應該改變maxi的值,如果i是maxi,則maxi應該等於第二大的值,麻煩,先算了
client[i] = -1;//一旦套接口關閉。位置空閒了,爲-1
close(connfd);//關閉套接口,讓客戶端接收到通知
}
fputs(recvbuf, stdout);
writen(connfd, recvbuf, strlen(recvbuf));
if (--nready <= 0)//所有的事件處理完畢,就break
{
break;
}
}
}
}
return 0;
}
-
測試結果:客戶端沒有sleep
服務端
客戶端
-
測試結果:客戶端加了sleep
服務端:
客戶端:
-
解釋如下
(1)客戶端
0 1 2已經被標準輸入,標準輸出,標準錯誤輸出所佔用
1021個連接
創建第1022個套接字的時候失敗了,若客戶端沒有調用sleep(4),會給服務端發送FIN段,
若客戶端客戶端調用了sleep(4),就不會發送FIN段給對方了
(2)服務端
收到這1021個連接,會在已完成連接隊列中維護1021個條目,若客戶端沒有sleep(4),可能還有300個fd沒有處理,此時創建第1022個套接字的時候失敗了,失敗了就意味着退出進程,導致客戶端發送了FIN段給服務端,而此時服務端還沒有完全接受連接,所以有可能先收到了客戶端的FIN段,所以會出現client close和accept混在一起了 -
沒有sleep的服務端解釋:
close已經收到了,但是已完成連接隊列還沒有完全接收完,一旦套接字關閉,就能騰出來足夠的文件描述符來接受連接了,所以這裏的1021並不代表有1021個併發,因爲有一些客戶端已經關閉了,所以客戶端有個sleep(4)。 -
有sleep的客戶端解釋:
爲啥客戶端是1020個併發?
除了0,1,2外,還有監聽套接字3,所以併發數量爲1024-4=1020.
當fd爲1021時,創建套接字就會失敗,因爲超出了進程創建的fd的限制,所以顯示Too many open files
2.poll:與select差不多
- poll
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
fds:關心的套接口以及事件,通常指向一個數組;
nfds:加入到該數組中的io的個數;
timeout:超時時間;
- struct pollfd結構體
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */要請求的事件
short revents; /* returned events */返回的事件
};
- eg客戶端:代碼如上:NetworkProgramming-master (1)\LinuxNetworkProgramming\P17\P17echocli.c
- eg服務端:NetworkProgramming-master (1)\LinuxNetworkProgramming\P17\P17pollsrv.c
//
// Created by jxq on 19-8-7.
//
// socket編程 12 poll模型
#include <iostream>
#include <stdio.h>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
using namespace std;
struct packet
{
int len;
char buf[1024];
};
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0);
ssize_t readn(int fd, void *buf, size_t count)
{
size_t nleft = count; // 剩餘字節數
ssize_t nread;
char *bufp = (char*) buf;
while (nleft > 0)
{
nread = read(fd, bufp, nleft);
if (nread < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
} else if (nread == 0)
{
return count - nleft;
}
bufp += nread;
nleft -= nread;
}
return count;
}
ssize_t writen(int fd, const void *buf, size_t count)
{
size_t nleft = count;
ssize_t nwritten;
char* bufp = (char*)buf;
while (nleft > 0)
{
if ((nwritten = write(fd, bufp, nleft)) < 0)
{
if (errno == EINTR)
{
continue;
}
return -1;
}
else if (nwritten == 0)
{
continue;
}
bufp += nwritten;
nleft -= nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd, void *buf, size_t len)
{
while (1)
{
int ret = recv(sockfd, buf, len, MSG_PEEK); // 查看傳入消息
if (ret == -1 && errno == EINTR)
{
continue;
}
return ret;
}
}
ssize_t readline(int sockfd, void *buf, size_t maxline)
{
int ret;
int nread;
char *bufp = (char*)buf; // 當前指針位置
int nleft = maxline;
while (1)
{
ret = recv_peek(sockfd, buf, nleft);
if (ret < 0)
{
return ret;
}
else if (ret == 0)
{
return ret;
}
nread = ret;
int i;
for (i = 0; i < nread; i++)
{
if (bufp[i] == '\n')
{
ret = readn(sockfd, bufp, i+1);
if (ret != i+1)
{
exit(EXIT_FAILURE);
}
return ret;
}
}
if (nread > nleft)
{
exit(EXIT_FAILURE);
}
nleft -= nread;
ret = readn(sockfd, bufp, nread);
if (ret != nread)
{
exit(EXIT_FAILURE);
}
bufp += nread;
}
return -1;
}
void echo_srv(int connfd)
{
char recvbuf[1024];
// struct packet recvbuf;
int n;
while (1)
{
memset(recvbuf, 0, sizeof recvbuf);
int ret = readline(connfd, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
break;
}
fputs(recvbuf, stdout);
writen(connfd, recvbuf, strlen(recvbuf));
}
}
int main(int argc, char** argv) {
// 1. 創建套接字
int listenfd;
if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
ERR_EXIT("socket");
}
// 2. 分配套接字地址
struct sockaddr_in servaddr;
memset(&servaddr, 0, sizeof servaddr);
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
// inet_aton("127.0.0.1", &servaddr.sin_addr);
int on = 1;
// 確保time_wait狀態下同一端口仍可使用
if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0) {
ERR_EXIT("setsockopt");
}
// 3. 綁定套接字地址
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
ERR_EXIT("bind");
}
// 4. 等待連接請求狀態
if (listen(listenfd, SOMAXCONN) < 0) {
ERR_EXIT("listen");
}
// 5. 允許連接
struct sockaddr_in peeraddr;
socklen_t peerlen;
// 6. 數據交換
int nready;
int maxfd = listenfd;
int maxi = 0;//最大不空閒的位置爲0
int connfd;
int i;
struct pollfd client[2048];//struct pollfd結構體保存客戶端的信息
int ret;
int Max = 0;
for (i = 0; i < 2048; ++i)
{
client[i].fd = -1;//-1表示客戶端空閒
}
client[0].fd = listenfd;
client[0].events = POLLIN;//POLLIN:表示監聽套接字listenfd的可讀事件感興趣
while (1)
{
nready = poll(client, maxi+1, -1);//-1表示永遠等待,等到有事件才返回
//nready = select(maxfd + 1, &rset, NULL, NULL, NULL);
if (nready == -1)//爲-1表示出錯
{
if (errno == EINTR)
{
continue;
}
ERR_EXIT("select");
}
if (nready == 0)//爲0表示超時了
{
continue;
}
//if (FD_ISSET(listenfd, &rset))
if (client[0].revents & POLLIN)//監聽套接口是client[0]
{
peerlen = sizeof(peeraddr);
connfd = accept(listenfd, (struct sockaddr *)&peeraddr, &peerlen);
if (connfd == -1)
{
ERR_EXIT("accept");
}
for (i = 0; i < 2048; i++)
{
if (client[i].fd < 0)
{
client[i].fd = connfd;
if (i > maxi)
maxi = i;
break;
}
}
if (i == 2048)
{
fprintf(stderr, "too many clients\n");
exit(EXIT_FAILURE);
}
printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));
printf("port = %d\n", ntohs(peeraddr.sin_port));
client[i].events = POLLIN;//得到新的fd,就得將其添加進去
if (--nready <= 0)
{
continue;
}
}
//已連接套接口從1到maxi進行遍歷
for (i = 1; i < = maxi; ++i)
{
connfd = client[i].fd;
if (connfd == -1)
{
continue;
}
//if (FD_ISSET(connfd, &rset))
if (client[0].events & POLLIN)//表示發生了可讀事件
{
char recvbuf[1024] = {0};
ret = readline(connfd, recvbuf, 1024);
if (ret == -1)
{
ERR_EXIT("readline");
}
if (ret == 0)
{
printf("client close\n");
client[i].fd = -1;
//FD_CLR(connfd, &allset);
maxi--;
}
fputs(recvbuf, stdout);
//sleep(4);
writen(connfd, recvbuf, strlen(recvbuf));
if (--nready <= 0)
{
break;
}
};
}
}
// 7. 斷開連接
close(listenfd);
return 0;
}
-
測試:ulimit -n 2048
服務端:去掉0,1,2,監聽fd,爲2048-4=2044
客戶端:去掉0,1,2,爲2048-3=2045,即打開2045個fd
-
struct pollfd結構體中的events
(1)POLLIN與select的可讀事件相對應
(2)POLLPRI與select的緊急事件相對應
(3)POLLOUT與select的可寫事件相對應
- Makefile
.PHONY:clean all
CC=gcc
CFLAGS=-Wall -g
BIN=echosrv echocli nofile_limit contest pollsrv
all:$(BIN)
%.o:%.c
$(CC) $(CFLAGS) -c $< -o $@
clean:
rm -f *.o $(BIN)