要在 Ubuntu 环境中完成实验目标,我们需要分为以下几个步骤实现一个支持 HTTP 和 HTTPS 的简单服务器。以下内容将包括分步指导和代码实现。
实验步骤¶
1. 安装环境和依赖¶
在 Ubuntu 中准备开发环境:
-
安装 GCC 和 Make 工具:
sudo apt update sudo apt install build-essential
-
安装 OpenSSL 开发库:
sudo apt install libssl-dev
2. 实现实验功能¶
需要创建一个支持 HTTP 和 HTTPS 的服务器,以下是实现的分步说明:
HTTP 和 HTTPS 逻辑¶
-
HTTP 服务器 (80 端口):
- 监听 80 端口。
- 解析请求报文,并始终返回
301 Moved Permanently
状态码。 - 在响应中添加
Location
字段,指向https://
版本的 URL。 -
HTTPS 服务器 (443 端口):
-
使用 OpenSSL 初始化。
- 监听 443 端口。
- 支持以下逻辑:
- 200 OK:如果请求的文件存在,返回文件内容。
- 206 Partial Content:支持
Range
请求,返回部分内容。 - 404 Not Found:如果请求的文件不存在,返回错误。
- 解析 HTTP 报文,提取请求路径、方法和字段。
3. 项目代码实现¶
以下是实现的完整代码框架。
HTTP 线程:监听 80 端口¶
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
void* http_server(void* arg) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
perror("HTTP socket creation failed");
exit(1);
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(80);
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("HTTP bind failed");
exit(1);
}
listen(sock, 10);
printf("HTTP server is running on port 80...\n");
while (1) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_sock = accept(sock, (struct sockaddr*)&client_addr, &addr_len);
if (client_sock < 0) {
perror("HTTP accept failed");
continue;
}
char buffer[1024] = {0};
read(client_sock, buffer, sizeof(buffer));
// 301 Moved Permanently
char* response = "HTTP/1.1 301 Moved Permanently\r\n"
"Location: https://127.0.0.1\r\n"
"Content-Length: 0\r\n\r\n";
write(client_sock, response, strlen(response));
close(client_sock);
}
close(sock);
return NULL;
}
HTTPS 线程:监听 443 端口¶
#include <openssl/ssl.h>
#include <openssl/err.h>
void handle_https_request(SSL* ssl) {
char buffer[1024] = {0};
if (SSL_read(ssl, buffer, sizeof(buffer)) <= 0) {
perror("SSL_read failed");
return;
}
// 解析 GET 请求
char method[16], path[256];
sscanf(buffer, "%s %s", method, path);
if (strcmp(method, "GET") != 0) {
SSL_write(ssl, "HTTP/1.1 405 Method Not Allowed\r\n", 32);
return;
}
// 去掉请求路径的 "/"
char* file_path = path + 1;
FILE* file = fopen(file_path, "rb");
if (!file) {
// 404 Not Found
char* response = "HTTP/1.1 404 Not Found\r\n"
"Content-Length: 0\r\n\r\n";
SSL_write(ssl, response, strlen(response));
} else {
// 200 OK
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
char response[1024];
sprintf(response, "HTTP/1.1 200 OK\r\n"
"Content-Length: %ld\r\n\r\n", file_size);
SSL_write(ssl, response, strlen(response));
char file_buffer[1024];
int bytes_read;
while ((bytes_read = fread(file_buffer, 1, sizeof(file_buffer), file)) > 0) {
SSL_write(ssl, file_buffer, bytes_read);
}
fclose(file);
}
}
void* https_server(void* arg) {
SSL_library_init();
SSL_load_error_strings();
OpenSSL_add_all_algorithms();
const SSL_METHOD* method = TLS_server_method();
SSL_CTX* ctx = SSL_CTX_new(method);
// 加载证书和私钥
SSL_CTX_use_certificate_file(ctx, "./keys/server.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "./keys/server.key", SSL_FILETYPE_PEM);
int sock = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(443);
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
listen(sock, 10);
printf("HTTPS server is running on port 443...\n");
while (1) {
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);
int client_sock = accept(sock, (struct sockaddr*)&client_addr, &addr_len);
SSL* ssl = SSL_new(ctx);
SSL_set_fd(ssl, client_sock);
if (SSL_accept(ssl) <= 0) {
SSL_free(ssl);
close(client_sock);
continue;
}
handle_https_request(ssl);
SSL_free(ssl);
close(client_sock);
}
close(sock);
SSL_CTX_free(ctx);
return NULL;
}
主函数:创建两个线程¶
#include <pthread.h>
int main() {
pthread_t http_thread, https_thread;
pthread_create(&http_thread, NULL, http_server, NULL);
pthread_create(&https_thread, NULL, https_server, NULL);
pthread_join(http_thread, NULL);
pthread_join(https_thread, NULL);
return 0;
}
4. 编译和运行¶
-
编译程序:
gcc -Wall -o server server.c -lssl -lcrypto -lpthread
-
运行程序:
sudo ./server
-
测试:
- 用浏览器访问
http://127.0.0.1
,应返回301 Moved Permanently
。 - 用浏览器访问
https://127.0.0.1
,可下载或查看文件内容。
- 用浏览器访问
文件说明¶
server.c
: 上述代码。keys/server.crt
和keys/server.key
: 需要提前生成或获取的 SSL 证书和私钥。
一些问题¶
我: tell me everything about coding socket on linux ubuntu ChatGPT 说:
Socket programming on Linux (including Ubuntu) involves creating, binding, listening to, and communicating over sockets, which are endpoints for network communication. Below is a complete guide to understanding and working with sockets on Linux:
Basics of Socket Programming¶
- Socket: A socket is an abstraction representing a communication endpoint. It allows communication between two processes (on the same or different machines) over a network.
- Types of Sockets:
- Stream Sockets (
SOCK_STREAM
): Provide reliable, connection-oriented communication (e.g., TCP). - Datagram Sockets (
SOCK_DGRAM
): Provide unreliable, connectionless communication (e.g., UDP).
- Stream Sockets (
- Protocol Families:
- IPv4 (
AF_INET
): Used for internet communication over IPv4. - IPv6 (
AF_INET6
): Used for internet communication over IPv6. - UNIX (
AF_UNIX
): Used for interprocess communication (IPC) on the same machine.
- IPv4 (
Key Steps in Socket Programming¶
- Create a Socket: Use
socket()
to create a socket. - Bind the Socket: Use
bind()
to associate the socket with an IP address and port. - Listen (Server): Use
listen()
to prepare the socket to accept incoming connections. - Connect (Client): Use
connect()
to establish a connection to the server. - Accept Connections (Server): Use
accept()
to accept an incoming connection. - Send/Receive Data: Use
send()
,recv()
, or similar functions to exchange data. - Close the Socket: Use
close()
to release the socket resource.
Linux System Calls for Sockets¶
Function | Description |
---|---|
socket() |
Creates a socket and returns a file descriptor. |
bind() |
Binds the socket to a specific IP address and port. |
listen() |
Marks the socket as a passive socket to accept incoming connections. |
accept() |
Accepts a new connection on a socket. Returns a new socket for the connection. |
connect() |
Initiates a connection on a socket (used by clients). |
send() |
Sends data on a connected socket. |
recv() |
Receives data from a connected socket. |
close() |
Closes the socket file descriptor. |
select() |
Monitors multiple sockets for readability, writability, or exceptions (used for multiplexing). |
poll() |
Similar to select() , monitors multiple sockets. |
epoll() |
Advanced version of select() for efficient I/O multiplexing. |
解读示例程序¶
1. The C File: https-server-example.c
¶
This program implements a basic HTTPS server using OpenSSL. Here's the step-by-step breakdown:
Key Features¶
-
SSL Initialization:
- It initializes OpenSSL by loading algorithms and error strings.
- Configures the server to use TLS (Transport Layer Security) with
TLS_server_method()
. -
Certificate and Private Key:
-
The server loads an SSL certificate (
cnlab.cert
) and private key (cnlab.prikey
) from the./keys/
folder. - These files are required to establish a secure HTTPS connection.
-
Socket Setup:
-
Creates a TCP socket (
socket()
) to listen on port 443 (default HTTPS port). - Uses
setsockopt()
to allow reusing the port after restarting the server. - Binds the socket to any available network interface (
INADDR_ANY
) and the specified port. -
Listening and Accepting Connections:
-
The server listens for incoming connections with
listen()
and accepts them usingaccept()
. - Each connection gets wrapped in an SSL session using
SSL_new()
andSSL_set_fd()
. -
Handling Requests:
-
Reads data from the client using
SSL_read()
. - Sends back a hardcoded HTTP response using
SSL_write()
. - Releases resources (
SSL_free()
andclose()
) after handling the request.
How the Code Works¶
- Initialize OpenSSL and load the certificate/private key.
- Set up a socket to listen for incoming connections.
- For every new client:
- Accept the connection.
- Wrap the connection with SSL.
- Handle the HTTPS request:
- Read data from the client.
- Send an HTTP response back.
- Clean up after finishing the request.
- Continue listening for new clients.