// Run like this: simple_client address port // Results in argv ["./simple_client", "address", "port"] #include #include #include #include #include #include #include #include int main(int argc, char ** argv){ if(argc < 3){ printf("Usage: %s hostname port\n", argv[0]); return 1; } struct sockaddr_in sad; sad.sin_port = htons(atoi(argv[2])); sad.sin_family = AF_INET; int skt = socket(AF_INET, SOCK_STREAM, 0); // Same as a server // do a dns lookup /* This is kind of lengthy: * 1. gethostbyname will return a pointer to a hostent structure * 2. The hostent structure (entry) will contain a list of addresses * 3. We'll take the first address, and call it c_addr * 4. Then we copy c_addr into sin.sin_addr, our socket address structure * * Couple notes: * - gethostbyname will return a pointer into static memory. We don't need * to free it, and we shouldn't free it, and if we were to call gethostbyname * again we'd end up overwriting it. We could copy the structure if we want, * but it does contain pointers, so we'd need to carefully do a deep copy. * Easiest approach is to just extract what we need from it and move on. * - Most of the time, we just want the first thing in the list. In fact, * most servers will only resolve to one IP address anyway. So we can ignore * the rest of the list. * - The code I wrote here assumes that h_addr_list is a list of struct in_addr, * but this might not be true if there is an IPv6 entry in the list or something. * h_addrtype will tell us if it's an IPv4 or IPv6 address, and thus if we should * actually be using struct in_addr6. I'm just assuming IPv4 here. * - gethostbyname is marked depricated on Debian Bookworm, but not on earlier * Linux versions (like bullseye, on isoptera) or on FreeBSD, so I think maybe * Debian is ahead of the curve here (although it's actually just following * POSIX.1-2008). The expectation is that people will move to getnameinfo * from manual section 3 instead. Perhaps I should update my demos! */ struct hostent* entry = gethostbyname(argv[1]); if(!entry){ if(h_errno == HOST_NOT_FOUND){ printf("This is our own message that says the host wasn't found\n"); } herror("gethostbyname"); return 1; } struct in_addr **addr_list = (struct in_addr**)entry->h_addr_list; struct in_addr* c_addr = addr_list[0]; char* ip_string = inet_ntoa(*c_addr); sad.sin_addr = *c_addr; // copy the address we found into sad // Finally done with DNS! printf("Connecting to: %s\n", ip_string); /* Servers do bind, listen, accept. Clients do connect. * This makes the socket active, not passive, and attempts to connect * to the specified IP address and port. */ if( connect(skt, (struct sockaddr*)&sad, sizeof(struct sockaddr_in)) ){ perror("connect"); return 1; } char message[64]; /* Unlike a server, we just have one socket as a client. So we use skt * to send and receive, even though a server would have used the client * sockets instead (client_fd in most of the demos I wrote). */ size_t actual_length = read(skt, message, 64); printf("Received message(%lu bytes): %s\n", actual_length, message); write(skt, "Hello", 6); /* This'll do a TCP disconnect. It's nice to actually disconnect so the server * knows we've left, and not died or fallen asleep or whatever. */ close(skt); return 0; }