用 IO 多路复用实现 nginx 动态资源代理 (C/Java/Golang)
成果展现
代理 HTML
代理图片
留神, 动态资源代理基于 HTTP, 能够理解上一篇文章: 几十行代码应用 TCP 简略实现 HTTP(C/Golang/Java) https://blog.csdn.net/jarvan5…
Java
public static void main(String[] args) throws IOException {
String basePath = "D:\\Enviroment\\java\\java_static_server";
int port = 9000;
ServerSocket serverSocket = new ServerSocket(port);
System.out.println("port:"+port);
while (true) {Socket socket = serverSocket.accept();
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
String reqUrl = getReqUrl(inputStream);
String[] split = reqUrl.split("/");
//1.header
writeHeader(outputStream,split[split.length-1]);
//2.data
Path path = Paths.get(basePath,reqUrl);
try {Files.copy(path, outputStream);
} catch (IOException ioException) {System.out.println("FileNotFound:"+path);
continue;
}
outputStream.close();
socket.close();}
}
private static String getReqUrl(InputStream inputStream) throws IOException {BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line = reader.readLine();
String[] strs = line.split(" ");
//GET /index.html HTTP/1.1...
return strs[1];
}
private static void writeHeader(OutputStream outputStream,String contentType) throws IOException {StringBuilder respBuf = new StringBuilder();
respBuf.append("HTTP/1.1 200 OK\n");
respBuf.append("Content-Type:");
respBuf.append(contentType);
respBuf.append(";charset=UTF-8\n\n");
outputStream.write(respBuf.toString().getBytes());
outputStream.flush();}
Java IO 多路复用
public static void main(String[] args) throws IOException {
String basePath = "D:\\Enviroment\\java\\java_static_server";
int port = 9001;
System.out.println("port:" + port);
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
serverSocketChannel.socket().bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {if (selector.select(1000) == 0) {System.out.print(".");
continue;
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = selectionKeys.iterator();
while (keyIterator.hasNext()) {
try {SelectionKey key = keyIterator.next();
keyIterator.remove();
if (key.isAcceptable() && !key.isReadable()) {SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if (key.isReadable()) {SocketChannel channel = (SocketChannel) key.channel();
byte[] readBuf = new byte[1024];
channel.read(ByteBuffer.wrap(readBuf));
String readString = new String(readBuf);
String[] splits = readString.split(" ");
String reqUrl = splits[1];
String[] po = reqUrl.split("/");
System.out.println("客户端发送数据:" + reqUrl);
//1.header
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 200 OK\n");
sb.append("Content-Type:");
sb.append(po[po.length - 1]);
sb.append(";charset=UTF-8\n\n");
channel.write(ByteBuffer.wrap(sb.toString().getBytes()));
//2.data
FileInputStream fi;
fi = new FileInputStream(basePath + reqUrl);
int len = 0;
byte[] buf = new byte[1024];
while ((len = fi.read(buf)) > 0) {channel.write(ByteBuffer.wrap(buf));
}
key.cancel();
channel.close();}
}catch(Exception e){continue;}
}
}
}
- 应用前记得批改代理的门路
basePath
为你的动态资源地址 - 残缺代码仓库 https://github.com/dengjiawen8955/java_static_server
C
#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include "stdlib.h"
#define PORT 9000
#define BASE_PATH "/root/CLionProjects/c_static_server"
#define BUF_SIZE 1024
#define NOT_FOUND_DATA "NOT_FOUND"
char buff[BUF_SIZE];
char *get_param_form_request(char *request){char *s = strstr(request, "/");
char *s2 = strstr(s, " ");
int size = strlen(s) - strlen(s2);
char *result = malloc(sizeof(char*) * size);
strncpy(result, s, size);
return result;
}
char *str_join(char* str1,char* str2){char *result = malloc(sizeof(char *) * (strlen(str1) + strlen(str2)));
strcat(result, str1);
strcat(result, str2);
return result;
}
int send_header_with_content_type(int client_socket_fd,char *content_type){sprintf(buff, "HTTP/1.1 200 OK\r\nContent-Type:%s:charset=UTF-8\r\n\r\n", content_type);
int send_n = send(client_socket_fd, buff, strlen(buff), 0);
memset(buff, 0, strlen(buff));
if (send_n < 0) {return -1;}
return 0;
}
//socket -> bind -> listen
int main(int argc, char *argv[]) {int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {perror("socket error\n");
return -1;
}
struct sockaddr_in lst_addr;
lst_addr.sin_family = AF_INET;
lst_addr.sin_port = htons(PORT);
lst_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(sockfd, (struct sockaddr *) &lst_addr, len);
if (ret < 0) {perror("bind error\n");
return -1;
}
if (listen(sockfd, 5) < 0) {perror("listen error\n");
return -1;
}
while (1) {
struct sockaddr_in cli_addr;
int client_accept_fd = accept(sockfd, (struct sockaddr *) &cli_addr, &len);
if (client_accept_fd < 0) {perror("accept error\n");
continue;
}
int ret = recv(client_accept_fd, buff, BUF_SIZE, 0);
if (ret > 0) {printf("req:%s\n", buff);
}
char *param = get_param_form_request(buff);
memset(buff, 0x00, BUF_SIZE);
char *path = str_join(BASE_PATH, param);
if (access(path, F_OK|R_OK)==0) {
//header.
char *post_fix = strstr(param, ".");
post_fix++;
send_header_with_content_type(client_accept_fd, post_fix);
//data.
FILE *p = fopen(path, "rb");
int read2_n;
while ((read2_n = fread(buff, sizeof(char), BUF_SIZE, p)) != 0) {send(client_accept_fd, buff, read2_n, 0);
memset(buff, 0, read2_n);
}
fclose(p);
} else{
//header
send_header_with_content_type(client_accept_fd, "html");
//data
send(client_accept_fd, NOT_FOUND_DATA, strlen(NOT_FOUND_DATA), 0);
}
send(client_accept_fd, buff, strlen(buff), 0);
close(client_accept_fd);
}
}
C IO 多路复用
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define EPOLL_MAX_NUM 2048
#define BUF_SIZE 4096
#define LISTEN_N 10
char buffer[BUF_SIZE];
char *get_param_form_request(char *request){char *s = strstr(request, "/");
char *s2 = strstr(s, " ");
int size = strlen(s) - strlen(s2);
char *result = malloc(sizeof(char*) * size);
strncpy(result, s, size);
return result;
}
char *str_join(char* str1,char* str2){char *result = malloc(sizeof(char *) * (strlen(str1) + strlen(str2)));
strcat(result, str1);
strcat(result, str2);
return result;
}
int send_header_with_content_type(int client_socket_fd,char *content_type){sprintf(buffer, "HTTP/1.1 200 OK\r\nContent-Type:%s:charset=UTF-8\r\n\r\n", content_type);
int send_n = send(client_socket_fd, buffer, strlen(buffer), 0);
memset(buffer, 0, strlen(buffer));
if (send_n < 0) {return -1;}
return 0;
}
int main(int argc, char **argv) {
char *base_path = "/root/CLionProjects/c_static_server";
int port = 9000;
printf("base_path:%s\nport:%d\n", base_path, port);
int listen_fd = 0;
int client_fd = 0;
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
socklen_t client_len;
int epfd = 0;
struct epoll_event event, *my_events;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
server_addr.sin_port = htons(port);
int bind_i = bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (bind_i == -1) {printf("bind error\n");
return -1;
}
int listen_i = listen(listen_fd, LISTEN_N);
if (listen_i == -1) {printf("listen error\n");
return -1;
}
epfd = epoll_create(EPOLL_MAX_NUM);
if (epfd < 0) {perror("epoll create");
goto END;
}
event.events = EPOLLIN;
event.data.fd = listen_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &event) < 0) {perror("epoll ctl add listen_fd");
goto END;
}
my_events = malloc(sizeof(struct epoll_event) * EPOLL_MAX_NUM);
while (1) {int active_fds_cnt = epoll_wait(epfd, my_events, EPOLL_MAX_NUM, -1);
int i = 0;
for (i = 0; i < active_fds_cnt; i++) {if (my_events[i].data.fd == listen_fd) {client_fd = accept(listen_fd, (struct sockaddr *) &client_addr, &client_len);
if (client_fd < 0) {perror("accept");
continue;
}
char ip[20];
printf("new connection[%s:%d]\n", inet_ntop(AF_INET, &client_addr.sin_addr, ip, sizeof(ip)),ntohs(client_addr.sin_port));
event.events = EPOLLIN | EPOLLET;
event.data.fd = client_fd;
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event);
} else if (my_events[i].events & EPOLLIN) {client_fd = my_events[i].data.fd;
int n = read(client_fd, buffer, BUF_SIZE);
if (n < 0) {perror("read");
continue;
} else if (n == 0) {epoll_ctl(epfd, EPOLL_CTL_DEL, client_fd, &event);
close(client_fd);
} else {printf("[read]: %s\n", buffer);
char *param = get_param_form_request(buffer);
memset(buffer, 0, BUF_SIZE);
char *path = str_join(base_path, param);
if (access(path, F_OK|R_OK)==0) {
//header.
char *post_fix = strstr(param, ".");
post_fix++;
send_header_with_content_type(client_fd, post_fix);
//data.
FILE *p = fopen(path, "rb");
int read2_n;
while ((read2_n = fread(buffer, sizeof(char), BUF_SIZE, p)) != 0) {send(client_fd, buffer, read2_n, 0);
memset(buffer, 0x00, read2_n);
}
fclose(p);
} else{
//header
send_header_with_content_type(client_fd, "html");
//data
send(client_fd, "NOT FOUND", strlen("NOT FOUND"), 0);
}
close(client_fd);
}
}
}
}
END:
close(epfd);
close(listen_fd);
return 0;
}
- 应用前记得批改代理的门路
base_path
为你的动态资源地址 - 残缺代码仓库 https://github.com/dengjiawen…
Golang
Golang 底层的 netpoller 基于 IO 多路复用, 所以 go 是原生反对 IO 多路复用的
type Req struct {reqUrl,contentType string}
func main() {
basePath := "/root/go/src/go_static_server"
address := ":9000"
listen, _ := net.Listen("tcp",address)
log.Printf("listen=%#v\n", address)
for {conn, _ := listen.Accept()
go func() {defer conn.Close()
log.Printf("NEW CLIENT:%s\n", conn.RemoteAddr().String())
//get request url
req := getReq(conn)
path := basePath+req.reqUrl
file, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
if err!=nil {return}
//write back header
writeHeader(conn,req.contentType)
//write back file
writeFile(conn,file)
}()}
}
func getReq(conn net.Conn) *Req {reader := bufio.NewReader(conn)
line, _, _ := reader.ReadLine()
splits := strings.Split(string(line), " ")
reqUrl := splits[1]
paths := strings.Split(reqUrl, "/")
contentType := paths[len(paths)-1]
return &Req{reqUrl: reqUrl,contentType: contentType}
}
func writeHeader(conn net.Conn,contentType string) {buffer := bytes.Buffer{}
buffer.WriteString("HTTP/1.1 200 OK\r\n")
buffer.WriteString("Content-Type:")
buffer.WriteString(contentType)
buffer.WriteString(";charset=UTF-8\r\n\r\n")
conn.Write(buffer.Bytes())
}
func writeFile(conn net.Conn,file *os.File) {buf := make([]byte,1024)
for {readN, _ := file.Read(buf)
if readN <= 0 {break}
conn.Write(buf)
}
}
- 应用前记得批改代理的门路
basePath
为你的动态资源地址 - 残缺代码仓库 https://github.com/dengjiawen…r
本文由 mdnice 多平台公布