TCP/IP Network Programming Notes

Keywords: C network socket

Chapter 1 understanding network programming and sockets

1.1 understand network programming and sockets

1.1.1 network programming and socket overview

Network programming: write programs to enable two networked computers to exchange data with each other.

socket: a software device used for network data transmission.

Network programming is also called socket programming.

The telephone can be used to make or receive calls at the same time, but there is a difference between making and receiving calls for sockets. Let's first discuss the socket creation process for listening. Let's use the telephone to explain the creation of socket.

1.1.2 building a telephone socket

  • A conversation by calling the socket function (installing the telephone)

    Q: "what do you need to prepare for answering the phone?"

    A: "of course it's a telephone!"

    The telephone can be installed only when there is a telephone. Next, we will prepare a telephone. The following function creates a socket equivalent to a telephone:

      #include<sys/socket.h>
      int socket(int domain, int type, int protocol);
    

    The file descriptor is returned on success and - 1 on failure.

    When the telephone is ready, consider the assignment of telephone numbers so that others can contact themselves.

  • A conversation that occurs when the bind function is called (assign a phone number)

    Q: "what's your phone number, please?"

    A: "my phone number is 123-1234."

    The same is true for sockets. Just like assigning a telephone number to a telephone (although the telephone number is not really given to the telephone), use the following function to assign address information (IP address and port number) to the created socket:

      #include<sys/socket.h>
      int bind(int sockfd, struct sockaddr *myaddr, socklen_t addren);
    

    Returns 0 on success and - 1 on failure.

    After calling the bind function to assign an address to the socket, all the preparations for answering the phone are basically completed. Next, you need to connect the phone line and wait for an incoming call.

  • The conversation that occurs when the listen function is called (to connect a telephone line)

    Q: "after the telephone has been set up, do you only need to connect the telephone line?"

    A: "yes, you can answer the phone just by connecting. “

    When the telephone line is connected, the telephone will turn to the answering state. At this time, others can make a call and request to connect to the telephone. Similarly, it is necessary to convert the socket into the state that can receive the connection:

      #include<sys/socket.h>
      int listen(int sockfd, int backlog);
    

    Returns 0 on success and - 1 on failure.

    After connecting the telephone line, if someone makes a call, the bell will ring. Pick up the microphone to answer the phone.

  • The conversation that occurs when the accept function is called (pick up the microphone)

    Q: "the phone rings. What should I do? “

    Answer: "answer! “

    Picking up the microphone means accepting the other party's connection request. The same is true for sockets. If someone requests a connection in order to complete data transmission, you need to call the following function to accept it.

      #include<sys/socket.h>
      int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    

    The file descriptor is returned on success and - 1 on failure.

    The socket creation process of receiving requests in network programming can be sorted as follows:

    • Step 1: call the socket function to create a socket.

    • Step 2: call the bind function to assign the IP address and port number.

    • Step 3: call the listen function to change to the acceptable request state.

    • Step 4: call the accept function to accept the connection request.

1.1.3 writing server socket program

The server is a program that can accept connection requests. Next, build the server to verify the function call process mentioned earlier. After receiving the connection request, the server returns "Hello world" to the requester! "Reply. When reading the code, please focus on the calling process of sockets and related functions. You don't have to understand all the examples.

Server side code:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char * message);

int main(int argc, char * argv[ ])
{
	int serv_sock;
	int clnt_sock;
	
	struct sockaddr_in serv_addr;
	struct sockaddr_in clnt_addr;
	socklen_t clnt_addr_size;
	
	char message[] = "Hello World!";
	
	if(argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}
	serv_sock = socket(PF_INET, SOCK_STREAM, 0);	// Call the socket function to create a socket
	if(serv_sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_addr.sin_port = htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr *) &serv_addr,  sizeof(serv_addr)) == -1)	//Call the bind function to assign the IP address and port number
		error_handling("bind() error");
	if(listen(serv_sock, 5) == -1)	// Call the listen function to turn the socket into an acceptable connection state
	{
		error_handling("listen() error");
	}
	clnt_addr_size = sizeof(clnt_addr);
	clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);	// Call the accept function to accept the connection request
																					// Call this function when there is no connection request,
																					// It will not return until there is a connection and until there is a connection request
	if(clnt_sock == -1)
		error_handling("accept() error");
	
	write(clnt_sock, message, sizeof(message));	// The write function is used to transfer data. If the program executes to this line through line 40, it indicates that there is a connection request.
	close(clnt_sock);
	close(serv_sock);
	return 0;
}

void error_handling(char * message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

1.1.4 building a calling socket

  • The following describes the client socket used to request a connection. The client program has only two steps:

    1. Call the socket function to create a socket

    2. Call the connect function to send a connection request to the server

  • connect function:

      #include<sys/socket.h>
      int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addren);
    

    Returns 0 on success and - 1 on failure.

1.1.5 writing client socket program

  • Client source code:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>
void error_handling(char *message);

int main(int argc, char * argv[])
{
	int sock;
	struct sockaddr_in serv_addr;
	char message[30];
	int str_len;
	
	if(argc != 3)
	{
		printf("usage: %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock = socket(PF_INET, SOCK_STREAM, 0);		// Create a socket. At this time, the socket cannot be divided into server and client. If it is followed by
												// Calling the bind and listen functions will become a server-side socket; if calling the connect function
												//, will become a client socket.
	if(sock == -1)
		error_handling("socket() error");
	
	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
	serv_addr.sin_port = htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)	// Call the connect function to send a connection request to the server
		error_handling("connect() error!");
	
	str_len=read(sock, message, sizeof(message) - 1);
	if(str_len == -1)
	{
		error_handling("read() error!");
	}
	
	printf("Message from server : %s\n", message);
	close(sock);
	return 0;
}

void error_handling(char * message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

1.1.6 running on Linux platform

  1. Command to compile the hello_server.c example:

    gcc hello_server.c -o hserver
    ./hserver 9190			// 9190 is the open port number in the server
    

    Compile the hello_server.c file and generate the executable hsserver.

  2. Commands to compile the hello_client example:

    gcc hello_client.c -o hclient
    ./hclient 127.0.0.1 9190	// 127.0.0.1 and 9190 are the ip address and port number of the server, respectively
    

    At this point, the client will receive the string Hello World from the server!

1.2 Linux based file operation

For Linux, socket operation is no different from file operation, so it is necessary to understand the file in detail. In the Linux world, socket is also regarded as a kind of file, so it is natural to use the related functions of file I/O in the process of network data transmission.

1.2.1 underlying file access and file descriptor

  • File descriptor assigned to standard I / O and standard error

    File descriptorobject
    0Standard Input: Standard Input
    1Standard Output: Standard Output
    2Standard Error: Standard Error

    Files and sockets are usually assigned file descriptors after the creation process. File descriptors are sometimes called file handles, but "handle" is mainly a term in Windows.

1.2.2 open file

  • First, the function of opening a file to read and write data is introduced:

      #include<sys/types.h>
      #include<sys/stat.h>
      #include<fcnt1.h>
      
      int open(const char * path, int flag);	// Open file function
    

    The file descriptor is returned on success and - 1 on failure.

    This function needs to pass two parameters:

    • The first parameter: path is the open target file name and path information.
    • The second parameter: flag is the file open mode (file characteristic information).
  • File open mode:

    Open modemeaning
    O_CREATCreate files if necessary
    O_TRUNCDelete all existing data
    O_APPENDMaintain existing data and save to the back
    O_RDONLYRead only open
    O_WRONLYWrite only open
    O_RDWRRead write on

1.2.3 closing documents

  • The file must be closed after use. The following describes the functions called when closing the file:

      #include<unistd.h>
      int close(int fd);
    

    Returns 0 on success and - 1 on failure

    Parameter: fd, file descriptor of the file or socket to be closed.

    This function can close not only the file, but also the socket, which proves once again the characteristic of "Linux operating system does not distinguish between file and socket".

1.2.4 write data to file

  • The write function described next is used to output (transfer) data to a file. Linux does not distinguish between files and sockets, so this function is also used when transmitting data to other computers through sockets. Previous example hello_ It is also called in server. C to pass the string "Hello World!".

      #include<unistd.h>
      ssize_t write(int fd, const void * buf, size_t nbytes);
    

    The number of bytes written is returned on success and - 1 on failure.

    Parameter 1: fd, displays the file descriptor of the data transfer object.

    Parameter 2: buf, save the buffer address value of data to be transmitted.

    Parameter 3: nbytes, the number of bytes of data to be transmitted.

    In this function definition, size_t is the unsigned int type declared by typedef. Yes, ssize_ For T, size_ s added before t stands for signed, that is, ssize_t is the type of signed int declared by typedef.

  • Create a new file and save the data:

    For codes, see:

    #include<stdio.h>
    #include<stdlib.h>
    #include<fcntl.h>
    #include<unistd.h>
    void error_handling(char * message);
    
    int main(void)
    {
       int fd;
       char buf[] = "Let's go!\n";
       fd = open("data.txt", O_CREAT| O_WRONLY| O_TRUNC);	//The file open mode is O_CREAT, O_WRONLY, O_ The combination of TRUNC, therefore
       													//An empty file will be created and can only be written. If there is a data.txt file, all data in the file will be cleared.
       if(fd == -1)
       	error_handling("open() error!");
       printf("file descriptor: %d \n", fd);
       
       if(write(fd, buf, sizeof(buf)) == -1)	// The data stored in the file transfer buf corresponding to the file descriptor stored in fd.
       	error_handling("write() error!");
       close(fd);
       return 0;
    }
    void error_handling(char *message)
    {
       fputs(message, stderr);
       fputc('\n', stderr);
       exit(1);
    }
    
    

    Compile run:

    gcc low_open.c -o lopen
    ./lopen
    

    Then the screen will output the file descriptor and generate a data.txt file with Let's go!

1.2.5 read the data in the file

  • Corresponding to the previous write function, the read function is used to input (receive) data.

      #include<unistd.h>
      ssize_t read(int fd, void * buf, size_t nbytes);
    

    The number of bytes received is returned on success (but 0 is returned on encountering the end of the file), and - 1 is returned on failure.

    Parameter 1: fd, displays the file descriptor of the data receiving object.

    Parameter 2: buf, buffer address value to save received data.

    Parameter 3: nbytes the maximum number of bytes of data to accept.

  • The following example reads the data saved in data.txt through the read() function.

    For codes, see:

    #include<stdio.h>
    #include<stdlib.h>
    #include<fcntl.h>
    #include<unistd.h>
    #define BUF_SIZE 100
    void error_handling(char * message);
    
    int main(void)
    {
    	int fd;
    	char buf[BUF_SIZE];
    
    	fd = open("data.txt", O_RDONLY);	// Open and read the special file data.txt
    	if(fd == -1)
    	{
    		error_handling("open() error!");
    	}
    	printf("file descriptor: %d \n", fd);	
    
    	if(read(fd, buf, sizeof(buf)) == -1)	// Call the read function to save the read data to the array buf declared in line 11.
    		error_handling("read() error!");
    	printf("file data: %s", buf);
    	close(fd);
    	return 0;
    }
    
    void error_handling(char * message)
    {
    	fputs(message, stderr);
    	fputc('\n', stderr);
    	exit(1);
    }
    

    After compiling and running, the output result is:

    file descriptor:3
    file data: Let's go!
    

    This concludes the introduction to I/O operations based on file descriptors. The same applies to sockets.

1.2.6 file descriptor and socket

  • The following source code file will create both a file and a socket, and compare the returned file descriptor value with an integer type.

    For codes, see:

#include<stdio.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/socket.h>

int main(void)
{
    int fd1, fd2, fd3;
    fd1 = socket(PF_INET, SOCK_STREAM, 0);
    fd2 = open("test.dat", O_CREAT| O_WRONLY| O_TRUNC);
    fd3 = socket(PF_INET, SOCK_DGRAM, 0);

    printf("file descriptor 1: %d\n", fd1);
    printf("file descriptor 2: %d\n", fd2);
    printf("file descriptor 3: %d\n", fd3);

    close(fd1);
    close(fd2);
    close(fd3);
    return 0;
}

Operation results:

file destriptor 1: 3
file descroptor 2: 4
file descriptor 3: 5

From the integer value of the output file descriptor, it can be seen that the descriptors are numbered from small to large from 3, because 0, 1 and 2 are descriptors assigned to standard I/O.

Attach note source code: https://github.com/Barry-xc/TCP-IP-SocketProgramming

Posted by coolbeansdude51 on Sun, 21 Nov 2021 11:01:45 -0800