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);
}
shttp.c
這裡 判斷 EINPROGRESS
還有 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 );
}
Authors: CrazyDog, CrazyMonkeyemail: kccddb@gmail.comDate: 20230222
Mar 14, 2025Author: \mathcal{CrazyDog}, Ph.D. in Electrical Engineering, NCTUemail: kccddb@gmail.comDate: 20230910
Nov 4, 2024Author: WhoAmI Date: 20230523 email:kccddb@gmail.com Copyright: CC BY-NC-SA 《荀子‧勸學》不積跬步,無以至千里;不積小流,無以成江海。 I. Mutex variables MUST be declared with type pthread_mutex_t, and must be initialized before they can be used. II. 了解 pthread_mutex_lock / pthread_mutex_unlock 與可能問題和代價 III. 程式執行效率與startvation 等問題
Jul 11, 2024Author: WhoAmIemail: kccddb@gmail.com
Dec 6, 2023or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up