用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 -> listenint 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 10char 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多平台公布