最近准备学习一下epoll,就写了一个小程序做测试。把中间遇到的问题记录下来,请大家多多指正。
代码很简单:
一个服务端,接收请求,将接收到的内容再返回给客户端。
一个客户端,多线程的发送请求再接收。
server.cc
#include <stdio.h>
#include <strings.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFSIZE 1024
#define MAXEVENTS 1024
typedef struct cusdata{
char buf[BUFSIZE];
int size;
}cusdata;
cusdata bufdata[MAXEVENTS];
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
void setreuse(int sock){
int flag=1;
if( setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flag,sizeof(int))==-1)
{
perror("setsockopt");
exit(0);
}
}
void test(){
int accepted = 0;
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1){
perror("listen_fd");
exit(0);
}
setnonblocking(listenfd);
setreuse(listenfd);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="127.0.0.1";
int port=9988;
inet_aton(local_addr,&(serveraddr.sin_addr));
serveraddr.sin_port=htons(port);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, 1000);
int epollfd = epoll_create(1024);
if(epollfd == -1){
perror("epoll_create");
exit(0);
}
struct epoll_event ev, events[MAXEVENTS];
ev.events = EPOLLIN;
ev.data.fd = listenfd;
int ret = epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
if(ret == -1){
perror("epoll_ctl");
exit(0);
}
while(1){
// printf("to wait event\n");
int nfds = epoll_wait(epollfd, events, MAXEVENTS, 100);
if(nfds == -1){
perror(NULL);
}
for(int i=0; i <nfds; i++){
if(events[i].data.fd == listenfd){
int addrlen = sizeof(clientaddr);
int newfd = accept(listenfd, (sockaddr*)&clientaddr, (socklen_t*)&addrlen);
if(newfd==-1){
perror("accept");
exit(0);
}
char *addrstr = inet_ntoa(clientaddr.sin_addr);
// printf("accept client from addrstr:%s newfd:%d\n", addrstr, newfd);
accepted++;
printf("accepted:%d\n", accepted);
ev.data.fd = newfd;
ev.events = EPOLLIN|EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_ADD, newfd, &ev);
}
else if(events[i].events & EPOLLIN){
int curfd = events[i].data.fd;
// printf("---------EPOLLIN------------\n");
memset(bufdata[curfd].buf, 0, BUFSIZE);
int n = read(curfd, bufdata[curfd].buf, BUFSIZE);
if(n<=0){
// printf("close %d\n", curfd);
close(curfd);
}
else{
// printf("recv fd:%d %s\n",curfd, bufdata[curfd].buf);
bufdata[curfd].size = n;
ev.data.fd = curfd;
ev.events = EPOLLOUT | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_MOD, curfd, &ev);
}
}
else if(events[i].events & EPOLLOUT){
// printf("---------EPOLLOUT------------\n");
int curfd = events[i].data.fd;
write(curfd, bufdata[curfd].buf, bufdata[curfd].size);
// printf("send fd:%d %s\n",curfd, bufdata[curfd].buf);
ev.data.fd = curfd;
ev.events = EPOLLIN | EPOLLET;
epoll_ctl(epollfd, EPOLL_CTL_MOD, curfd, &ev);
}
}
}
}
int main(){
test();
}
server.cc里遇到的陷阱:
epoll_data结构如下:
typedef unionepoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
仔细看epoll_data为一个union,不能给ptr和fd同时赋值,否则会覆盖。
client.cc
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFSIZE 10
#define SENDTIME 100000
#define THREAD_NUM 100
pthread_attr_t attr;
void* worker(void* args){
int* ptid = (int*)args;
int tid = *ptid;
char *local_addr="127.0.0.1";
int port=9988;
for(int i=0; i<SENDTIME; i++){
struct sockaddr_in serveraddr;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1){
perror("socket");
exit(0);
}
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
inet_aton(local_addr,&(serveraddr.sin_addr));
serveraddr.sin_port=htons(port);
int ret = connect(fd, (sockaddr*)&serveraddr, sizeof(serveraddr));
if(ret<0){
perror("connect");
exit(0);
}
char buf[BUFSIZE];
char msg[BUFSIZE];
memset(msg, 0, BUFSIZE);
int len = snprintf(buf, BUFSIZE, "%d-%d", tid, i);
ret = write(fd, buf, len);
printf("send %s\n", buf);
if(ret != len){
printf("write error tid:%d time:%d write_len:%d expect_len:%d\n", tid, i, ret, len);
perror(NULL);
exit(0);
}
ret = read(fd, msg, BUFSIZE);
printf("recv %s\n", msg);
if(ret != len){
printf("write error tid:%d time:%d read_len:%d expect_len:%d\n", tid, i, ret, len);
perror(NULL);
exit(0);
}
close(fd);
}
}
void test(){
pthread_t pids[THREAD_NUM];
int args[THREAD_NUM];
for(int i = 0; i<THREAD_NUM; i++){
args[i]=i;
ret=pthread_create(&(pids[i]), NULL, worker, (void*)&(args[i]));
if(ret!=0){
perror("pthread_create");
exit(0);
}
}
for(int i=0; i <THREAD_NUM; i++){
pthread_join(pids[i], NULL);
}
}
int main(){
test();
}
client.cc遇到问题:
1 当连接次数达到28063时出现Cannot assign requested address。google了一下发现是连接次数太多导致端口用光,需要将TIME_WAIT状态的端口重复利用才行。所以将listenfd设置为SO_REUSEADDR,但是发现无用,连接次数到了20000多,仍然会出现这个问题。还想请懂的人指导一下。
2当线程创建到300多个时,会出现pthread_create: Cannot allocate memory。google一下得知因为每个线程的栈在10M,在32位系统上,用户进程内存为3G,导致内存不足。解决方法:
1)在子线程里调用pthread_detach(),这样已经完成的子线程会自动释放内存,不用等到join的时候再释放。
遇到问题:主线程里不用再join等待子线程,但是主线程创建完子线程直接退出。导致部分子线程还没执行完,整个进程就退出。不知有没有既可以将子线程设置为detach(为了及时释放内存),又可以在主线程等待子线程完成的方法。
2)修改子线程的栈大小,这样就可以创建更多的子线程。
client.cc
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFSIZE 10
#define SENDTIME 1
#define THREAD_NUM 500
pthread_attr_t attr;
void* worker(void* args){
// pthread_detach(pthread_self());
// size_t size=0;
// pthread_attr_getstacksize(&attr, &size);
// printf("stack size:%d\n", size);
int* ptid = (int*)args;
int tid = *ptid;
char *local_addr="127.0.0.1";
int port=9988;
for(int i=0; i<SENDTIME; i++){
struct sockaddr_in serveraddr;
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1){
perror("socket");
exit(0);
}
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
inet_aton(local_addr,&(serveraddr.sin_addr));
serveraddr.sin_port=htons(port);
int ret = connect(fd, (sockaddr*)&serveraddr, sizeof(serveraddr));
if(ret<0){
perror("connect");
exit(0);
}
char buf[BUFSIZE];
char msg[BUFSIZE];
memset(msg, 0, BUFSIZE);
int len = snprintf(buf, BUFSIZE, "%d-%d", tid, i);
ret = write(fd, buf, len);
printf("send %s\n", buf);
if(ret != len){
printf("write error tid:%d time:%d write_len:%d expect_len:%d\n", tid, i, ret, len);
perror(NULL);
exit(0);
}
ret = read(fd, msg, BUFSIZE);
printf("recv %s\n", msg);
if(ret != len){
printf("write error tid:%d time:%d read_len:%d expect_len:%d\n", tid, i, ret, len);
perror(NULL);
exit(0);
}
close(fd);
}
}
void test(){
int ret=pthread_attr_init(&attr);
if(ret!=0){
perror("pthread_attr_init");
exit(0);
}
ret = pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN+1024);
if(ret!=0){
perror("pthread_attr_setstacksize");
exit(0);
}
pthread_t pids[THREAD_NUM];
int args[THREAD_NUM];
for(int i = 0; i<THREAD_NUM; i++){
args[i]=i;
ret=pthread_create(&(pids[i]), NULL, worker, (void*)&(args[i]));
if(ret!=0){
perror("pthread_create");
exit(0);
}
}
for(int i=0; i <THREAD_NUM; i++){
pthread_join(pids[i], NULL);
}
}
int main(){
test();
}
将子线程是栈设置小后,就可以创建更多线程。
总经一下不明白的问题,还请了解的人指导:
1 当连接次数达到28063时出现Cannot assign requested address。google了一下发现是连接次数太多导致端口用光,需要将TIME_WAIT状态的端口重复利用才行。所以将listenfd设置为SO_REUSEADDR,但是发现无用,连接次数到了20000多,仍然会出现这个问题。
2 子线程里调用pthread_detach(),主线程里不用再join等待子线程,但是主线程创建完子线程直接退出。导致部分子线程还没执行完,整个进程就退出。不知有没有既可以将子线程设置为detach(为了及时释放内存),又可以在主线程等待子线程完成的方法。
发现最后要解决的问题都是与系统紧密相关的,关于epoll的反而少了,准备下一步再对epoll做更多学习,到时候再分享给大家。