Introduction to Socket programming under Linux

Posted by treilad on Wed, 23 Feb 2022 15:51:56 +0100

1. Network byte order and host byte order

Network byte order is a data representation format specified in TCP/IP. It has nothing to do with the specific CPU type and operating system, so as to ensure that the data can be correctly interpreted when transmitted between different hosts. The network byte order adopts the big endian sorting method.

Different CPU s have different byte order types. These byte orders refer to the order in which integers are stored in memory. This is called host byte order, which has two types: large end and small end.

htons()--"Host to Network Short"

htonl()--"Host to Network Long"

ntohs()--"Network to Host Short"

ntohl()--"Network to Host Long"

2. Several structures

struct sockaddr {

  unsigned short sa_family; /* Address family, AF_xxx */

  char sa_data[14]; /*14 Byte Protocol address*/

};
struct sockaddr_in {

  short int sin_family; /* Communication type */

  unsigned short int sin_port; /* port */

  struct in_addr sin_addr; /* Internet address */

  unsigned char sin_zero[8]; /* Same length as sockaddr structure*/

};
struct in_addr {

  unsigned long s_addr;

};

3,inet_addr and inet_ntoa

Function inet_addr() converts the IP address from a string to an unsigned long integer. Note that INET_ The address returned by addr() is already in network byte format

ina.sin_addr.s_addr = inet_addr("132.241.5.10");

inet_ntoa() takes the structure in addr as a parameter instead of long shaping. Also note that it returns a pointer to a character

printf("%s",inet_ntoa(ina.sin_addr));

4. socket() function

int socket(int domain, int type, int protocol);

Domain: protocol domain, also known as protocol family. The commonly used protocol family is AF_INET

Type: Specifies the socket type. The commonly used socket types are SOCK_STREAM,SOCK_DGRAM,SOCK_RAW, etc. (what are the types of sockets?).

Protocol: so the name means to specify the protocol. When the protocol is 0, the default protocol corresponding to the type will be automatically selected.

5. bind() function

Bind the socket to the specified IP address and port number

int bind(int sockfd, struct sockaddr *my_addr, int addrlen);

6,connect()

serv_addr is the data structure struct sockaddr that holds the destination port and IP address

As for the client, the kernel will automatically select an appropriate port number

int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);

7,listen()

After bind(), use listen to listen for the bound port number

int listen(int sockfd, int backlog);

backlog: the maximum number of incoming connections queued by the kernel for this socket interface. Incoming connections are those that wait in the queue until accept()

8. accept() function

What the accept() function actually does is to return the next completed connection at the column head of the completed connection queue. The three-way handshake of the server is automatically completed by the kernel after the listen() function and before accept().

The function returns the SOCKADDR of the client through the last two parameters_ In structure and length

The return value is a new socket file descriptor, so there are two sockets. The original one is still listening on your port, and the new one is preparing to send (send()) and receive (recv()) data

int accept(int sockfd, void *addr, int *addrlen);

9. send() and recv() functions

These two functions are used for the communication of streaming socket or datagram socket. flag is generally 0.

int send(int sockfd, const void *msg, int len, int flags);
int recv(int sockfd, void *buf, int len, unsigned int flags);

If you use connect() to connect a datagram socket, you can simply call send() and recv() to meet your requirements

10. sendto() and recvfrom() functions

Datagram socket usage

int sendto(int sockfd, const void *msg, int len, unsigned int flags, 

const struct sockaddr *to, int tolen);
int recvfrom(int sockfd, void *buf, int len, unsigned int flags,  

struct sockaddr *from, int *fromlen);

11. getpeername() function

The function getpeername() tells you who is on the other side of the connected streaming socket

int getpeername(int sockfd, struct sockaddr *addr, int *addrlen);

12. gethostname() function

Returns the host name of the machine on which your program is running

int gethostname(char *hostname, size_t size);

13. gethostbyname() function

Mostly used for clients. DNS domain name service

struct hostent *gethostbyname(const char *name);
struct hostent {

  char *h_name;

  char **h_aliases;

  int h_addrtype;

  int h_length;

  char **h_addr_list;

};  

14. TCP example

The server:

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define MYPORT 3490 / * define user connection port*/

#define BACKLOG 10 / * how much to wait for connection control*/

main()

{

  int sockfd, new_fd; /* listen on sock_fd, new connection on new_fd */

  struct sockaddr_in my_addr; /* my address information */

  struct sockaddr_in their_addr; /* connector's address information */

  int sin_size;

  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

  perror("socket");

  exit(1);

  }

  my_addr.sin_family = AF_INET; /* host byte order */

  my_addr.sin_port = htons(MYPORT); /* short, network byte order */

  my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */

  bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */

 

  if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr))== -1) {

  perror("bind");

  exit(1);

  }

  if (listen(sockfd, BACKLOG) == -1) {

  perror("listen");

  exit(1);

  }

 

  while(1) { /* main accept() loop */

  sin_size = sizeof(struct sockaddr_in);

  if ((new_fd = accept(sockfd, (struct sockaddr *)&their_addr, &sin_size)) == -1) {

  perror("accept");

  continue;

  }

  printf("server: got connection from %s\n", \

  inet_ntoa(their_addr.sin_addr));

  if (!fork()) { /* this is the child process */

  if (send(new_fd, "Hello, world!\n", 14, 0) == -1)

  perror("send");

  close(new_fd);

  exit(0);

  }

  close(new_fd); /* parent doesn't need this */

  while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */

}

}

client:

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define PORT 3490 / * the port on which the client connects to the remote host*/

#Define MAXDATA Size 100 / * maximum bytes that can be received at a time*/

int main(int argc, char *argv[])

{

int sockfd, numbytes;

char buf[MAXDATASIZE];

struct hostent *he;

struct sockaddr_in their_addr; /* connector's address information */

if (argc != 2) {

fprintf(stderr,"usage: client hostname\n");

exit(1);

}

if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */

herror("gethostbyname");

exit(1);

}

if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

perror("socket");

exit(1);

}

their_addr.sin_family = AF_INET; /* host byte order */

their_addr.sin_port = htons(PORT); /* short, network byte order */

their_addr.sin_addr = *((struct in_addr *)he->h_addr);

bzero(&(their_addr.sin_zero),; /* zero the rest of the struct */

if(connect(sockfd,(struct sockaddr *)&their_addr,sizeof(struct sockaddr)) == -1) {

perror("connect");

exit(1);

}

if ((numbytes=recv(sockfd, buf, MAXDATASIZE, 0)) == -1) {

perror("recv");

exit(1);

}

buf[numbytes] = '\0';

printf("Received: %s",buf);

close(sockfd);

return 0;

}

15. UDP example

The server:

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define MYPORT 4950 /* the port users will be sending to */

#define MAXBUFLEN 100

main()

{

int sockfd;

struct sockaddr_in my_addr; /* my address information */

struct sockaddr_in their_addr; /* connector's address information */

int addr_len, numbytes;

char buf[MAXBUFLEN];

if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {

perror("socket");

exit(1);

}

my_addr.sin_family = AF_INET; /* host byte order */

my_addr.sin_port = htons(MYPORT); /* short, network byte order */

my_addr.sin_addr.s_addr = INADDR_ANY; /* auto-fill with my IP */

bzero(&(my_addr.sin_zero),; /* zero the rest of the struct */

if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {

perror("bind");

exit(1);

}

addr_len = sizeof(struct sockaddr);

if ((numbytes=recvfrom(sockfd, buf, MAXBUFLEN, 0,     \

(struct sockaddr *)&their_addr, &addr_len)) == -1) {

perror("recvfrom");

exit(1);

}

printf("got packet from %s\n",inet_ntoa(their_addr.sin_addr));

printf("packet is %d bytes long\n",numbytes);

buf[numbytes] = '\0';

printf("packet contains \"%s\"\n",buf);

close(sockfd);

}

client:

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <sys/socket.h>

#include <sys/wait.h>

#define MYPORT 4950 /* the port users will be sending to */

int main(int argc, char *argv[])

{

int sockfd;

struct sockaddr_in their_addr; /* connector's address information */

struct hostent *he;

int numbytes;

 

if (argc != 3) {

fprintf(stderr,"usage: talker hostname message\n");

exit(1);

}

 

if ((he=gethostbyname(argv[1])) == NULL) { /* get the host info */

herror("gethostbyname");

exit(1);

}

if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1) {

perror("socket");

exit(1);

}

their_addr.sin_family = AF_INET; /* host byte order */

their_addr.sin_port = htons(MYPORT); /* short, network byte order */

their_addr.sin_addr = *((struct in_addr *)he->h_addr);

bzero(&(their_addr.sin_zero),; /* zero the rest of the struct */

if ((numbytes=sendto(sockfd, argv[2], strlen(argv[2]), 0, \

(struct sockaddr *)&their_addr, sizeof(struct sockaddr))) == -1) {

perror("sendto");

exit(1);

}

printf("sent %d bytes to %s\n",numbytes,inet_ntoa(their_addr.sin_addr));

close(sockfd);

return 0;

}

16. Block

Many functions use blocking. accept() is blocked, and all recv * () functions are blocked. They can do so because they are allowed to do so. When you first call socket() to create a socket descriptor, the kernel sets it as blocking. If you don't want socket blocking, you need to call the function fcntl():

#include <unistd.h>
#include <fontl.h>
......
sockfd = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sockfd, F_SETFL, O_NONBLOCK);
......

Let your program query the socket data in the busy state, which will waste a lot of CPU time. A better solution is to use the select() described below to query whether there is data to read in.

17. select() -- multiplex I/O

select() allows you to monitor multiple sockets at the same time. If you want to know, it will tell you which socket is ready to read, which is ready to write, and which socket has an exception.

If you have a socket listening (), you can see if there is a new connection by adding the file descriptor of the socket to the readfds set

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

int select(int numfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);

numfds is an integer value, which refers to the range of all file descriptors in the set, that is, the maximum value of all file descriptors plus 1

fd_set can be understood as a collection, which stores file descriptors, that is, file handles. fd_set set can be operated manually through some macros. Here are some macros to operate on this type:

FD_ZERO(fd_set *set) – Clear a collection of file descriptors
FD_SET(int fd, fd_set *set) - add to fd To set
FD_CLR(int fd, fd_set *set) – Remove from collection fd
FD_ISSET(int fd, fd_set *set) – test fd Is it in the collection

Struct timeout is used to represent the time value. It has two members, one is the number of seconds and the other is the number of microseconds. If NULL is passed in as a formal parameter, that is, the time structure is not passed in, that is, the select is placed in the blocking state until a file descriptor in the monitored file descriptor set changes; Second, if the time value is set to 0 seconds and 0 milliseconds, it will become a pure non blocking function. No matter whether the file descriptor changes or not, it will immediately return to continue execution. If the file does not change, it will return 0, and if there is change, it will return a positive value; Third, the timeout value is greater than 0, which is the timeout time of waiting, that is, select blocks within the timeout time, and returns when an event arrives within the timeout time. Otherwise, it must be returned anyway after the timeout.