我想编写一个C程序来生成Get Request而不使用任何外部库。这可能只使用C库,使用套接字吗?我正在考虑制作一个http数据包(使用正确的格式)并将其发送到服务器。这是唯一可能的方式还是有更好的方法?
答案 0 :(得分:26)
使用BSD套接字,或者,如果你有点限制,说你有一些RTOS,一些更简单的TCP堆栈,比如lwIP,你可以形成GET / POST请求。
有许多开源实现。请参阅“happyhttp”作为示例(http://scumways.com/happyhttp/happyhttp.html)。我知道,它是C ++,而不是C,但唯一的是“C ++依赖”,它有一个字符串/数组管理,所以它很容易移植到纯C。
请注意,没有“数据包”,因为HTTP通常通过TCP连接进行传输,因此从技术上讲,只有RFC格式的符号流。由于http请求通常以connect-send-disconnect方式完成,因此实际上可能将其称为“数据包”。
基本上,一旦你有一个开放的套接字(sockfd),你需要做的就是“全部”
char sendline[MAXLINE + 1], recvline[MAXLINE + 1];
char* ptr;
size_t n;
/// Form request
snprintf(sendline, MAXSUB,
"GET %s HTTP/1.0\r\n" // POST or GET, both tested and works. Both HTTP 1.0 HTTP 1.1 works, but sometimes
"Host: %s\r\n" // but sometimes HTTP 1.0 works better in localhost type
"Content-type: application/x-www-form-urlencoded\r\n"
"Content-length: %d\r\n\r\n"
"%s\r\n", page, host, (unsigned int)strlen(poststr), poststr);
/// Write the request
if (write(sockfd, sendline, strlen(sendline))>= 0)
{
/// Read the response
while ((n = read(sockfd, recvline, MAXLINE)) > 0)
{
recvline[n] = '\0';
if(fputs(recvline,stdout) == EOF) { cout << ("fputs erros"); }
/// Remove the trailing chars
ptr = strstr(recvline, "\r\n\r\n");
// check len for OutResponse here ?
snprintf(OutResponse, MAXRESPONSE,"%s", ptr);
}
}
答案 1 :(得分:13)
POSIX 7最小可运行示例
让我们抓取http://example.com。
wget.c
#define _XOPEN_SOURCE 700
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char** argv) {
char buffer[BUFSIZ];
enum CONSTEXPR { MAX_REQUEST_LEN = 1024};
char request[MAX_REQUEST_LEN];
char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\n\r\n";
struct protoent *protoent;
char *hostname = "example.com";
in_addr_t in_addr;
int request_len;
int socket_file_descriptor;
ssize_t nbytes_total, nbytes_last;
struct hostent *hostent;
struct sockaddr_in sockaddr_in;
unsigned short server_port = 80;
if (argc > 1)
hostname = argv[1];
if (argc > 2)
server_port = strtoul(argv[2], NULL, 10);
request_len = snprintf(request, MAX_REQUEST_LEN, request_template, hostname);
if (request_len >= MAX_REQUEST_LEN) {
fprintf(stderr, "request length large: %d\n", request_len);
exit(EXIT_FAILURE);
}
/* Build the socket. */
protoent = getprotobyname("tcp");
if (protoent == NULL) {
perror("getprotobyname");
exit(EXIT_FAILURE);
}
socket_file_descriptor = socket(AF_INET, SOCK_STREAM, protoent->p_proto);
if (socket_file_descriptor == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Build the address. */
hostent = gethostbyname(hostname);
if (hostent == NULL) {
fprintf(stderr, "error: gethostbyname(\"%s\")\n", hostname);
exit(EXIT_FAILURE);
}
in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list)));
if (in_addr == (in_addr_t)-1) {
fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list));
exit(EXIT_FAILURE);
}
sockaddr_in.sin_addr.s_addr = in_addr;
sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(server_port);
/* Actually connect. */
if (connect(socket_file_descriptor, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
/* Send HTTP request. */
nbytes_total = 0;
while (nbytes_total < request_len) {
nbytes_last = write(socket_file_descriptor, request + nbytes_total, request_len - nbytes_total);
if (nbytes_last == -1) {
perror("write");
exit(EXIT_FAILURE);
}
nbytes_total += nbytes_last;
}
/* Read the response. */
fprintf(stderr, "debug: before first read\n");
while ((nbytes_total = read(socket_file_descriptor, buffer, BUFSIZ)) > 0) {
fprintf(stderr, "debug: after a read\n");
write(STDOUT_FILENO, buffer, nbytes_total);
}
fprintf(stderr, "debug: after last read\n");
if (nbytes_total == -1) {
perror("read");
exit(EXIT_FAILURE);
}
close(socket_file_descriptor);
exit(EXIT_SUCCESS);
}
编译:
gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c
获取http://example.com并输出到stdout:
./wget example.com
此命令会暂停大多数服务器,直到超时,这是预期的:
Content-Length
未发送,服务器可以关闭以确定长度。连接部分也适用于IP:
host example.com
给出:
example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
所以我们这样做:
./wget 93.184.216.34
但是,回复是一个错误,因为我们没有在我们的计划中正确设置Host:
,那就是required in HTTP 1.1。
在Ubuntu 18.04上测试。
服务器示例
答案 2 :(得分:4)
“没有任何外部库”严格来说也会排除libc,因此您必须自己编写所有系统调用。我怀疑你的意思是严格的。如果您不想链接到另一个库,并且不希望将源代码从另一个库复制到您的应用程序中,那么使用套接字API直接处理TCP流是您最好的方法。
创建HTTP请求并通过TCP socket connection发送请求很简单,就像阅读答案一样。它解析的答案真的很棘手,特别是如果你的目标是支持相当大的标准部分。如果你正在与任意网络服务器交谈,那么错误页面,重定向,内容协商等等都会让我们的生活变得非常困难。另一方面,如果知道服务器表现良好,并且一条简单的错误消息可以用于任何意外的服务器响应,那么这也是相当简单的。
答案 3 :(得分:0)
尝试套接字编程,下面的 C++ 代码向指定的主机发出一个简单的 GET 请求并打印响应头和内容
在 Windows 10 中测试
#include <windows.h>
#include <string>
#include <stdio.h>
#include <winsock2.h>
using std::string;
SOCKET conn;
WSADATA wsaData;
struct hostent *hp;
unsigned int addr;
struct sockaddr_in server;
long fileSize;
const int bufSize = 512;
char readBuffer[bufSize], sendBuffer[bufSize], tmpBuffer[bufSize];
char *memBuffer=NULL;
char *headerBuffer=NULL;
long totalBytesRead, thisReadSize, headerLen;
char *tmpResult=NULL, *result;
char* antenna(string host,string path);
SOCKET connectToServer(char *szServerName, WORD portNum);
int getHeaderLength(char *content);
int main(){
if(WSAStartup(0x101, &wsaData) != 0){printf("startup failure");}
memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-1}/1/public/values?alt=json");
printf("Response content:\n%s\n\n", memBuffer);
memBuffer = antenna("www.spreadsheets.google.com", "/feeds/list/{Published_Sheet_ID-2}/1/public/values?alt=json");
printf("Response content:\n%s", memBuffer);
WSACleanup();
}
char *antenna(string host, string path){
fileSize=0;
totalBytesRead=0;
memBuffer=NULL;
headerBuffer=NULL;
tmpResult=NULL,
conn = connectToServer((char*)host.c_str(), 80);
if(conn == 0){printf("No Internet connection");}
sprintf(sendBuffer, "GET %s HTTP/1.0 \r\nHost: %s\r\nConnection: close\r\n\r\n", path.c_str(),host.c_str());
send(conn, sendBuffer, strlen(sendBuffer), 0);
printf("Request Format: \n%s",sendBuffer);
while(1){
memset(readBuffer, 0, bufSize);
thisReadSize = recv (conn, readBuffer, bufSize, 0);
if ( thisReadSize <= 0 ){break;}
tmpResult = (char*)realloc(tmpResult, thisReadSize+totalBytesRead);
memcpy(tmpResult+totalBytesRead, readBuffer, thisReadSize);
totalBytesRead += thisReadSize;
}
headerLen = getHeaderLength(tmpResult);
long contenLen = totalBytesRead-headerLen;
result = new char[contenLen+1];
memcpy(result, tmpResult+headerLen, contenLen);
result[contenLen] = 0x0;
char *myTmp;
myTmp = new char[headerLen+1];
strncpy(myTmp, tmpResult, headerLen);
myTmp[headerLen] = 0;
delete(tmpResult);
headerBuffer = myTmp;
printf("Response Header: \n%s",headerBuffer);
fileSize = contenLen;
closesocket(conn);
if(fileSize != 0){
delete(memBuffer);
delete(headerBuffer);
}
return(result);
}
SOCKET connectToServer(char *szServerName, WORD portNum)
{
conn = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (conn == INVALID_SOCKET){return 0;}
if(inet_addr(szServerName)==INADDR_NONE){hp=gethostbyname(szServerName);}
else{
addr=inet_addr(szServerName);
hp=gethostbyaddr((char*)&addr,sizeof(addr),AF_INET);
}
if(hp==NULL){closesocket(conn);return 0;}
server.sin_addr.s_addr=*((unsigned long*)hp->h_addr);
server.sin_family=AF_INET;
server.sin_port=htons(portNum);
if(connect(conn,(struct sockaddr*)&server,sizeof(server)))
{
closesocket(conn);
return 0;
}
return conn;
}
int getHeaderLength(char *content)
{
const char *srchStr1 = "\r\n\r\n", *srchStr2 = "\n\r\n\r";
char *findPos;
int ofset = -1;
findPos = strstr(content, srchStr1);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr1);
}
else
{
findPos = strstr(content, srchStr2);
if (findPos != NULL)
{
ofset = findPos - content;
ofset += strlen(srchStr2);
}
}
return ofset;
}
编译(使用g++):
g++ -static test.cpp -o test.exe -lws2_32
-lws2_32 指定链接器与 winsock dll 链接