Basic Socket Programming

Tram Ho

1. Structure address

1.1. Struct sockaddr{}

This structure is used most of the system calls in part 2, in which:

  • sa_family is the address family, has the form AF_xxxx, mainly we use AF_INET
  • sa_data[]: store destination address and port

1.2. Struct sockaddr_in{}

As a parallel structure to struct sockaddr{}, because storing and retrieving data in struct sockaddr{} is quite complicated, we use this structure.

For some funcs that require the above structure, you can use the sockaddr_in structure and then proceed to cast.

1.3. Structure hostent{}

Host data storage structure, mainly used in converting between ip address and host (DNS), used in part 4 – Converting between host name and IP address. Featured are the following two functions:

struct hostent* gethostbyname(const char* hostname)

struct hostent* gethostbyaddr(const char* addr, size_t len, int family)

2. Data processing

2.1. Convert between host byte and network byte

Data is stored in 2 types Big-endian and Little-endian.

In socket programming, there are two names for storing data, Host byte order and Network byte order (IP), corresponding to Little-endian and Big-endian. Use Network byte order for data that needs to be transmitted through different servers. So need to convert between these two data types by the following functions:

  • htons() — “Host to Network Short”
  • htonl() — “Host to Network Long”
  • ntohs() — “Network to Host Short”
  • ntohl() — “Network to Host Long”

In which Short performs conversion with type 2 bytes (using conversion for port), Long is 4 bytes (using conversion for network).

For example, I have port 64, which needs to be stored in the sin_port variable in the struct sockaddr_sin , and of course needs to be stored in the Network byte order form because this data is being transmitted.

64 has a hex value of 0x40, or in 2-byte form it is written as 0x0040. Perform the conversion sin_addr = htons(64), this function converts from little to big, so its hex value will be 0x4000 and has a value of 16384. That’s why when I run the following program it will be terminated The result is 16384. In simpler terms, both conversion types are byte inversions, but we have two different funcs to understand the conversion type.

2.2. Convert host string to host address and vice versa

inet_addr()

inet_aton()

This function converts const char *cp (string address) to numbers-and-dots format, stored in struct in_addr that we use in struct sockaddr_in

Example:

inet_ntoa

Convert numbers_and_dots to string address

Example

3. System calls

Reference source: https://www.gta.ufrj.br/ensino/eel878/sockets/syscalls.html

3.1. Socket()

Creates a socket, returns the file descriptor, with the smallest value that has not been used. Equals -1 on failure.

  • domain: use PF_INET
  • type: SOCK_STREAM or SOCK_DGRAM
  • protocol: 0 to automatically select the appropriate protocol

See also by: man socket

3.2. Bind()

When a socket is created, there is no value assigned to the socket (ipaddress, port), bind() is used to do that. See more at man bind

  • sockfd: sock file descriptor
  • struct sockaddr *my_addr:
  • addrlen : size of address, can use sizeof(struct sockaddr)

Example:

3.3. Connect()

Connect to 1 remote server

  • sockfd: sock file desciptor
  • struct sockaddr *serv_addr: Structure to store the connection destination and address
  • addrlen: address size, use sizeof(struct sockaddr)

This function will return -1 if an error is encountered.

Example:

3.4. Listen()

Wait for remote connections to arrive and process

  • sockfd: sock file descriptor
  • backlog: number of incoming connections on the queue to be processed

Returns fd or the value -1 if an error is encountered

Because the listen() function receives a connection from another host, it is necessary to set the port that we receive. So the process would be:

  • socket() => bind() => listen() => accept()

We will talk about the accept() function below.

3.5. Accept()

Accept incoming connection and return a file desciptor (same value as fd of listen), used to send and receive data with 2 func send() and recv().

  • sockfd: fd of listen()
  • struct sockaddr *addr: pointer to struct sockaddr_in address storage structure
  • socklen_t *addrlen: pointer to the storage variable sizeof(struct sockaddr_in)

Returns fd with use for send() and recv() below

Example

3.6. Send() and Recv()

Send()

  • sockfd: fd of the host that wants to send data, can be the fd returned by socket() or accept()
  • const void *msg: data to send
  • int len: The size of the data in bytes
  • int flags: Set the value to 0

The function returns the number of bytes sent, sending all if the number of bytes is less than 1K. Compare the return value with int len ​​to see if the data has been sent and process the rest.

Recv()

Get data sent to

  • sockfd: fd of sender
  • void *buf: buffer containing data
  • int len: maximum input size
  • flags set to 0

The function returns -1 if an error occurs, 0 if the connection is closed and the other value is the number of bytes received

3.7. Close() and shutdown()

Close()

close(sockfd)

  • sockfd: fd wants to close the connection, stop sending and receiving data

shutdown()

  • sockfd: fd wants to close the connection
  • how: how to close (0-stop receiving, 1-stop sending, 2-stop receiving and sending)

4. Switch between Host name and IP address

5. Error handling in socket

To print errors while connecting or using functions, we use the perror() and herror() in the errno.h library.

For example, when an error returns -1 in the socket() function, we print the error by perror(“socket”); Example:

For the DNS handling functions (in part 4), use the herror function to print the error. Example:

Share the news now

Source : Viblo