入职公司的试用期小内容,这个是多进程的版本,其中支持了图片,网页,大致结构比较清晰,拿出来跟大家分享下。
server.c源码如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#define SERVER_NAME "IAMCAP-SERVER"
#define PROTOCOL "HTTP/1.1"
#define REQUESTLEN 4096
#define RESPONSELEN 1024000
#define TIMELEN 55
#define TEMPLEN 100
#define BUFFERLEN 1024
#define HOMEPATH "/Users/iamcap/http/"
static char* add_header(int , char *, char *, off_t , char *, int );
void parsedata(char*,int);
int main(int argc, char **argv){
if(argc <=2){
printf("usage: %s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
//初始化通信结构体
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
//端口重复利用
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert( sock >= 0);
int opt = 1;
setsockopt(sock, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));
int ret = bind(sock, (struct sockaddr*)&address, sizeof(address));
assert(ret != -1);
printf("listening ...\n");
ret = listen(sock, 5);
assert(ret != -1);
while(1){
//保存客户端的信息
struct sockaddr_in client;
socklen_t client_addrlength = sizeof(client);
int connectfd = accept(sock,(struct sockaddr*)&client,&client_addrlength);
if(connectfd < 0){
printf("errno is: %d\n", errno);
}else{
//创建worker进程
pid_t pid = fork();
if(-1 == pid){
printf("wrong!!!\n");
}else if(0 == pid){
//在子进程中
char remote[INET_ADDRSTRLEN];
memset(remote,'\0',INET_ADDRSTRLEN);
printf("\nconnected with ip: %s and port: %d\n", inet_ntoa(client.sin_addr),ntohs(client.sin_port));
char recvdata[REQUESTLEN];
memset(recvdata,'\0',REQUESTLEN);
//从建立的socket中接收浏览器的请求信息
int ret = recv(connectfd, recvdata,REQUESTLEN-1, 0);
printf("\n%s\n",recvdata);
//处理接收到的信息
parsedata(recvdata,connectfd);
}else{
//在父进程中
close(connectfd);
}
}
}
close(sock);
return 0;
}
// send header to the browser
static char* add_header(int status, char *title, char *mime_type, off_t length, char *buffer , int buffersize)
{
char temp[TEMPLEN];
memset(buffer, 0, sizeof(buffersize));
sprintf(temp, "%s %d %s\r\n", PROTOCOL, status, title);
//将所有的头部数据格式都按照协议的标准填充到buffer
strcat(buffer, temp);
sprintf(temp, "Server: %s\r\n", SERVER_NAME);
strcat(buffer, temp);
sprintf(temp, "Connection: Keep-Alive\r\n");
strcat(buffer, temp);
sprintf(temp, "Content-Type: %s\r\n", mime_type);
strcat(buffer, temp);
sprintf(temp, "Content-Length: %d\r\n", (int)length);
strcat(buffer, temp);
sprintf(temp, "Content-Language: zh-CN\r\n");
strcat(buffer, temp);
strcat(buffer, "\r\n");
return buffer;
}
static char* get_mime_type(char *filename)
{
char *postfix;
postfix = strrchr(filename, '.');
if (!strcasecmp(postfix, ".html") || !strcasecmp(postfix, ".htm")){
return "text/html; charset=UTF-8";
}else if (!strcasecmp(postfix, ".jpg") || !strcasecmp(postfix, ".jpeg")){
return "image/jpeg";
}else{
return "text/plain; charset=UTF-8";
}
}
void parsedata(char *recvdata, int connectfd){
//切换到主页目录
if(chdir(HOMEPATH)<0){
printf("chdir error\n");
exit(0);
}
char method[BUFFERLEN];
char path[BUFFERLEN];
char protocol[BUFFERLEN];
memset(method,'\0',BUFFERLEN);
memset(path,'\0',BUFFERLEN);
memset(protocol,'\0',BUFFERLEN);
sscanf(recvdata, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
//打开文件或文件夹
struct stat property;
stat(path,&property);
if(S_ISDIR(property.st_mode)){ //如果是文件夹
if(0 == strcmp(path,"/")){ //是主页目录
//返回index.html
FILE *fp;
if(fp = fopen("index.html","r")){
char head[BUFFERLEN];
memset(head,'\0',BUFFERLEN);
stat(path+1,&property);
stat("index.html",&property);
add_header(200, "OK", "text/html", property.st_size, head, BUFFERLEN);
char temp[RESPONSELEN];
memset(temp,'\0',RESPONSELEN);
strcpy(temp,head);
fread(temp+strlen(head),1,property.st_size,fp);
send(connectfd, temp, strlen(temp),0);
//将应答的html报文回发过去,浏览器可能还会发送图片显示请求报文
close(connectfd);
}
}else{ //其他目录,直接返回
exit(0);
}
}else{ //如果是文件
if(0 == strcasecmp(method,"GET")){
FILE *fp;
if(fp = fopen(path+1,"rb")){
char head[BUFFERLEN];
memset(head,'\0',BUFFERLEN);
stat(path+1,&property);
add_header(200, "OK", get_mime_type((path + 1)), property.st_size, head, BUFFERLEN);
char temp[RESPONSELEN];
memset(temp,'\0',RESPONSELEN);
strcpy(temp,head);
fread(temp+strlen(head),1,property.st_size,fp);
send(connectfd, temp, strlen(head)+property.st_size,0);
close(connectfd);
}
}else if(0 == strcasecmp(method,"POST")){
}else if(0 == strcasecmp(method,"HEAD")){
}else if(0 == strcasecmp(method,"PUT")){
}else{
exit(0);
}
}
}
index.html代码如下:
<html><body><H1>this is a page</H1><img src="mypic.jpg" /></body></html>
mypic.jpg 是 /Users/iamcap/http/路径下的一张图片。
这里需要注意的有以下几点:
1. http的本身就是一个文本文件,你需要知道http协议的格式,可以http://blog.csdn.net/gueter/article/details/1524447
2. 在显示图片的处理过程中,记得用fread函数,不要企图使用strlen函数来获取长度,可以借助struct stat { } 和 HTTP头部共同拼接出所发内容的长度
3. 浏览器在浏览的过程中,可能会发送GET请求favicon.ico,这个其实就是页面显示的那个小图标,可以不用管。
4. 在使用数组之前,尽量用memset进行初始化,免得垃圾收据会影响内容发送。