I'm trying to get straight in my head the relationship between HTTP and TCP.
I tried to resolve (what I perceived as) contradictory answers from a web search of "tcp vs http" by writing a server that listens at a TCP socket bound to some address+port, then typing that address+port into a web brower.
Having done so, I saw that the content received at the accept()ed socket was text with human-readable "HTTP stuff" (my knowledge of HTTP isn't enough to intelligently identify the content).
From Chrome, my server receives:
GET / HTTP/1.1
Host: 127.0.0.23:9018
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
...and from Firefox, my server receives:
GET / HTTP/1.1
Host: 127.0.0.23:9018
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
From the above results, I conjectured that HTTP is sending HTTP-conformant bytes (is it always ASCII?) over a TCP connection to a server's socket that has been accept()ed after listen()ing to a specific address+port.
So I further conjectured that in order to get content to show up in a web browser that connects to the address+port that my server is listen()ing at, my server should write() some kind of HTTP-compliant response to the socket.
This Stack Overflow Q&A gave me a candidate minimal HTTP response.
Putting it all together, my server's MCVE code is:
#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <netinet/in.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdexcept>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define IP "127.0.0.23"
#define PORT (9018)
/**
* A primitive, POC-level HTTP server that accepts its first incoming connection
* and sends back a minimal HTTP OK response.
*/
class Server {
private:
static const std::string ip_;
static const std::uint16_t port_{PORT};
int listen_sock_;
pthread_t tid_;
public:
/**
* Ctor: create and bind listen_sock_ and start a thread for startRoutine().
*/
Server() {
using namespace std;
int result;
if (! createSocket()) { throw runtime_error("failed creating socket"); }
if (! bindSocket()) { throw runtime_error("failed binding socket"); }
if ((result = pthread_create(&tid_, NULL, startRoutine, this))) {
std::stringstream ss;
ss << "pthread_create() error " << errno << "(" << result << ")";
std::cerr << ss.str() << std::endl;
throw runtime_error("failed spawning Server thread");
}
}
/** Dtor: wait for the spawned thread and destroy listen_sock_. */
~Server() {
pthread_join( tid_, NULL );
destroySocket();
}
private:
/** Creates listen_sock_ as a stream socket. */
bool createSocket() {
listen_sock_ = socket(PF_INET, SOCK_STREAM, 0);
if (listen_sock_ < 0) {
std::stringstream ss;
ss << "socket() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
}
return (listen_sock_ >= 0);
}
[138/573]
/** Shuts down and closes listen_sock_. */
void destroySocket() {
if (listen_sock_ >= 0) {
shutdown(listen_sock_, SHUT_RDWR);
close(listen_sock_);
}
}
/** Binds listen_sock_ to ip_ and port_. */
bool bindSocket() {
int ret;
sockaddr_in me;
me.sin_family = PF_INET;
me.sin_port = htons(port_);
me.sin_addr.s_addr = inet_addr(ip_.c_str());
int optval = 1;
setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
if ((ret = bind(listen_sock_, (sockaddr*)&me, sizeof me))) {
std::stringstream ss;
ss << "bind() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
}
return (! ret);
}
/**
* Accept a connection from listen_sock_.
* Caller guarantees listen_sock_ has been listen()ed to already.
* #param tv [in, out] How long to wait to accept a connection.
* #return accepted socket; -1 on any error.
*/
int acceptConnection(timeval& tv) {
int sock = -1;
int ret;
fd_set readfds;
sockaddr_in peer;
socklen_t addrlen = sizeof peer;
FD_ZERO(&readfds);
FD_SET(listen_sock_, &readfds);
ret = select(listen_sock_ + 1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
std::stringstream ss;
ss << "select() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
return sock;
}
else if (! ret) {
std::cout << "no connections within " << tv.tv_sec << "seconds"
<< std::endl;
return sock;
}
if ((sock = accept(listen_sock_, (sockaddr*)&peer, &addrlen)) < 0) {
std::stringstream ss;
ss << "accept() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
}
else {
std::stringstream ss;
ss << "socket " << sock << " accepted connection from "
<< inet_ntoa( peer.sin_addr ) << ":" << ntohs(peer.sin_port);
std::cout << ss.str() << std::endl;
}
return sock;
}
[60/573]
/** Read from the specified socket and dump to stdout. */
static void dumpReceivedContent(const int& sock) {
fd_set readfds;
struct timeval tv = {30, 0};
int ret;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
ret = select(sock + 1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
std::stringstream ss;
ss << "select() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
return;
}
else if (! ret) {
std::cout << "no content received within " << tv.tv_sec << "seconds"
<< std::endl;
return;
}
if (FD_ISSET(sock, &readfds)) {
ssize_t bytes_read;
char buf[80] = {0};
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
std::cout << "received content:" << std::endl;
std::cout << "----" << std::endl;
while ((bytes_read = read(sock, buf, (sizeof buf) - 1)) >= 0) {
buf[bytes_read] = '\0';
std::cout << buf;
}
std::cout << std::endl << "----" << std::endl;
}
}
/** Write a minimal HTTP OK response to the specified socker. */
static void sendMinHttpResponse(const int& sock) {
static const std::string resp =
"HTTP/1.1 200 OK\r\n"
"Content-Length: 13\r\n"
"Content-Type: text/plain\r\n\r\nHello World!";
write(sock, resp.c_str(), resp.length());
}
/**
* Thread start routine: listen for, then accept connections; dump received
* content; send a minimal response.
*/
static void* startRoutine(void* arg) {
Server* s;
if (! (s = (Server*)arg)) {
std::cout << "Bad arg" << std::endl;
return NULL;
}
if (listen(s->listen_sock_, 3)) {
std::stringstream ss;
ss << "listen() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
return NULL;
}
std::cout << "Server accepting connections at "
<< s->ip_ << ":" << s->port_ << std::endl;
{
timeval tv = { 30, 0 };
int sock = s->acceptConnection(tv);
if (sock < 0) {
std::cout << "no connections accepted" << std::endl;
return NULL;
}
dumpReceivedContent(sock);
sendMinHttpResponse(sock);
shutdown(sock, SHUT_RDWR);
close(sock);
}
return NULL;
}
};
const std::string Server::ip_{IP};
int main( int argc, char* argv[] ) {
Server s;
return 0;
}
When I point Chrome and Chromium browsers to my server (127.0.0.23:9018), I get a blank page with no content, but when I point Firefox to my server, I get the "Hello world!" string that I wanted.
Why does this only work with Firefox, and not with Chrome or Chromium?
Your server responds with an invalid data size Content-Length: 13.
The data is Hello World!, the size is 12.
resp.length() does not count \0, thus the server does not send Hello World!\0.
The header must be Content-Length: 12.
I addition to #273K's answer, another problem I see is your dumpReceivedContent() method is completely ignoring the HTTP protocol and just reads and logs everything the client sends until the client disconnects or an error occurs, and then you call sendMinHttpResponse() on a now-likely-invalid TCP connection, so the client probably won't be able to receive it.
You can't just blindly read from the TCP connection as you are doing. You MUST parse the client's data as it arrives so you can detect the end of the client's request properly and then leave the TCP connection open until your response has been sent.
Refer to RFC 2616 Section 4.4 and RFC 7230 Section 3.3.3 for the rules you must follow to detect when you have received the client's complete request without over-reading from the TCP connection.
I have a number of previous answers that provide pseudo code for demonstrating how to implement those rules.
Related
Premise:
I'm building on newly-learned networking fundamentals learned from these two questions: one, two.
I'll call the the code at the bottom of this post my "http responder," and not a "http server," since I recently got an educational/appreciated slap on the wrist for calling it the latter.
The program functions as follows:
it listens at INADDR_ANY port 9018 (a naively/randomly-chosen number)
it dumps (to stdout) the content received at the accepted socket until there's no more content to read
it sends a minimal HTTP response with status OK.
(in case #Remy Lebeau visits this question, item 2, specifically, is why this program is not a http server: it does not parse the incoming HTTP request, it just dumbly dumps it and responds -- even in the case of a closed TCP connection -- but I believe this is not relevant to the question asked here).
From my second link, above, I learned about why a web server would want to listen to a specific port on all interfaces.
My understanding is that the way this is done in C-family languages is by binding to INADDR_ANY (as opposed to a specific IP address, like "127.0.0.13").
Question:
When I run this program, I observe the expected result if I try to connect from a web browser that is running on the same PC as where the executable is run: my browser shows a minimal webpage with content "I'm the content" if I connect to 127.0.0.1:9018, 127.0.0.2:9018, 127.0.0.13.9018, 127.0.0.97:9018, etc.
Most relevant to this question, I also get the same minimal webpage by pointing my browser to 10.0.0.17:9018, which is the IP address assigned to my "wlpls0" interface:
$ ifconfig
...
wlp1s0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 10.0.0.17 netmask 255.255.255.0 broadcast 10.0.0.255
inet6 fe80::5f8c:c301:a6a3:6e35 prefixlen 64 scopeid 0x20<link>
ether f8:59:71:01:89:cf txqueuelen 1000 (Ethernet)
RX packets 1272659 bytes 1760801882 (1.7 GB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 543118 bytes 74285210 (74.2 MB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
However, I only observe this desired webpage if the browser that I point to 10.0.0.7:9018 is running on the same PC as where the a.out is running.
From another PC on the same network, if I point its browser to 10.0.0.17:9018, the browser spins without connecting, and eventually says "Hmm...can't reach this page" and "10.0.0.17 took too long to respond".
So my question is: what are reasons why only a browser running on the same PC as the running a.out can connect to the "http responder"? Why do browsers on a different PC in the same network seem unable to connect?
What I have tried:
On the other PC, I am able to ping 10.0.0.17 -- and that just about exhausts my knowledge of how to debug networking issues.
I considered whether the issue at root is more likely to be "networking stuff", which might make this question better asked at Super User, but then I thought to start my inquiry with Stack Overflow, in case the issues is in the C++ code.
The code:
// main.cpp
#include <arpa/inet.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <iostream>
#include <netinet/in.h>
#include <pthread.h>
#include <semaphore.h>
#include <stdexcept>
#include <sstream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#define IP "0.0.0.0"
#define PORT (9018)
/**
* A primitive, POC-level HTTP server that accepts its first incoming connection
* and sends back a minimal HTTP OK response.
*/
class Server {
private:
static const std::string ip_;
static const std::uint16_t port_{PORT};
int listen_sock_;
pthread_t tid_;
public:
Server() { ///< create + bind listen_sock_; start thread for startRoutine().
using namespace std;
int result;
if (! createSocket()) { throw runtime_error("failed creating socket"); }
if (! bindSocket()) { throw runtime_error("failed binding socket"); }
if ((result = pthread_create(&tid_, NULL, startRoutine, this))) {
std::stringstream ss;
ss << "pthread_create() error " << errno << "(" << result << ")";
std::cerr << ss.str() << std::endl;
throw runtime_error("failed spawning Server thread");
}
}
~Server() { ///< wait for the spawned thread and destroy listen_sock_.
pthread_join( tid_, NULL );
destroySocket();
}
private:
bool createSocket() { ///< Creates listen_sock_ as a stream socket.
listen_sock_ = socket(PF_INET, SOCK_STREAM, 0);
if (listen_sock_ < 0) {
std::stringstream ss;
ss << "socket() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
}
return (listen_sock_ >= 0);
}
void destroySocket() { ///< shut down and closes listen_sock_.
if (listen_sock_ >= 0) {
shutdown(listen_sock_, SHUT_RDWR);
close(listen_sock_);
}
}
bool bindSocket() { ///< binds listen_sock_ to ip_ and port_.
int ret;
sockaddr_in me;
me.sin_family = PF_INET;
me.sin_port = htons(port_);
me.sin_addr.s_addr = INADDR_ANY;
int optval = 1;
setsockopt(listen_sock_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
if ((ret = bind(listen_sock_, (sockaddr*)&me, sizeof me))) {
std::stringstream ss;
ss << "bind() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
}
return (! ret);
}
/**
* Accept a connection from listen_sock_.
* Caller guarantees listen_sock_ has been listen()ed to already.
* #param tv [in, out] How long to wait to accept a connection.
* #return accepted socket; -1 on any error.
*/
int acceptConnection(timeval& tv) {
int sock = -1;
int ret;
fd_set readfds;
sockaddr_in peer;
socklen_t addrlen = sizeof peer;
FD_ZERO(&readfds);
FD_SET(listen_sock_, &readfds);
ret = select(listen_sock_ + 1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
std::stringstream ss;
ss << "select() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
return sock;
}
else if (! ret) {
std::cout << "no connections within " << tv.tv_sec << " seconds"
<< std::endl;
return sock;
}
if ((sock = accept(listen_sock_, (sockaddr*)&peer, &addrlen)) < 0) {
std::stringstream ss;
ss << "accept() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
}
else {
std::stringstream ss;
ss << "socket " << sock << " accepted connection from "
<< inet_ntoa( peer.sin_addr ) << ":" << ntohs(peer.sin_port);
std::cout << ss.str() << std::endl;
}
return sock;
}
static void dumpReceivedContent(const int& sock) { ///< read & dump from sock.
fd_set readfds;
struct timeval tv = {30, 0};
int ret;
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
ret = select(sock + 1, &readfds, NULL, NULL, &tv);
if (ret < 0) {
std::stringstream ss;
ss << "select() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
return;
}
else if (! ret) {
std::cout << "no content received within " << tv.tv_sec << "seconds"
<< std::endl;
return;
}
if (FD_ISSET(sock, &readfds)) {
ssize_t bytes_read;
char buf[80] = {0};
fcntl(sock, F_SETFL, fcntl(sock, F_GETFL, 0) | O_NONBLOCK);
std::cout << "received content:" << std::endl;
std::cout << "----" << std::endl;
while ((bytes_read = read(sock, buf, (sizeof buf) - 1)) >= 0) {
buf[bytes_read] = '\0';
std::cout << buf;
}
std::cout << std::endl << "----" << std::endl;
}
}
static void sendMinHttpResponse(const int& sock) { ///< min HTTP OK + content.
static const std::string html =
"<!doctype html>"
"<html lang=en>"
"<head>"
"<meta charset=utf-8>"
"<title>blah</title>"
"</head>"
"<body>"
"<p>I'm the content</p>"
"</body>"
"</html>";
std::stringstream resp;
resp << "HTTP/1.1 200 OK\r\n"
<< "Content-Length: " << html.length() << "\r\n"
<< "Content-Type: text/html\r\n\r\n"
<< html;
write(sock, resp.str().c_str(), resp.str().length());
}
/**
* Thread start routine: listen for, then accept connections; dump received
* content; send a minimal response.
*/
static void* startRoutine(void* arg) {
Server* s;
if (! (s = (Server*)arg)) {
std::cout << "Bad arg" << std::endl;
return NULL;
}
if (listen(s->listen_sock_, 3)) {
std::stringstream ss;
ss << "listen() error " << errno << "(" << strerror(errno) << ")";
std::cerr << ss.str() << std::endl;
return NULL;
}
std::cout << "Server accepting connections at "
<< s->ip_ << ":" << s->port_ << std::endl;
{
timeval tv = { 30, 0 };
int sock = s->acceptConnection(tv);
if (sock < 0) {
std::cout << "no connections accepted" << std::endl;
return NULL;
}
dumpReceivedContent(sock);
sendMinHttpResponse(sock);
shutdown(sock, SHUT_RDWR);
close(sock);
}
return NULL;
}
};
const std::string Server::ip_{IP};
int main( int argc, char* argv[] ) {
Server s;
return 0;
}
Compilation/execution:
This is a "working" case when the http responder receives a connection from a web browser on the same PC connecting to 10.0.0.17:9018:
$ g++ -g ./main.cpp -lpthread && ./a.out
Server accepting connections at 0.0.0.0:9018
socket 4 accepted connection from 10.0.0.17:56000
received content:
----
GET / HTTP/1.1
Host: 10.0.0.17:9018
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
----
This is the problem/question case when the http responder receives nothing from a web browser on a different PC in the same network connecting to 10.0.0.17:9018:
$ ./a.out
Server accepting connections at 0.0.0.0:9018
no connections within 0 seconds
no connections accepted
** The "no connections within 0 seconds" message is because select() updated the struct timeval.tv_sec field -- the program has actually waited 30 seconds.
I'm using libev + non-blocking sockets to send a request to a server. I'm using Keep Alive because I need to send future requests to the destination over this same connection.
Behavior
Run the program and it fetches the URL and logs to console, as expected.
After doing this, wait and don't push ctrl+c to exit the program.
Expected
App should stay open because event loop is waiting for future responses but should not console log anything after the initial response.
Actual
Leave the app running. After 30+ seconds, it will start to console log the same response over and over and over again without end.
Question
Why is libev calling my callback (example_cb) repeatedly when no new request was sent and no new response data was received? How can I fix this?
#include <ev.h>
#include <stdio.h>
#include <iostream>
#include <ctype.h>
#include <cstring>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <unistd.h>
#include <sstream>
#include <fstream>
#include <string>
using namespace std;
void sendRequest(int sockfd)
{
puts("------");
puts("sendRequest() was called");
stringstream ss;
ss << "GET /posts/11 HTTP/1.1\r\n"
<< "Host: jsonplaceholder.typicode.com\r\n"
<< "Accept: application/json\r\n"
<< "\r\n";
string request = ss.str();
if (send(sockfd, request.c_str(), request.length(), 0) != (int)request.length()) {
cout << "Error sending request." << endl;
exit(1);
}
cout << "Request sent. No err occured." << endl;
}
static void delay_cb(EV_P_ ev_timer *w, int revents)
{
puts("------");
puts("delay_cb() was called");
sendRequest(3);
}
static void example_cb(EV_P_ ev_io *w, int revents)
{
puts("------");
puts("example_cb() was called");
int sockfd = 3;
size_t len = 80*1024, nparsed; // response must be <= 80 Kb
char buf[len];
ssize_t recved;
recved = recv(sockfd, &buf, len, 0);
if (recved < 0) {
perror("recved was <1");
}
// don't process keep alives
if (buf[0] != '\0') {
std::cout << buf << std::endl;
}
// clear buf
buf[0] = '\0';
std::cout << "buf after clear attempt: " << buf << std::endl;
}
int example_request()
{
std::string hostname = "jsonplaceholder.typicode.com";
int PORT = 80;
struct sockaddr_in client;
struct hostent * host = gethostbyname(hostname.c_str());
if (host == NULL || host->h_addr == NULL) {
cout << "Error retrieving DNS information." << endl;
exit(1);
}
bzero(&client, sizeof(client));
client.sin_family = AF_INET;
client.sin_port = htons( PORT );
memcpy(&client.sin_addr, host->h_addr, host->h_length);
// create a socket
int sockfd = socket(PF_INET, SOCK_STREAM, 0);
if (sockfd < 0) {
cout << "Error creating socket." << endl;
exit(1);
}
cout << "Socket created" << endl;
// enable keep alive
int val = 1;
setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &val, sizeof val);
if (connect(sockfd, (struct sockaddr *)&client, sizeof(client)) < 0) {
close(sockfd);
cout << "Could not connect" << endl;
exit(1);
}
cout << "Socket connected" << endl;
// make non-blocking
int status = fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFL, 0) | O_NONBLOCK);
if (status == -1) {
perror("ERROR making socket non-blocking");
}
std::cout << "Socket set to non-blocking" << std::endl;
std::cout << "Sockfd is: " << sockfd << std::endl;
return sockfd;
}
int main(void)
{
// establish socket connection
int sockfd = example_request();
struct ev_loop *loop = EV_DEFAULT;
ev_io example_watcher;
ev_io_init(&example_watcher, example_cb, sockfd, EV_READ);
ev_io_start(loop, &example_watcher);
// used to send the request 2 sec later
ev_timer delay_watcher;
ev_timer_init(&delay_watcher, delay_cb, 2, 0.0);
ev_timer_start(loop, &delay_watcher);
ev_run(loop, 0);
return 0;
}
Edit: Code updated with suggestions from comments
The source of the problem is that you do not check recved == 0 condition which corresponds to the other side closing the connection. When that happens the OS sets the socket into "closed mode" which (at least under linux) is always ready for reading and subsequent calls to recv will always return 0.
So what you need to do is to check for that condition, call close(fd); on the file descriptor (possibly with shutdown before) and ev_io_stop on the associated watcher. If you wish to continue at that point then you have to open a new socket and eo_io_start new watcher.
it may be a strange request, but, actually, I would like make my call to recv() blocking.
It appears that it should be blocking by default, but for me, it is not, and I can't find any way to make it.
I have a very simple server application, "open socket - bind - wait for connection - receive" on one hand,
and, on the other hand, a simple "open socket - connect - send" client application.
They communicate on LocalHost.
Server :
int clTcpServer::openSocket(void)
{
this->__iLocalSocketId = socket(AF_INET, //stand for IPV4
SOCK_STREAM, //using TCP protocol
0); //no specified / ip protocol
return 0;
}
int clTcpServer::openServer(uint16_t u16Port)
{
this->__strServerInfo.sin_family = AF_INET; // stand for IPV4
this->__strServerInfo.sin_addr.s_addr = INADDR_ANY; // Listen to any address (no filter)
this->__strServerInfo.sin_port = htons(u16Port);
if(bind(this->__iLocalSocketId, (sockaddr *)&this->__strServerInfo, sizeof(this->__strServerInfo)) < 0)
{
std::cout << "TCP server - ERROR - Can't bind socket to port " << std::dec << (int)u16Port << std::endl;
return -1;
}
std::cout << "TCP server - Server opened" << std::endl;
listen(this->__iLocalSocketId, 3);
return 0;
}
int clTcpServer::waitForConnection(void)
{
int iClientIpInfo;
char* pcClientIp;
std::cout << "Waiting for connection ..." << std::endl;
iClientIpInfo = sizeof(struct sockaddr_in);
this->__iDistantSocketId = accept(this->__iLocalSocketId,
(sockaddr *)&this->__strClientInfo,
(socklen_t*)&iClientIpInfo);
if(this->__iDistantSocketId < 0)
{
std::cout << "TCP server - ERROR - Connection refused" << std::endl;
return -1;
}
pcClientIp = inet_ntoa(this->__strClientInfo.sin_addr);
std::cout << "Connection accepted from " << pcClientIp << std::endl;
return 0;
}
int clTcpServer::receiveDataBuffer(void * pvidBuffer, size_t sizeMaxBufferSize)
{
int iRecvSize;
iRecvSize = recv(this->__iDistantSocketId, pvidBuffer, sizeMaxBufferSize, (int)0);
if (iRecvSize < (int)0 )
{
std::cout << "TCP server - ERROR - Recv failed" << std::endl;
return -1;
}
return iSentSize;
}
Client :
int clTcpClient::openSocket(void)
{
/* Create a new socket for client comm. */
this->__iSocketId = socket( AF_INET, //stand for IPV4
SOCK_STREAM, //using TCP protocol
0); //no specified / ip protocol
if (this->__iSocketId == (int)-1)
{
std::cout << "TCP client - ERROR - Can't open Socket" << std::endl;
return -1;
}
return 0;
}
int clTcpClient::connectToServer(std::string stgIpAddress, uint16_t u16Port)
{
/* configure server info according to parameters */
this->__strServerInfo.sin_family = AF_INET; //stand for IPV4
this->__strServerInfo.sin_port = htons(u16Port);
/* Retrieve IP addr*/
this->__strServerInfo.sin_addr.s_addr = inet_addr(stgIpAddress.c_str());
/* Try to connect to the server */
if (connect(this->__iSocketId,
(sockaddr*)&this->__strServerInfo,
sizeof(this->__strServerInfo)) < 0)
{
std::cout << "TCP client - ERROR - Can't connect to " << stgIpAddress << ":" << std::dec << (int)u16Port << std::endl;
return -1;
}
std::cout << "TCP client - Connected" << std::endl;
return 0;
}
int clTcpClient::sendDataBuffer(const void *kpvidData, size_t sizeDataLength)
{
ssize_t sizeSentSize;
/* Send the buffer */
sizeSentSize = send(this->__iSocketId, kpvidData, sizeDataLength, (int)0);
if (sizeSentSize < (ssize_t)0 )
{
std::cout << "TCP client - ERROR - Send failed" << std::endl;
return -1;
}
std::cout << "TCP client - " << (int)sizeSentSize << " byte(s) sent." << std::endl;
return 0;
}
This is working fine, but recv() here is not blocking. If I call it (after having initialized all the stuff, of course), if there is nothing to receive, the function returns 0, as "0 byte received", and that's all.
It means If I want to put this recv function in a loop, CPU is working 100% to receive ... nothing.
What's wrong here ?How can I make it blocking ?
Thank you
I am completely new to Winsock and have been trying to write a small HTTP server listening on localhost for educational purposes mainly. Currently the server simply returns a web page to whoever connects to it, without parsing any request.
Logically, I must always be listening for new connections on the listening port (I chose 81 here) after I finish with a client and close the connection, so I've googled quite a bit and found that I should probably be using SO_REUSEADDR for this purpose, but maybe I got it wrong. I am using Firefox as the client.
The first connection always goes without a hitch. However, the second time a client attempts to connect, the accept function doesn't seem to accept the connection. On the other hand, I can see that a connection IS established at that time using a utility that watches local ports (CurrPorts). I've looked for hours for a solution and have tried to make the socket non-blocking, but no luck. What did I do wrong?
#pragma comment(lib,"Ws2_32.lib")
#include <WinSock2.h>
#include <iostream>
#include <thread>
#include <string>
#include <array>
#include <ctime>
#include <winerror.h>
inline std::string getAddress(sockaddr_in* sin)
{
std::string res = std::to_string(sin->sin_addr.S_un.S_un_b.s_b1) + '.' + std::to_string(sin->sin_addr.S_un.S_un_b.s_b2) + '.' + std::to_string(sin->sin_addr.S_un.S_un_b.s_b3) + '.' + std::to_string(sin->sin_addr.S_un.S_un_b.s_b4);
return res;
}
void acceptTCP(SOCKET& origSock)
{
SOCKET tempSock = SOCKET_ERROR;
struct sockaddr* sa = new sockaddr();
int size = sizeof(*sa);
while (tempSock == SOCKET_ERROR)
{
tempSock = accept(origSock, sa, &size);
int err = WSAGetLastError();
if (err != 0 && err != WSAEWOULDBLOCK) std::cout << "\r\n" << err;
}
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
std::cout << "\r\nConnected to " << getAddress(sin) << ":" << htons(sin->sin_port);
origSock = tempSock;
}
int closeSocket(SOCKET socket)
{
shutdown(socket, 2); //I've tried using 0
std::clock_t start = std::clock();
char buf[1];
while ((std::clock() - start) / (double)CLOCKS_PER_SEC < 5)
{
int res = recv(socket, buf, strlen(buf), IPPROTO_TCP);
//std::cout << "\r\n" << res;
bool br = false;
switch (res)
{
case 0: br = true; break; //client closed connection
case -1:
{
int err = WSAGetLastError();
if (err != WSAEWOULDBLOCK && err != WSAEINTR) //client closed connection
{
br = true;
break;
}
else std::cout << "\r\nError on close socket: " << err;
}
default: exit(1); //data is being sent after shutdown request
};
if (br) break;
//if (res == -1) std::cout << ": " << WSAGetLastError();
//else std::cout << ": " << buf;
//Sleep(1000);
}
return closesocket(socket);
}
int main()
{
WSADATA WsaDat;
if (WSAStartup(MAKEWORD(1, 1), &WsaDat) != 0) std::cout << "???";
while (true)
{
SOCKET socket0 = socket(AF_INET, SOCK_STREAM, 0);
if (socket0 == INVALID_SOCKET) std::cout << "Invalid socket!";
struct sockaddr_in saServer;
saServer.sin_family = AF_INET;
saServer.sin_port = htons(81);
saServer.sin_addr.S_un.S_un_b.s_b1 = 127;
saServer.sin_addr.S_un.S_un_b.s_b2 = 0;
saServer.sin_addr.S_un.S_un_b.s_b3 = 0;
saServer.sin_addr.S_un.S_un_b.s_b4 = 1;
int enable = 1;
if (setsockopt(socket0, SOL_SOCKET, SO_REUSEADDR, (const char*)&enable, sizeof(int)) < 0)
std::cout << "setsockopt(SO_REUSEADDR) failed";
u_long iMode = 1;
ioctlsocket(socket0, FIONBIO, &iMode);
if (bind(socket0, (SOCKADDR*)&saServer, sizeof(saServer)) == SOCKET_ERROR) std::cout << "\r\nSocket Error " << WSAGetLastError();
else std::cout << "Socket bound!";
listen(socket0, 1);
std::thread threadConnection(&acceptTCP, std::ref(socket0)); //I use a thread in case I will want to handle more than one connection at a time in the future, but it serves no purpose here
threadConnection.join();
std::string content = "<!DOCTYPE html><html><head><title>test</title></head><body><p>test</p></body></html>";
std::string response = "HTTP/1.1 200 OK\r\nServer: myServer\r\nContent-Type: text/html\r\nConnection: close\r\nContent-Length: " + std::to_string(content.length()) + "\r\n\r\n" + content;
std::cout << "\r\n" << send(socket0, response.c_str(), strlen(response.c_str())*sizeof(char), 0);
Sleep(1000);
std::cout << "\r\n" << closeSocket(socket0);
}
WSACleanup();
}
Here's how your code should work:
Main function:
Open listening socket.
Bind it.
Call listen.
Call accept.
Dispatch a thread to handle the socket we just accepted.
Go to step 4.
Note that the thread never touches the listening socket.
I'm trying to create a web crawler, and I want it to be able to connect to web sites through a local proxy.
So, let's say that we want to send a GET message to google and retrieve it's HTML code, all this through a local proxy (i'm working at my university and there is a proxy to connect to external sites like google).
This is my code:
#include <iostream>
#include <cstring> // Needed for memset
#include <sys/socket.h> // Needed for the socket functions
#include <netdb.h> // Needed for the socket functions
#include <cstdlib>
#include <string>
using namespace std;
int main(int argc, char* argv[])
{
addrinfo host_info; // The struct that getaddrinfo() fills up with data.
addrinfo *host_info_list;
int socketfd;
char* msg = NULL;
char* msg2 = NULL;
int status;
int len;
memset(&host_info, 0, sizeof host_info);
host_info.ai_family = AF_INET;//AF_UNSPEC;
host_info.ai_socktype = SOCK_STREAM;
//PROXY IP = proxy.feng.edu.uy ; PORT = 3318; //HTTP1.0 proxy
status = getaddrinfo("proxy.feng.edu.uy", "3318", &host_info, &host_info_list);
socketfd = socket(host_info_list->ai_family, host_info_list->ai_socktype,
host_info_list->ai_protocol);
if (socketfd == -1) std::cout << "ERROR: socket error " << std::endl ;
std::cout << "Connect()ing..." << std::endl;
status = connect(socketfd, host_info_list->ai_addr, host_info_list->ai_addrlen);
if (status == -1) std::cout << "ERROR: connect error" << std::endl ;
msg = new char[200];
strcpy(msg,"CONNECT www.google.com HTTP/1.0\r\n");
strcat(msg,"\r\n");
ssize_t bytes_sent;
len = strlen(msg);
bytes_sent = send(socketfd, msg, len, 0);
ssize_t bytes_recieved=0;
std::cout << "Waiting to recieve data..." << std::endl;
char* incoming_data_buffer = new char[200];
bytes_recieved = recv(socketfd, incoming_data_buffer,200, 0);
if (bytes_recieved == 0) std::cout << "host shut down." << std::endl ;
if (bytes_recieved == -1)std::cout << "ERROR: receive error!" << std::endl ;
std::cout << bytes_recieved << " bytes recieved" << std::endl ;
std::cout << incoming_data_buffer << std::endl;
msg2 = new char[300];
strcpy(msg2,"GET http://www.google.com/ HTTP/1.0\r\n\r\n");
std::cout << "Message sent to google: " << msg2 << std::endl;
len = strlen(msg2);
bytes_sent = send(socketfd, msg2, len, 0);
cout << "bytes_sent: " << bytes_sent << endl;
bytes_recieved=0;
std::cout << "Waiting to recieve data ..." << std::endl;
char* incoming_data_buffer2 = new char[1000];
bytes_recieved = recv(socketfd, incoming_data_buffer2,1000, 0);
if (bytes_recieved == 0) std::cout << "host shut down." << std::endl ;
if (bytes_recieved == -1)std::cout << "ERROR: recieve error!" << std::endl ;
std::cout << bytes_recieved << " bytes recieved" << std::endl ;
std::cout << incoming_data_buffer2 << std::endl;
return 0;
}
The problem I'm having is as follows..
First, incoming_data_buffer (which is the buffer from the "CONNECT") returns: "HTTP1.0 200 connection established", which is good, no problems until now.
Next I send the "GET" message to the proxy so that it forwards the message to google as expected(now that the connection is established), and it stays idle in the "recv()" for 1 minute or so, then it returns 0 (which means that the connection is closed i guess) and the buffer is empty...
My problem is that i have no clue why recv() returns 0... Any ideas?? It supposedly means the connection is closed, but why then? what else do i have to do in order for the proxy to maintain the connection? (supposing that a "connection closed" is the problem).
Thanks in advance!
The CONNECT method is an HTTP tunneling feature. A proxy that supports it may limit its use to connecting to HTTPS web sites (source: Wikipedia -- HTTP tunnel). You are trying to establish a connection with a standard HTTP server through CONNECT, which the proxy may be blocking.
After you establish a connection with your proxy, instead of establishing a tunnel, just send your request. This will work since you are using an absoluteURI to specify your GET target.