总述
开发:WSL+vscode
本项目利用C语言套接字编程和多线程编程实现了Linux下一个简单web代理服务器,只支持HTTP 1.0协议的GET方法。
代理服务器端口号由用户自己手动输入,在终端输入 ./proxy <port>即可启动。
代理服务器的工作流程想必大家都清楚了
服务器创建套接字 ——> 服务器与客户端连接并分配线程 ——> 服务器接收客户端消息 ——> 服务器将客户端消息发向目标地址 ——> 服务器接收目标地址的响应 ——> 服务器将响应反馈给客户端
接下来只需要将各个步骤涉及的功能实现即可。
技术实现
首先验证用户是否输入端口号
if (argc != 2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
如果输入端口号则验证输入端口号是否合法
int port = atoi(argv[1]);
if (port < 1 || port > 65535) {
fprintf(stderr, "Error: Invalid port number. Port number should be between 1 and 65535.\n");
return 1;
}
接下来创建服务器端套接字
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
bzero(&server_address, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(port);
server_address.sin_addr.s_addr = INADDR_ANY;
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, MAX_CONNECTIONS);
printf("Listening on port %d\n", port);
bzero将套接字初始为0,AF_INET表示该套接字使用的是ipv4协议,htons是将端口号转换成网络字节序,INADDR_ANY表示接收来自任意地址的连接。
最后bind函数将服务器套接字绑定起来,也就是说服务器端开始监听端口了。
listen只是用来限制同时连接服务器的最大用户数。
服务器循环接收客户端的连接并为其分配线程处理
while (1) {
int client_socket = accept(server_socket, NULL, NULL);
printf("Accepted connection\n");
if (fork() == 0) {
//创建新进程处理连接的客户端
}
close(client_socket);//关闭连接套接字
}
每有一个新的连接服务器使用fork创建一个新进程处理,完成之后关闭客户端套接字释放资源。
接收客户端消息
char buffer[MAX_BUFFER];
read(client_socket, buffer, sizeof(buffer));
char method[256], url[256], version[256];
sscanf(buffer, "%s %s %s", method, url, version);
if (strcasecmp(method, "GET") != 0) {
char *response = "HTTP/1.0 501 Not Implemented\r\n\r\n";
write(client_socket, response, strlen(response));
close(client_socket);
exit(1);
}
设置缓冲区大小,将客户端消息读入缓冲区,然后使用sscanf将其以空格形式分割成方法、url、http版本。判断请求方法是不是get方法,如果不是则向客户端发送501错误
将客户端请求发送到目标地址
int port=80; //default port
char host[256], path[256], *port_ptr;
sscanf(url, "http://%[^/]%s", host, path);
// Check if the URL contains a port number
port_ptr = strchr(host, ':');
if (port_ptr) {
*port_ptr = '\0'; // Null-terminate the host string
port = atoi(port_ptr + 1); // Convert the port number to an integer
}
printf("Host: %s:%d\nPath: %s\n", host, port, path);
struct hostent *server = gethostbyname(host);
int target_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in target_address;
bzero(&target_address, sizeof(target_address));
target_address.sin_family = AF_INET;
target_address.sin_port = htons(port);
bcopy((char *)server->h_addr_list[0], (char *)&target_address.sin_addr.s_addr, server->h_length);
connect(target_socket, (struct sockaddr *)&target_address, sizeof(target_address));
char request[MAX_BUFFER];
sprintf(request, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path, host);
write(target_socket, request, strlen(request));
默认网站端口是80,将客户端发来的url拆成主机地址和端口号,如果url不含有端口号则使用默认端口号80,否则使用url中给出的端口号。
创建与目标地址连接的套接字,gethostbyname是解析域名的库函数,bcopy将gethostbyname解析到的ip地址复制到target_address套接字中,然后connect连接,发送request请求,注意http报文格式。
接收目标地址响应并反馈给客户端
while (1) {
bzero(buffer, sizeof(buffer));
int bytes = read(target_socket, buffer, sizeof(buffer) - 1);
if (bytes <= 0) {
break;
}
write(client_socket, buffer, bytes);
}
close(client_socket);
close(target_socket);
exit(0);
接收比较简单,直接把目标地址的响应全部发给客户端就行了。
由于是HTTP 1.0,最后不要忘记释放所有资源哦
运行效果
假设gcc编译生成proxy可执行文件,终端输入./proxy 8888运行程序
在火狐里面设置一下代理
输入http://baidu.com访问一下,成功访问到百度的html页面
也可以进行telnet测试
也是成功返回了百度的html
源码
源码放在下面了,大家慢慢享用 ^_^
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#define MAX_BUFFER 4096
#define MAX_CONNECTIONS 100
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <port>\n", argv[0]);
exit(1);
}
int port = atoi(argv[1]);
if (port < 1 || port > 65535) {
fprintf(stderr, "Error: Invalid port number. Port number should be between 1 and 65535.\n");
return 1;
}
int server_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in server_address;
bzero(&server_address, sizeof(server_address));
server_address.sin_family = AF_INET;
server_address.sin_port = htons(port);
server_address.sin_addr.s_addr = INADDR_ANY;
bind(server_socket, (struct sockaddr *)&server_address, sizeof(server_address));
listen(server_socket, MAX_CONNECTIONS);
printf("Listening on port %d\n", port);
while (1) {
int client_socket = accept(server_socket, NULL, NULL);
printf("Accepted connection\n");
if (fork() == 0) {
char buffer[MAX_BUFFER];
read(client_socket, buffer, sizeof(buffer));
char method[256], url[256], version[256];
sscanf(buffer, "%s %s %s", method, url, version);
if (strcasecmp(method, "GET") != 0) {
char *response = "HTTP/1.0 501 Not Implemented\r\n\r\n";
write(client_socket, response, strlen(response));
close(client_socket);
exit(1);
}
int port=80; //default port
char host[256], path[256], *port_ptr;
sscanf(url, "http://%[^/]%s", host, path);
// Check if the URL contains a port number
port_ptr = strchr(host, ':');
if (port_ptr) {
*port_ptr = '\0'; // Null-terminate the host string
port = atoi(port_ptr + 1); // Convert the port number to an integer
}
printf("Host: %s:%d\nPath: %s\n", host, port, path);
struct hostent *server = gethostbyname(host);
int target_socket = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in target_address;
bzero(&target_address, sizeof(target_address));
target_address.sin_family = AF_INET;
target_address.sin_port = htons(port);
bcopy((char *)server->h_addr_list[0], (char *)&target_address.sin_addr.s_addr, server->h_length);
connect(target_socket, (struct sockaddr *)&target_address, sizeof(target_address));
char request[MAX_BUFFER];
sprintf(request, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path, host);
write(target_socket, request, strlen(request));
while (1) {
bzero(buffer, sizeof(buffer));
int bytes = read(target_socket, buffer, sizeof(buffer) - 1);
if (bytes <= 0) {
break;
}
write(client_socket, buffer, bytes);
}
close(client_socket);
close(target_socket);
exit(0);
}
close(client_socket);
}
return 0;
}
版权归原作者 猿神起洞⋋ _ ⋌ 所有, 如有侵权,请联系我们删除。