实现简易Web服务器(c语言)
任务:
(1)实现服务器与客户端间的通信。
(2)可以实现HTTP请求中的GET方法。
(3)提供静态网页浏览功能,如可浏览:HTML页面,无格式文本,常见图像格式等。
(4)提供可以传递参数的动态网页浏览功能。
(5)可以检查一些明显错误报告给客户端,如:403无权访问,404找不到所请求的文件,501不支持相应方法等。
(6)在服务器端可输出HTTP响应的相关信息。
服务器端可配置参数,如:主目录,首页文件名,HTTP端口号等项。
套接字接口
套接字接口是一组函数,它们和Unix I/O函数结合起来,用以创建网络网络应用。
客户端和服务器使用socket函数来创建一个套接字描述符。
服务器端通过bind函数告诉内核将addr中的服务器套接字地址和套接字描述符sockfd联系起来;通过listen函数告诉内核,描述符是被服务器而不是客户端使用;通过accept函数来等待来自客户端的连接请求。
HTTP(超文本传输协议)被用在Web客户端和服务器之间的交互。当客户端需要服务时,它就向服务器发送一个HTTP请求说明需要的东西,服务器收到后解析,然后进行回应。本次实验,实现了应用最广泛的GET方法,并通过解析,判断是需要动态内容,还是静态内容,还是错误处理。
doit函数:处理一个HTTP事务。
首先读和解析请求行,获得方法,如果不是所支持的GET方法,就发送给它一个错误消息,并返回到主程序,主程序随后关闭连接并等待下一个连接请求。否则,读并且调用read_requesthdrs忽略报头中的其他信息。然后将URI解析为一个文件名和一个可能为空的CGI参数字符串,并且设置一个标志,表明请求的是静态内容还是动态内容。如果所需的文件不存在,就发送一个错误信息到客户端并返回。
parse_uri函数:解析URI并转为所需的文件名。
初始时设主目录为当前目录,可执行文件的目录为./cgi-bin。如果请求的是衣一个静态内容,就清楚CGI参数字符串,然后转为文件的路径名。如果请求的是动态内容,就提取出所有的CGI参数,然后转为文件的路径名。
serve_static函数:处理静态内容。
首先打开filename文件,用mmap函数将文件映射到一个虚拟内存空间,然后关闭这个文件,rio_writen函数复制到客户端已连接的描述符。这样前段就可以显示出所需的内容。
serve_dynamic函数:处理动态内容。
首先向客户端发送成功相应的内容,然后派生一个子进程,子进程用来自请求URI的CGI参数初始化QUERY_STRING环境变量,重定向标准输出到已连接文件描述符,然后执行CGI程序。
signal_r(SIGCHLD, sigchild_handler)函数:处理僵尸进程函数。
fork创建出很多进程,这些进程执行完以后就exit(0)然后发个信号通知主进程,exit函数退出后,进程的有关资源(打开的文件描述符,栈,寄存器,有关信息等)还没有释放掉,如果进程不被回收的话就会占用存储器资源这样的进程就称为僵尸进程。所以解决办法就是主进程使用一个信号处理函数,等待僵尸进程回收。
/*
* @filename: webServer.c
* @author: Flyuz
* @date: 2018年6月25日
* @description: 主程序
*/ #include "functionLib.h" void doit(int fd);
void serve_static(int fd, char *filename, int filesize);
int parse_uri(char *uri, char *filename, char *cgiargs);
void read_requesthdrs(rio_t *rp);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg,
char *longmsg);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs); int main(int argc, char **argv) {
int listenfd, connfd;
socklen_t clientlen; struct sockaddr_in clientaddr; if (argc != ) {
fprintf(stderr, "Usage: %s <port>\n", argv[]);
exit();
} printf("The web server has been started.....\n"); listenfd = Open_listenfd(argv[]);
/*
信号处理函数
用来处理僵尸进程
*/
signal_r(SIGCHLD, sigchild_handler); while () {
clientlen = sizeof(clientaddr);
if((connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen)) < )
{
if(errno == EINTR)
continue;
else
printf("Accept error...");
}
pid_t pid = Fork();
if(pid == )
{
doit(connfd);
Close(connfd);
exit();
}
else
{
Close(connfd);
}
}
} void doit(int fd) {
int is_static;
struct stat sbuf;
rio_t rio;
char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
char filename[MAXLINE]; //设置根目录
char cgiargs[MAXLINE]; //初始化 rio 结构
Rio_readinitb(&rio, fd);
//读取http请求行
Rio_readlineb(&rio, buf, MAXLINE);
//格式化存入 把该行拆分
sscanf(buf, "%s %s %s", method, uri, version); //只能处理GET请求,如果不是GET请求的话返回错误
if (strcasecmp(method, "GET")) {
clienterror(fd, method, "", "Not Implemented",
"Flyuz does not implement thid method");
return;
} //读取并忽略请求报头
read_requesthdrs(&rio); // memset(filename,0,sizeof(filename));
//解析 URI
is_static = parse_uri(uri, filename, cgiargs); //文件不存在
if (stat(filename, &sbuf) < ) {
clienterror(fd, filename, "", "Not found",
"Flyuz couldn't find this file");
return;
} if (is_static) { //服务静态内容
if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
clienterror(fd, filename, "", "Forbidden",
"Flyuz couldn't read the file");
return;
}
serve_static(fd, filename, sbuf.st_size);
} else { //服务动态内容
if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
clienterror(fd, filename, "", "Forbidden",
"Flyuz couldn't run the CGI program");
return;
}
serve_dynamic(fd, filename, cgiargs);
}
} /*
* 读取http 请求报头,无法使用请求报头的任何信息,读取之后忽略掉
*/
void read_requesthdrs(rio_t *rp) {
char buf[MAXLINE]; Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
//空文本行终止请求报头,碰到 空行 就结束 空行后面是内容实体
while (strcmp(buf, "\r\n")) {
Rio_readlineb(rp, buf, MAXLINE);
printf("%s", buf);
}
return;
} /*
* 解析URI 为 filename 和 CGI 参数
* 如果是动态内容返回0;静态内容返回 1
*/
int parse_uri(char *uri, char *filename, char *cgiargs) {
if (!strstr(uri,
"cgi-bin")) { //默认可执行文件都放在cgi-bin下,这里表示没有找到
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
/*
if(uri[strlen(uri)-1] == "/") //设置默认文件
strcat(filename, "index.html");
*/ return ; // static
} else { //动态内容
char *ptr = strchr(uri, '?');
if (ptr) { //有参数
strcpy(cgiargs, ptr + );
*ptr = '\0';
} else { //无参数
strcpy(cgiargs, "");
} strcpy(filename, ".");
strcat(filename, uri);
return ;
}
} /*
* 功能:发送一个HTTP响应,主体包含一个本地文件的内容
*/
void serve_static(int fd, char *filename, int filesize) {
int srcfd;
char *srcp, body[MAXBUF], filetype[MAXLINE]; /* 发送 响应行 和 响应报头 */
get_filetype(filename, filetype); sprintf(body, "HTTP/1.0 200 OK\r\n");
sprintf(body, "%sServer: Flyuz Web Server\r\n", body);
sprintf(body, "%sConnection:close\r\n", body);
sprintf(body, "%sContent-length: %d\r\n", body, filesize);
sprintf(body, "%sContent-type: %s\r\n\r\n", body, filetype);
Rio_writen(fd, body, strlen(body));
printf("Response headers: \n%s", body); /* 发送响应主体 即请求文件的内容 */
/* 只读方式发开filename文件,得到描述符*/
srcfd = Open(filename, O_RDONLY, );
/* 将srcfd 的前 filesize 个字节映射到一个从地址 srcp 开始的只读虚拟存储器区域
* 返回被映射区的指针 */
srcp = Mmap(, filesize, PROT_READ, MAP_PRIVATE, srcfd, );
/* 此后通过指针 srcp 操作,不需要这个描述符,所以关掉 */
Close(srcfd); Rio_writen(fd, srcp, filesize);
/* 释放映射的虚拟存储器区域 */
Munmap(srcp, filesize);
} /*
* 功能:从文件名得出文件的类型
*/
void get_filetype(char *filename, char *filetype) {
if (strstr(filename, ".html") || strstr(filename, ".php"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if (strstr(filename, ".ipg"))
strcpy(filetype, "image/jpeg");
else
strcpy(filetype, "text/plain");
} /*
* 功能:运行客户端请求的CGI程序
*/
void serve_dynamic(int fd, char *filename, char *cgiargs) {
char buf[MAXLINE];
char *emptylist[] = {NULL}; /* 发送响应行 和 响应报头 */
sprintf(buf, "HTTP/1.0 200 OK\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server Flyuz Web Server\r\n");
Rio_writen(fd, buf, strlen(buf)); /* 剩下的内容由CGI程序负责发送 */
if (Fork() == ) { //子进程
setenv("QUERY_STRING", cgiargs, );
Dup2(fd, STDOUT_FILENO);
Execve(filename, emptylist, __environ);
}
Wait(NULL);
/*
if(strstr(filename, ".php")) {
sprintf(response, "HTTP/1.1 200 OK\r\n");
sprintf(response, "%sServer: Pengge Web Server\r\n",response);
sprintf(response, "%sConnection: close\r\n",response);
sprintf(response, "%sContent-type: %s\r\n\r\n",response,filetype);
Write(connfd, response, strlen(response));
printf("Response headers:\n");
printf("%s\n",response);
php_cgi(filename, connfd,cgi_params);
Close(connfd);
exit(0);
//静态页面输出
}
*/
} /*
* 检查一些明显的错误,报告给客户端
*/
void clienterror(int fd, char *cause, char *errnum, char *shortmsg,
char *longmsg) {
char buf[MAXLINE], body[MAXBUF]; /* 构建HTTP response 响应主体 */
sprintf(body, "<html><title>Flyuz Error</title>");
sprintf(body,
"%s<body bgcolor="
"white"
">\r\n",
body);
sprintf(body, "%s<center><h1>%s: %s</h1></center>", body, errnum, shortmsg);
sprintf(body, "%s<center><h3>%s: %s</h3></center>", body, longmsg, cause);
sprintf(body, "%s<hr><center>The Flyuzy Web server</center>\r\n", body); /* 打印HTTP响应报文 */
sprintf(buf, "HTTP/1.0 %s %s", errnum, shortmsg);
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-type: text/html\r\n");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
Rio_writen(fd, buf, strlen(buf));
Rio_writen(fd, body, strlen(body));
}
主程序
/*
* @filename: functionLib.h
* @author: Flyuz
* @date: 2018年6月25日
* @description: 函数实现
*/ #ifndef ALL_H
#define ALL_H #include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <ctype.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/mman.h> #define RIO_BUFSIZE 8192
#define MAXLINE 8192 /* 一行最大长度 */
typedef struct {
int rio_fd; //内部读缓冲区描述符
int rio_cnt; //内部缓冲区中未读字节数
char *rio_bufp; //内部缓冲区中下一个未读的字节
char rio_buf[RIO_BUFSIZE]; //内部读缓冲区
}rio_t; //一个类型为rio_t的读缓冲区 #define MAXLINE 8192
#define MAXBUF 8192
#define LISTENQ 1024 typedef struct sockaddr SA;
typedef void handler_t(int);
void unix_error(char *msg); /* Process control wrappers */
pid_t Fork();
void Execve(const char *filename, char *const argv[], char *const envp[]);
pid_t Wait(int *status); /* Unix I/O */
int Open(const char *pathname, int flags, mode_t mode);
void Close(int fd);
int Dup2(int oldfd, int newfd); void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);
void Munmap(void *start, size_t length); /* RIO package */
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
void rio_readinitb(rio_t *rp, int fd);
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n); ssize_t Rio_readn(int fd, void *usrbuf, size_t n);
void Rio_writen(int fd, void *usrbuf, size_t n);
void Rio_readinitb(rio_t *rp, int fd);
ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n); int open_listenfd(char *port); int Open_listenfd(char *port); int Accept(int s, struct sockaddr *addr, socklen_t *addrlen); handler_t *signal_r(int signum, handler_t *handler);
void sigchild_handler(int sig); #endif
头文件
/*
* @filename: functionLib.c
* @author: Flyuz
* @date: 2018年6月25日
* @description: 辅助函数
*/ #include "functionLib.h" void unix_error(char *msg)
{
fprintf(stderr, "%s: %s\n",msg, strerror(errno));
exit();
} pid_t Fork()
{
pid_t pid;
pid = fork();
if(pid < )
unix_error("fork error");
return pid;
} void Execve(const char *filename, char *const argv[], char *const envp[])
{
if(execve(filename, argv,envp) < )
unix_error("Execve error");
} pid_t Wait(int *status)
{
pid_t pid; if((pid = wait(status)) < )
unix_error("Wait error");
return pid;
} int Open(const char *pathname, int flags, mode_t mode)
{
int rc; if((rc = open(pathname, flags, mode)) < )
unix_error("Open error");
return rc;
} int Accept(int s, struct sockaddr *addr, socklen_t *addrlen)
{
int rc; if((rc = accept(s, addr,addrlen)) < )
unix_error("Accept error");
return rc;
} void Close(int fd)
{
int rc; if((rc = close(fd)) < )
unix_error("Close error");
} int Dup2(int oldfd, int newfd)
{
int rc; if((rc = dup2(oldfd, newfd)) < )
unix_error("Dup2 error");
return rc;
} void *Mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset)
{
void *ptr; if((ptr = mmap(addr, len, prot, flags, fd, offset)) == ((void *)-))
unix_error("Mmap error");
return ptr;
} void Munmap(void *start, size_t length)
{
if(munmap(start, length) < )
unix_error("Munmap error");
}
/*
* 无缓冲输入函数
* 成功返回收入的字节数
* 若EOF返回0 ,出错返回-1
*/
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
char *bufp = usrbuf;
size_t nleft = n;
ssize_t nread;
while(nleft > ) {
if((nread = read(fd,bufp,nleft)) < ) {
if(errno == EINTR)
nread = ;
else
return -;
} else if( nread == )
break;
nleft -= nread;
bufp += nread;
}
return (n-nleft);
}
/*
* 无缓冲输出函数
* 成功返回输出的字节数,出错返回-1
*/
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
char *bufp = usrbuf;
size_t nleft = n;
ssize_t nwritten;
while(nleft > ) {
if((nwritten = write(fd, bufp, nleft)) <= ) {
if(errno == EINTR)
nwritten = ;
else
return -;
}
nleft -= nwritten;
bufp += nwritten;
}
return n;
} static ssize_t rio_read(rio_t *rp, char *usrbuf, size_t n)
{
while(rp -> rio_cnt <= ) {
rp -> rio_cnt = read(rp -> rio_fd, rp -> rio_buf,
sizeof(rp -> rio_buf));
if(rp -> rio_cnt < ) {
if(errno != EINTR)
return -;
} else if(rp -> rio_cnt == )
return ;
else
rp -> rio_bufp = rp -> rio_buf;
} int cnt = rp -> rio_cnt < n ? rp -> rio_cnt : n; memcpy(usrbuf, rp -> rio_bufp, cnt);
rp -> rio_bufp += cnt;
rp -> rio_cnt -= cnt;
return cnt;
}
// 初始化rio_t结构,创建一个空的读缓冲区
// 将fd和地址rp处的这个读缓冲区联系起来
void rio_readinitb(rio_t *rp, int fd)
{
rp -> rio_fd = fd;
rp -> rio_cnt = ;
rp -> rio_bufp = rp -> rio_buf;
} //带缓冲输入函数
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf; while(nleft > ) {
if((nread = rio_read(rp, bufp, nleft)) < )
return -;
else if (nread == )
break; nleft -= nread;
bufp += nread;
}
return (n-nleft);
}
/*
**带缓冲输入函数,每次输入一行
**从文件rp读出一个文本行(包括结尾的换行符),将它拷贝到usrbuf,并且用空字符来结束这个文本行
**最多读maxlen-1个字节,余下的一个留给结尾的空字符
**/
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf;
for(n = ; n<maxlen; n++) {
if((rc = rio_read(rp, &c, )) == ) {
*bufp++ = c;
if( c == '\n' ){
n++;
break;
}
} else if(rc == ) {
if( n == )
return ;
else
break;
} else
return -;
}
*bufp = '\0';
return n-;
} ssize_t Rio_readn(int fd, void *usrbuf, size_t n)
{
ssize_t nbytes;
if((nbytes = rio_readn(fd, usrbuf, n)) < )
unix_error("Rio_readn error"); return nbytes;
} void Rio_writen(int fd, void *usrbuf, size_t n)
{
if(rio_writen(fd, usrbuf, n) != n)
unix_error("Rio_writen error");
} void Rio_readinitb(rio_t *rp, int fd)
{
rio_readinitb(rp, fd);
} ssize_t Rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
ssize_t nbytes;
if((nbytes = rio_readlineb(rp, usrbuf, maxlen)) < )
unix_error("Rio_readlineb error");
return nbytes;
} ssize_t Rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
ssize_t nbytes;
if((nbytes = rio_readnb(rp, usrbuf, n)) < )
unix_error("Rio_readnb error");
return nbytes;
} /*打开并返回监听描述符*/
int open_listenfd(char *port)
{
int listenfd, optval = ;
struct sockaddr_in serveraddr; if((listenfd = socket(AF_INET, SOCK_STREAM, )) < ){
return -;
} if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval, sizeof(int)) < )
return -; bzero((char *)&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons((unsigned short)atoi(port)); if(bind(listenfd, (SA *)&serveraddr, sizeof(serveraddr)) < )
return -; if(listen(listenfd, LISTENQ) < )
return -; return listenfd;
} int Open_listenfd(char *port)
{
int rc;
if((rc = open_listenfd(port)) < )
unix_error("open_listenfd error");
return rc;
}
//信号处理
handler_t *signal_r(int signum, handler_t *handler)
{
struct sigaction action, old_action; action.sa_handler = handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART; if (sigaction(signum, &action, &old_action) < )
perror("Signal error");
return (old_action.sa_handler);
}
//排队 防止阻塞
void sigchild_handler(int sig)
{
int stat;
while(waitpid(-,&stat,WNOHANG)>);
return;
}
辅助函数
实现简易Web服务器(c语言)的更多相关文章
- Linux程序设计综合训练之简易Web服务器
1.功能需求: (1)学习网络套接字编程.HTPP协议.Web服务器等知识: (2)设计一简单Web服务器,提供静态网页浏览服务功能. 2.实现的功能: (1)C语言实现基于socket的Web服务器 ...
- [js高手之路]node js系列课程-创建简易web服务器与文件读写
web服务器至少有以下几个特点: 1.24小时不停止的工作,也就是说这个进程要常驻在内存中 2.24小时在某一端口监听,如: http://localhost:8080, www服务器默认端口80 3 ...
- 手写简易WEB服务器
今天我们来写一个类似于Tomcat的简易服务器.可供大家深入理解一下tomcat的工作原理,本文仅供新手参考,请各位大神指正!首先我们要准备的知识是: Socket编程 HTML HTTP协议 服务器 ...
- 自己实现一个简易web服务器
一个web服务器是网络应用中最基础的环节. 构建需要理解三个内容: 1.http协议 2.socket类 3.服务端实现原理 1.1 HTTP http请求 一般一个http请求包括以下三个部分: 1 ...
- 写一个简易web服务器、ASP.NET核心知识(4)--转载
第一次尝试(V1.0) 1.理论支持 这里主要要说的关于Socket方面的.主要是一个例子,关于Socket如何建立服务端程序的简单的代码. static void Main(string[] arg ...
- 简易web服务器
当通过Socket开发网络应用程序的时候,首先需要考虑所使用的网络类型,主要包括以下三个方面: 1)Socket类型,使用网络协议的类别,如IPv4的类型为PF_INET. 2)数据通信的类型,常见的 ...
- 【教程】手写简易web服务器
package com.littlepage.testjdbc; import java.io.BufferedReader; import java.io.FileReader; import ja ...
- 写一个简易web服务器、ASP.NET核心知识(4)
前言 昨天尝试了,基于对http协议的探究,我们用控制台写了一个简单的浏览器.尽管浏览器很low,但是对于http协议有个更好的理解. 说了上面这一段,诸位猜到我要干嘛了吗?(其实不用猜哈,标题里都有 ...
- 简易web服务器(java版)
//直接使用 ServerSocket 监听服务器端口,就能实现web服务器package ThreadPoolTest; import java.io.InputStream; import jav ...
随机推荐
- HDU5469(树的dfs)
Antonidas Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)Total S ...
- hihoCoder#1050(树中最长路)
时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上回说到,小Ho得到了一棵二叉树玩具,这个玩具是由小球和木棍连接起来的,而在拆拼它的过程中,小Ho发现他不仅仅可以拼凑成一 ...
- java代码流类。。程序怎么跟书上的结果不一样???
总结:这个程序很容易懂.的那是这个结果我觉得有问题啊..怎么“stop”后,输出的内容是输入过的呢? 应该是没有关系的呀,与输入的值是不同的....怎么书上运行的结果和我的不一样啊 package c ...
- RabbitMQ介绍
(一)RabbitMQ基本概念 RabbitMQ是流行的开源消息队列系统,用erlang语言开发.我曾经对这门语言挺有兴趣,学过一段时间,后来没坚持.RabbitMQ是 AMQP(高级消息队列协议)的 ...
- Oracle RMAN 学习:恢复
Oracle RMAN 学习:恢复 6 rman恢复 Rman中的恢复对应restore,recover Restore,数据修复,利用备份集的数据文件来替换已损坏的数据文件或将其恢复到另外一个位置, ...
- SetConsoleCtrlHandler演示
#include "stdafx.h"#include <Windows.h> static BOOL WINAPI Handler(DWORD cntrlEvent) ...
- 问题:table 可否实现对角线;结果:用div+css模拟表格对角线
首先声明: 这只是探讨一种CSS模拟表格对角线的用法,实际在工作中可能觉得这样做有点小题大作,这不是本主题讨论的重点.如果对此深以为然的朋友,请一笑过之... 有时在插入文档时,要用到表格对角线,常见 ...
- Linux基础命令-文件与目录
Linux基础命令-文件与目录 参考:<鸟哥linux私房菜>五-七章,17/12/5复习,18/01/15复习 文件权限 rwx421:用户,用户组,其他 umask查看默认权限:000 ...
- CentOS 6.3 下编译Nginx(笔记整理)
1. 安装关联程序 [root@localhost opt]# yum search gcc [root@localhost opt]# yum install gcc-c++ [root@local ...
- C#简单的图片合成及防止并发的办法
/// <summary> /// 合成图 /// </summary> private string ComposeCarBrandBadImage(AnonAttachme ...