Try   HackMD

DEMO CServer class, callback function, pthread, non-blocking connect simple and basic HTTP server (GET/POST)

Author: WhoAmI
email: kccddb@gmail.com
Date: 20230516
Copyright: CC BY-NC-SA

這是運用 OOP 觀念與 pthread, callback function 寫成的簡單 HTTP 1.0 server.

不了解 GET/POST 可自行 用 Wireshark 觀察

POST 在 message-body 中傳送
GET 在 HTTP Request 中

nonblocking connect: shttp.c
non-blocking connect 的重要用途之一就是選擇反映最快的 server 的方法之一
(您可自行修改使用)
ETIMEDOUT: Connection timed out
EAGAIN: Resource temporarily unavailable (may be the same value as EWOULDBLOCK) (POSIX.1-2001).
EINPROGRESS: The socket is nonblocking and the connection cannot be completed immediately. (UNIX domain sockets failed with EAGAIN instead.)

Partial write: writet
Partial read: readt
MAXCLIENTNO 20

BUG:

Modify default_accept and lock/unlock
Why?

注意 FIXME BUG (hint: select, high-speed)

請自行準備 index.html 檔案供實驗

servercpp.h

#define MAXCLIENTNO 20 typedef struct _CServercpp { int fd; unsigned short int port; int time_out; struct sockaddr client_addr[MAXCLIENTNO]; int (*on_read)(struct _CServercpp *pServer,char *buf,int len); int client_fd[MAXCLIENTNO]; int client_num; int active_fd; int (*on_accept)(struct _CServercpp *pServer); int fdmax; int (*on_callback)(void *items,int argc, char* argv[]); int (*callback[MAXCLIENTNO])(void *item,int argc, char* argv[]); void(*closefd)(struct _CServercpp *pServer,int fd); void(*close)(struct _CServercpp *pServer); pthread_mutex_t demomutex; }CServer; CServer *CServer_init(unsigned short int port); void CServer_exit(CServer *pServer); int writet(int fd,char *buf,int len,int msecs); int readt(int fd,char *buf,int len,int msecs); int read_send(int fd,char *fname);

servercpp.c

Notice that line 357:
Store it to local fd ASAP,
because pServer->active_fd may be changed! (if high-speed!)
How to fix it.

/* http simple server, OOP version pthread version gcc servercpp.c -o servercpp -lpthread . GET/POST Query string decode! callback function One can change the default callback function Author: WhoAmI,20191215 */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <pthread.h> #include <sys/stat.h> #include <fcntl.h> #include <time.h> #include "servercpp.h" //#define TEST #define debug000(x) #define debug001(x) x #define debug003(x) #define HTTPLEN 4000 #define HTTPheader "HTTP/1.1 200 OK\nContent-Type: text/html; charset=UTF-8\r\n\r\n" #define HTTPbody "<html><body><p style='color:Tomato;' >Welcome! HTTP header:</p><BR> %s <BR></body></html>" #define DEFAULTMSECS 50 static int callback(void *item,int argc, char* argv[]); static int bind_server(unsigned short int port) { int server_fd; int yes; struct sockaddr_in server_addr; yes=1; if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { return -1; } if (setsockopt(server_fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) { perror("setsockopt"); close(server_fd); return -1; } memset(&server_addr,0,sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = INADDR_ANY; server_addr.sin_port = htons(port); if (bind(server_fd,(struct sockaddr *)&server_addr,sizeof(server_addr))==-1) { perror("bind"); close(server_fd); return -1; } if (listen(server_fd,50) == -1) { perror("listen"); close(server_fd); } printf("[1] Create TCP Server on %d\n",port); return server_fd; } int writet(int fd,char *buf,int len,int msecs) { fd_set w_fds; struct timeval tv; int n; int m,j; tv.tv_sec = msecs/1000; tv.tv_usec = msecs*1000; FD_ZERO(&w_fds); FD_SET(fd,&w_fds); m=0; j=0; do { FD_SET(fd,&w_fds); if(msecs<=0) { n = select(fd+1, NULL, &w_fds, NULL, NULL ); } else { n = select(fd+1, NULL, &w_fds, NULL, &tv ); } switch(n) { case 0: errno = ETIMEDOUT; debug000(printf("Write timeout!\n")); return j; //20191215 break; case -1: return -1; default: if( FD_ISSET(fd,&w_fds)) { m=write(fd,buf+j,len-j); j=j+m; if(j==len)return j; } break; } } while(m>0); } int readt(int fd,char *buf,int len,int msecs) { fd_set r_fds; struct timeval tv; int n; int m,j; tv.tv_sec = msecs/1000; tv.tv_usec = msecs*1000; FD_ZERO(&r_fds); FD_SET(fd,&r_fds); m=0; j=0; do { FD_SET(fd,&r_fds); if(msecs<=0) { n = select(fd+1, &r_fds, NULL, NULL, NULL ); } { n = select(fd+1, &r_fds, NULL, NULL, &tv ); } debug000(printf("Read [%d] %d:%d usecs\n",n, tv.tv_sec,tv.tv_usec)); switch(n) { case 0: errno = ETIMEDOUT; debug003(printf("Read timeout!\n")); return j; //20191215 break; case -1: return -1; default: if( FD_ISSET(fd,&r_fds)) { m=read(fd,buf+j,len-j); j=j+m; if(j==len)return j; } break; } } while(m>0); return j; } static int default_read(CServer *pServer,char *buf,int len) { //default read op int n; debug000(printf("Enter Default Read...\n")); n=read( pServer->active_fd,buf,len); //for each connection return n; } static void closefd(CServer *pServer,int fd) { int i; for(i=0;i<MAXCLIENTNO;i++) { if(fd==pServer->client_fd[i]) { close(pServer->client_fd[i]); pServer->client_fd[i]=0; pServer->client_num--; pServer->active_fd=0; //set zero break; } } } unsigned int chkfile(char *fname) { unsigned int ssize; struct stat ssb; stat(fname, &ssb); ssize=(unsigned int)ssb.st_size; return ssize; } int read_send(int fd,char *fname) { int rfd; void *mem; unsigned int ssize; ssize=chkfile(fname); if(ssize>0) { rfd=open(fname, O_RDONLY); if(rfd>0) { mem=malloc(ssize); if(mem) { readt(rfd,mem,ssize,DEFAULTMSECS); writet(fd,mem,ssize,DEFAULTMSECS); free(mem); } close(rfd); } } return ssize; } /* * Based on ThomasH (Stackoverflow) * / static void urldecode2(char *dst, const char *src) { char a, b; while(*src) { if((*src == '%') && ((a = src[1]) && (b = src[2])) && (isxdigit(a) && isxdigit(b))) { if(a >= 'a') a -= 'a'-'A'; if(a >= 'A') a -= ('A' - 10); else a -= '0'; if(b >= 'a') b -= 'a'-'A'; if(b >= 'A') b -= ('A' - 10); else b -= '0'; *dst++ = 16*a+b; src+=3; } else if(*src == '+') { *dst++ = ' '; src++; } else { *dst++ = *src++; } } *dst++ = '\0'; } static int callback(void *item,int argc, char* argv[]) { int fd,n, *p; char buf[HTTPLEN]; char obuf[HTTPLEN]; char url[1000]; int len; int ispost; unsigned char *pget,*q,*query; CServer *pServer; pServer=( CServer *)item; len=HTTPLEN-1; ispost=0; //FIXME: HIGH-SPEED /* store it to local fd ASAP pServer->active_fd may be changed! (if high-speed!) */ fd=pServer->active_fd; debug000(printf("Enter Default Thread...fd=%d\n",fd)); do { n=readt(fd ,buf,len,DEFAULTMSECS); //for each connection if(n>0) { buf[n]=0; debug000(printf("%s\n",buf)); if(strstr(buf,"HTTP/")) { pget=strstr(buf,"GET "); if(pget) { pget=pget+5; } else { pget= strstr(buf,"POST "); if(pget) { printf("POST method\n"); pget=pget+6; ispost=1; query=strstr(pget,"\r\n\r\n"); query=query+2; printf("Query string=%s",query); } } for(q=pget;*q>' ' && *q!='?';q++) { } *q=0; if(ispost==0) { query=q+1; for(q=query;*q>' ' && *q!='\r';q++) { *q=0; } } url[0]=0; if(strlen(query)>0)urldecode2(url,query); printf("GET(POST) [%s] Query:[%s]\n",pget,url); write(fd,HTTPheader,strlen(HTTPheader)); if(read_send(fd,pget)<0) { sprintf(obuf,HTTPbody,buf); write(fd,obuf,strlen(obuf)); } else { break; } } } else { debug000(printf("Get ret=%d\n",n)); } } while(n>=0); } static void *pserver(void *pthreadid) { CServer *pServer; int fd; int i,n; int active_num; int (*on_callback)(void *item,int argc, char* argv[]); //FIXME: Un-stable for high-speed connection! debug001(printf("Thread start!\n")); pServer = (CServer *)pthreadid; pthread_mutex_lock(&pServer->demomutex); //idp = (int *) pthreadid; fd = pServer->active_fd; //FIXME: maybe in-correct! get temporary fd for each thread debug000(printf("active fd=%d\n",fd)); on_callback=pServer->on_callback; for(i=0;i<MAXCLIENTNO;i++) { if(fd==pServer->client_fd[i]) { if((pServer->callback[i])!=NULL)on_callback=pServer->callback[i]; //set on_callback for each thread. active_num=i; break; } } pthread_mutex_unlock(&pServer->demomutex); //server start! on_callback is called outside the lock! debug000( printf("star action active=%d\n",fd)); on_callback(pServer,0,NULL); pthread_mutex_lock(&pServer->demomutex); //maybe too fast! pServer->closefd(pServer,fd); pthread_mutex_unlock(&pServer->demomutex); debug001(printf("Thread fd=[%d] exists!\n",fd)); pthread_exit((void*) pthreadid); } static int default_accept(CServer *pServer) { //default read op int len; int fd; int i; int found; pthread_t child; found=0; len=sizeof(struct sockaddr); debug000(printf("Wait new connection\n")); fd=accept(pServer->fd ,(struct sockaddr *)&(pServer->client_addr) ,(socklen_t*)&len ); if ((fd <0)) { perror("accept"); return -1; } debug000(printf("Accept new connection %d\n",fd)); for(i=0;i<MAXCLIENTNO;i++) { if(pServer->client_fd[i]<=0) { //create one thread! pthread_create(&child, NULL, pserver, (void *) pServer); pthread_mutex_lock(&pServer->demomutex); pServer->active_fd=fd; //for each connection (temporary!) pServer->client_fd[i]=fd; pthread_mutex_unlock(&pServer->demomutex); pthread_detach(child); found=1; break; } } if(found) { pServer->client_num++; debug000(printf("New TCP connection...fd=%d, total=%d\n",fd,pServer->client_num)); return fd; } else { debug000(printf("Cannnt accept the TCP connection...[MAX=%d]\n",MAXCLIENTNO)); close(fd); } return -1; } //Generate CServer Object CServer *CServer_init(unsigned short int port) { CServer *pthis; pthis=(CServer *) malloc(sizeof(CServer)); if (pthis==NULL) { return NULL; } memset(pthis,0,sizeof(CServer)) ; pthis->fd=bind_server(port); pthis->port=port; pthis->on_read=default_read; pthis->on_accept=default_accept; pthis->on_callback=callback; pthis->closefd=closefd; pthis->demomutex=PTHREAD_MUTEX_INITIALIZER; printf("Create TCP Server on %d fd=%d\n",port, pthis->fd); return pthis; } //Destory CServer Object void CServer_exit(CServer *pServer) { int i; if(pServer) { for(i=0;i<MAXCLIENTNO;i++) { if(pServer->client_fd[i]>0) close(pServer->client_fd[i]); } close(pServer->fd); free( (void *)pServer); } } #ifdef TEST int main(int argc,char *argv[]) { CServer *p; char buf[80]; int n; p=CServer_init(9999); //p->on_callback=callback_echo; //change the default! if(p) { do { p->on_accept(p); } while(1); CServer_exit(p); } } #endif --------------------------------------------------------------------------- /* DEMO CServer class, callback function, pthread, simple and basic HTTP server (GET/POST)... gcc servercpp.c app.c -o app -lpthread Author: WhoAmI(cddb.tw@gmail.com) */ app.c #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <sys/time.h> #include <string.h> #include <ctype.h> #include <errno.h> #include <pthread.h> #include <sys/stat.h> #include <fcntl.h> #include <time.h> #include <sys/wait.h> #include "servercpp.h" #define ECHOMSECS 500 #define debug000(x) x #define debug001(x) static void sig_handler(int signo) { pid_t pid; int stat; switch(signo) { case SIGCHLD: printf("SIGCHLD=%d\n",signo); pid=waitpid(0,&stat,WNOHANG); break; case SIGINT: printf("SIGINT=%d\n",signo); break; case SIGALRM: printf("Got SIGALRM=%d, exit!\n",signo); exit( EXIT_FAILURE); break; case SIGPIPE: printf("Got SIGPIPE=%d, exit!\n",signo); //exit( EXIT_FAILURE); default: printf("signal=%d\n",signo); } return; } static int callback_echo(void *item,int argc, char* argv[]) { int fd,n, *p; char buf[256]; char obuf[256]; int len; CServer *pServer; pServer=( CServer *)item; len=256-1; /* store it to local fd ASAP pServer->active_fd may be changed! (if high-speed!) BUG! Cannot handle PUTTY close connection! */ fd=pServer->active_fd; debug000(printf("Enter Default Echo Thread...fd=%d\n",fd)); do { n=readt(fd ,buf,len,ECHOMSECS); //for each connection if(n>0) { buf[n]=0; printf("%s\n",buf); debug000( writet(fd,buf,n,ECHOMSECS)); } else { debug001( printf("Get ret=%d,errno=%d\n",n,errno)); if(n<0)break; } } while(n>=0); //for PUTTY } int main(int argc,char *argv[]) { CServer *p; char buf[80]; int n; int port; port=9999; if(argc>1)port=atoi(argv[1]); signal (SIGPIPE, sig_handler); p=CServer_init(port); //Replace default on_callback by callback_echo if(port==9998){ printf("Demo simple ECHO server(port=%d)!\n",port); p->on_callback=callback_echo; //change the default! }else{ printf("Demo simple HTTP server(port=%d)!Try http://127.0.0.1:%d/index.html\n",port,port); } if(p) { do { n=p->on_accept(p); } while(n>0); CServer_exit(p); } exit(EXIT_SUCCESS); }

non-blocking connect

shttp.c

這裡 判斷 EINPROGRESS

請看 connect man page

還有 ERROR 沒處理到
例如:
ECONNREFUSED, ETIMEDOUT,


/* Simple send http header non-blocking connect Author: WhoAmI(cddb.tw@gmail.com) DATE: 2019/12/19 DNS 相關請自行參考 Beej's Guide to Network Programming You may need getaddrinfo (Linux man 3), please STUDY DNS and the following four files /etc/host.conf resolver configuration file /etc/hosts host database file /etc/nsswitch.conf name service switch configuration /etc/gai.conf (configuration file) */ #include <stdio.h> #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> /* superset of previous */ #include <string.h> #include <stdlib.h> #include <unistd.h> #include <signal.h> #include <arpa/inet.h> #include <sys/stat.h> #include <fcntl.h> //errno #include <errno.h> /* HTTP client GET /index.html?ip=1234567&name=jone HTTP/1.1 Host: 127.0.0.1:9999 */ int setnonblock (int fd) { int flags; int n; flags = fcntl(fd, F_GETFL); //O_NONBLOCK=0x4000 fcntl(fd, F_SETFL, flags | O_NONBLOCK); n= fcntl(fd, F_GETFL) & O_NONBLOCK; return n; } int setblock (int fd) { int flags; int n; flags = fcntl(fd, F_GETFL); fcntl(fd, F_SETFL, flags & ~O_NONBLOCK); n= fcntl(fd, F_GETFL) & O_NONBLOCK; return n; } #define debugerr(x) x int connecttcp(char *ip,int port,int msecs){ //EAGAIN, //EINTR:The system call was interrupted by a signal that was caught; int sockfd; struct sockaddr_in server_addr; socklen_t addrlen; int ret; int n; fd_set rfds,wfds; struct timeval tv; //int msecs=1000; int retval; if((sockfd = socket(PF_INET, SOCK_STREAM, 0))<0){ perror("socket()"); exit(EXIT_FAILURE); } setnonblock(sockfd); memset(&server_addr,0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); server_addr.sin_addr.s_addr = inet_addr(ip); addrlen=sizeof(server_addr); n=1; do{ FD_ZERO(&wfds); FD_SET(sockfd, &wfds); tv.tv_sec = msecs/1000; tv.tv_usec =(tv.tv_sec*1000-msecs)*1000; retval = select(sockfd+1,NULL,&wfds, NULL, &tv);//for writing //printf("retval=%d",retval); errno=0; ret=connect(sockfd, (struct sockaddr *)&server_addr,addrlen); if(ret<0){ //sleep(1); perror("connect"); debugerr(fprintf(stderr,"errno=%d, EINPROGRESS=%d,EISCONN=%d\n",errno,EINPROGRESS,EISCONN)); //connect: Operation now in progress //retval=1errno=119, 119 //EINPROGRESS //EISCONN =127 //Need select if(errno == EISCONN)break; if (errno == EINTR){ continue; } if (errno == EAGAIN){ continue; } }else break; }while(n>0); setblock(sockfd); debugerr(fprintf(stderr,"Connect to %s port %d, fd=%d\n",ip,port,sockfd)); return sockfd; } int main(int argc,char *argv[]) { int fd; char buf[128]="GET /demox.html?ip=31.31.520.99&name=Sugar31 HTTP/1.1\r\nHost: 127.0.0.1:9999\r\n\r\n\r\n"; char ret[1204]; int n; char *ip="127.0.0.1"; int port=9999; int msec=1000; printf("Connect to %s:%d [msec:%d] and\nSend:\n%s\n",ip,port,msec,buf); fd=connecttcp(ip,port,msec); setnonblock(fd); setblock(fd); if(fd>0){ write(fd,buf,strlen(buf)); do{ n=read(fd,ret,1000); if(n>0){ ret[n]=0; printf("--->%s\n",ret); } }while(n>0); //sleep(10); close(fd); exit(EXIT_SUCCESS); } exit(EXIT_FAILURE ); }