0

Kinda new to C# asynchronous/network programming, trying to write a program that fetches a bunch of GET request responses using plain TCP sockets, however I've noticed that most of the time, my callback functions are not being called (and on some occasions they are, which is even weirder).

Here's my code:

class CallbackService
{
    private static ConcurrentDictionary<String, String> results;
    private List<string> urlList;
    private const int PORT = 80; 
    public CallbackService(List<string> list)
    {
        urlList = list;
        results = new ConcurrentDictionary<string, string>(urlList.Count * 2, urlList.Count);
    }

    public void Run()
    {
        foreach (string url in urlList)
        {
            Work(url);
        }
    }

    private static void Work(string url)
    {
        string host = Utility.getHostFromURL(url);
        string resource = Utility.getResourceFromURL(url);
        IPAddress ipAddress = Utility.getIPFromHost(host);
        IPEndPoint endpoint = new IPEndPoint(ipAddress, PORT);
        Socket socket = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
        RequestState state = new RequestState(socket, endpoint, host, resource);
        state.socket.BeginConnect(state.endpoint, ConnectCallback, state);
    }

    private static void ConnectCallback(IAsyncResult connectState)
    {
        RequestState state = (RequestState)connectState.AsyncState;
        state.socket.EndConnect(connectState);
        string request = Utility.getRequestString(state.host, state.resource);
        Byte[] requestBytes = Encoding.ASCII.GetBytes(request);
        state.socket.BeginSend(requestBytes, 0, request.Length, 0, SendCallback, state);
    }

    private static void SendCallback(IAsyncResult sendState)
    {
        RequestState state = (RequestState)sendState.AsyncState;
        int bytesSent = state.socket.EndSend(sendState);
        state.socket.BeginReceive(state.data, 0, state.data.Length, 0, ReceiveCallback, state);
    }

    private static void ReceiveCallback(IAsyncResult sendState)
    {
        RequestState state = (RequestState)sendState.AsyncState;
        int responseSize = state.socket.EndReceive(sendState);
        string response = Encoding.ASCII.GetString(state.data, 0, responseSize);
        results.TryAdd(state.host, response);
        state.socket.Shutdown(SocketShutdown.Both);
        state.socket.Close();
    }

    public void PrintResults()
    {
        foreach (KeyValuePair<string, string> entry in results)
        {
            Console.WriteLine("Result from host " + entry.Key + ": " + entry.Value);
        }
    }

The utility functions (pretty sure they're working fine):

class Utility
{
    public static string getHostFromURL(string url)
    {
        return url.Split("/")[0];
    }

    public static string getResourceFromURL(string url)
    {
        var split = url.Split("/", 2);
        return split.Length > 1 ? split[1] : "/";      
    }

    public static IPAddress getIPFromHost(string host)
    {
        return Dns.GetHostEntry(host).AddressList[0];
    }

    public static string getRequestString(string host, string resource)
    {
        return "GET " + resource + " HTTP/1.1\r\n" +
               "Host: " + host + "\r\n" +
               "User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.102 Safari/537.36\r\n" +
               "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,#1#*;q=0.8\r\n" +
               "Accept-Language: en-US,en;q=0.9,ro;q=0.8\r\n" +
               "Accept-Encoding: gzip, deflate\r\n" +
               "Upgrade-Insecure-Requests: 1\r\n" +
               "Pragma: no-cache\r\n" +
               "Cache-Control: no-cache\r\n" +
               "Content-Length: 0\r\n\r\n";
    }
}

And the state class:

class RequestState
{
    public RequestState(Socket socket, IPEndPoint endpoint, string host, string resource)
    {
        this.socket = socket;
        this.endpoint = endpoint;
        this.host = host;
        this.resource = resource;
        this.data = new byte[2048];
    }

    public Socket socket;
    public IPEndPoint endpoint;
    public string host;
    public string resource;
    public byte[] data;
}

So generally, ConnectCallback will not be called at all, but sometimes it will (but then the same thing happens with SendCallback). Not sure what could be going wrong. From what I've noticed, the domain names are being resolved to the correct addresses. What am I missing here?

  • Why are you closing the connection after each message is received. Normally with TPC you keep the connection open. See msdn examples : https://learn.microsoft.com/en-us/dotnet/framework/network-programming/socket-code-examples. A socket is TCP and your code is HTTP. HTTP uses TCP for the transport layer. so I'm a little bit confused what you are trying to do. – jdweng Nov 17 '20 at 11:32
  • @jdweng "Why are you closing the connection after each message is received. Normally with TPC you keep the connection open." Yeah I just wanted to get the program up and running to see if everything is working as intended. Obviously the intention is to receive the whole message from the server later on. "A socket is TCP and your code is HTTP. HTTP uses TCP for the transport layer. so I'm a little bit confused what you are trying to do" What do you mean? I'm sending an HTTP request on port 80 through a socket that uses TCP. Not sure what the problem is here. – string_loginUsername Nov 17 '20 at 11:44
  • I can't tell if you are trying to use TCP, HTTP, or implement HTTP with a TCP socket. When you use TCP you usually reference a SOCKET; and HTTP you use REQUEST/RESPONSE. You have everything in your code SOCKET/REQUEST/RESPONSE. So I'm not sure what you are trying to do. – jdweng Nov 17 '20 at 11:50
  • @jdweng So basically I want to open a socket connection on port 80 for each url in the url list, send a GET request to each, and get back the server response. I know there's probably more direct ways of doing HTTP requests but I want to do it in the most basic way possible using a plain TCP socket. That's why the GET request message is hardcoded too. I just want to get it to work (have the server responses saved in the concurrent dictionary. However the problem is, as I stated before, that `ConnectCallback`, the callback for `BeginConnect` is not being called. – string_loginUsername Nov 17 '20 at 14:50
  • See following : https://stackoverflow.com/questions/19523088/create-http-request-using-tcpclient – jdweng Nov 17 '20 at 16:19

0 Answers0