What is socket
A socket interface is an API of a TCP/IP network. A socket interface defines many functions or routines that can be used by programmers to develop applications on a TCP/IP network. To learn TCP/IP network programming on the internet, you must understand the socket interface.
The socket interface designer first places the interface in the UNIX operating system. If you understand the input and output of Unix systems, you can easily understand socket. Network socket data transmission is a special type of I/O, and socket is also a file descriptor. Socket also has a function that calls socket () similar to opening a file. This function returns an integer socket descriptor. Subsequent operations such as connection establishment and data transmission are implemented through this socket. There are two common socket types: stream socket (sock_stream) and datagram socket (sock_dgram ). Streaming is a connection-oriented socket for connection-oriented TCP Service applications. The data reporting socket is a connectionless socket that corresponds to connectionless UDP Service applications.
Socket Creation
To establish a socket, the program can call the socket function, which returns a handle similar to a file descriptor. The socket function is prototype:
Int socket (INT domain, int type, int Protocol );
Domain indicates the protocol family used, usually pf_inet, indicating the Internet protocol family (TCP/IP protocol family). The type parameter specifies the socket type: sock_stream or sock_dgram, the socket interface also defines the original socket (sock_raw), which allows the program to use the lower-layer protocol; Protocol is usually assigned "0 ". The socket () call returns an integer socket descriptor, which you can use later.
The socket descriptor is a pointer to the internal data structure and points to the descriptor table entry. When the socket function is called, the socket execution body establishes a socket. In fact, "Creating a socket" means allocating storage space for a socket data structure. The socket execution body manages the descriptor table for you.
A network connection between two network programs includes five types of information: communication protocol, local Protocol address, local host port, remote host address, and remote protocol port. The socket data structure contains these five types of information.
Socket Configuration
After a socket descriptor is returned through a socket call, you must configure this socket before using the socket for network transmission. The connection-oriented Socket Client saves local and remote information in the socket data structure by calling the connect function. Clients that have no socket connection and servers that are connected to the socket, and servers that are connected to the socket, call the BIND function to configure local information.
The BIND function associates the socket with a port on the local machine, and then you can listen for service requests on this port. The BIND function is prototype:
Int BIND (INT sockfd, struct sockaddr * my_addr, int addrlen );
Sockfd is the socket descriptor returned by the socket function. my_addr is a sockaddr pointer pointing to information including the local IP address and port number. addrlen is often set to sizeof (struct sockaddr ).
The struct sockaddr structure type is used to save socket information:
Struct sockaddr {
Unsigned short sa_family;/* address family, af_xxx */
Char sa_data [14];/* 14-byte Protocol address */
};
Sa_family is generally af_inet, which represents the Internet (TCP/IP) address family. sa_data contains the IP address and port number of the socket.
There is also a structure type:
Struct sockaddr_in {
Short int sin_family;/* address family */
Unsigned short int sin_port;/* Port Number */
Struct in_addr sin_addr;/* IP Address */
Unsigned char sin_zero [8];/* fill in 0 to keep the same size as struct sockaddr */
};
This structure is more convenient to use. Sin_zero is used to fill the sockaddr_in structure with the same length as struct sockaddr. It can be set to zero using the bzero () or memset () function. The pointer to sockaddr_in and the pointer to sockaddr can be converted to each other, which means that if the parameter type required by a function is sockaddr, you can convert a pointer to sockaddr_in to a pointer to sockaddr when calling a function; or vice versa.
When using the BIND function, you can use the following value assignment to automatically obtain the local IP address and randomly obtain an unused port number:
My_addr.sin_port = 0;/* The system randomly selects an unused Port Number */
My_addr.sin_addr.s_addr = inaddr_any;/* enter the local IP Address */
By setting my_addr.sin_port to 0, the function automatically selects an unused port for you to use. Similarly, by setting my_addr.sin_addr.s_addr to inaddr_any, the system automatically fills in the local IP address.
Note that when using the BIND function, you need to convert sin_port and sin_addr to the network byte priority, while sin_addr does not need to be converted.
There are two types of computer data storage priorities: high byte priority and low byte priority. Data on the internet is transmitted over the network in the high byte priority. Therefore, for machines that store data in the low byte priority mode, data transmission over the Internet requires conversion, otherwise, data inconsistency may occur.
Below are several bytes sequence conversion functions:
· Htonl (): converts 32-bit values from the host's byte order to the network's byte order
· Htons (): converts 16-bit values from the host's byte order to the network's byte order
· Ntohl (): converts 32-bit values from the network byte order to the host byte order.
· Ntohs (): converts 16-bit values from the network byte order to the host byte order.
The BIND () function returns 0 when the call is successful. If an error occurs, it returns "-1" and sets errno as the corresponding error number. Note that do not set the port number to a value smaller than 1024 when calling the BIND function, because the port numbers from 1 to 1024 are reserved, you can select any unused port number in the range greater than 1024.
Establish a connection
Connection-oriented client programs use the connect function to configure the socket and establish a TCP connection with the remote server. The function prototype is:
Int connect (INT sockfd, struct sockaddr * serv_addr, int addrlen );
Sockfd is the socket descriptor returned by the socket function, serv_addr is a pointer containing the IP address and port number of the remote host, and addrlen is the length of the remote geological structure. When an error occurs, the connect function returns-1 and sets errno as the corresponding error code. For client program design, you do not need to call BIND (), because in this case, you only need to know the IP address of the target machine, and you do not need to care about which port the customer uses to establish a connection with the server, the socket execution body automatically selects an unused port for your program and notifies you when the program data is interrupted.
Connect function starts a direct connection with the remote host. The socket must be connected to the remote host only when the connection-oriented client program uses the socket. No connection protocol never establishes direct connection. The connection-oriented server never starts a connection, but passively listens to customer requests on the protocol port.
The listen function enables the socket to be in passive listening mode and creates an input data queue for the socket. The incoming service requests are stored in the queue until the program processes them.
Int listen (INT sockfd, int backlog );
Sockfd is the socket descriptor returned by the Socket System Call. Backlog specifies the maximum number of requests allowed in the Request queue. incoming connection requests will wait for accept () in the queue (refer to below ). Backlog limits the number of requests waiting for service in the queue. The default value is 20 for most systems. If a service request arrives, the input queue is full, and the socket rejects the connection request, the customer will receive an error message.
When an error occurs, the listen function returns-1 and returns the error code errno.
The accept () function allows the server to receive client connection requests. After the input queue is established, the server calls the accept function and then sleeps and waits for the client's connection request.
Int accept (INT sockfd, void * ADDR, int * addrlen );
Sockfd is the socket descriptor to be monitored. ADDR is usually a pointer to the sockaddr_in variable, this variable is used to store the information of the host requesting the service (a host sends this request from a port); addrten is usually a point value of sizeof (struct sockaddr_in). When an error occurs, the accept function returns-1 and sets the corresponding errno value.
First, when the socket monitored by the accept function receives a connection request, the socket execution body establishes a new socket, and the execution body associates the new socket with the address of the Request connection process, the initial socket that receives the service request can continue to listen on the previous socket, and data transmission can be performed on the new socket descriptor.
Data Transmission
The send () and Recv () functions are used for data transmission on the connected socket.
The send () function is prototype:
Int send (INT sockfd, const void * MSG, int Len, int flags );
Sockfd is the socket descriptor you want to use to transmit data; MSG is a pointer to the data to be sent; Len is the length of the data in bytes; flags is usually set to 0 (for the usage of this parameter, refer to the man Manual ).
The send () function returns the number of actually sent bytes, which may be less than the data you want to send. In the program, the return value of send () should be compared with the number of bytes to be sent. When the return value of send () does not match Len, this situation should be processed.
Char * MSG = "Hello! ";
Int Len, bytes_sent;
......
Len = strlen (MSG );
Bytes_sent = Send (sockfd, MSG, Len, 0 );
......
The Recv () function is prototype:
Int Recv (INT sockfd, void * Buf, int Len, unsigned int flags );
Sockfd is the socket descriptor for receiving data, Buf is the buffer for storing received data, and Len is the buffer length. Flags is also set to 0. Recv () returns the number of actually received bytes. If an error occurs,-1 is returned and the corresponding errno value is set.
Sendto () and recvfrom () are used for data transmission in the connectionless datagram socket mode. Because the local socket does not establish a connection with the remote machine, the destination address should be specified when sending data.
The prototype of the sendto () function is:
Int sendto (INT sockfd, const void * MSG, int Len, unsigned int flags, const struct sockaddr * To, int tolen );
This function has two more parameters than the send () function. To indicates the IP address and port number of the host, while tolen is often assigned sizeof (struct sockaddr ). The sendto function also returns the actual length of data bytes or-1 in case of a sending error.
The original recvfrom () function is:
Int recvfrom (INT sockfd, void * Buf, int Len, unsigned int flags, struct sockaddr * From, int * fromlen );
From is a variable of the struct sockaddr type, which saves the IP address and port number of the source machine. Fromlen is often set to sizeof (struct sockaddr ). When recvfrom () is returned, fromlen contains the number of data bytes actually stored in from. The recvfrom () function returns the number of bytes received or-1 if an error occurs, and the corresponding errno is set.
If you call the connect () function on the datagram socket, you can also use send () and Recv () for data transmission, but the socket is still a datagram socket, the UDP Service at the transport layer is used. However, when sending or receiving data reports, the kernel automatically adds the object and source address information.
End Transmission
After all data operations are completed, you can call the close () function to release the socket and stop any data operations on the socket:
Close (sockfd );
You can also call the shutdown () function to close the socket. This function allows you to stop data transmission in a certain direction, while data transmission in one direction continues. For example, you can close the write operation of a socket and allow the socket to continue to accept data until all data is read.
Int Shutdown (INT sockfd, int how );
Sockfd is the descriptor of the socket to be closed. The how parameter allows you to select the following methods for the shutdown operation:
· 0 ------- you are not allowed to continue receiving data.
· 1 ------- data cannot be sent again
· 2 ------- you are not allowed to send or receive data,
· If both allow, close () is called ()
If the shutdown operation succeeds, 0 is returned. If an error occurs,-1 is returned and the corresponding errno is set.
Socket programming instance
The server in the Code instance sends the string "Hello, you are connected!" to the client through a socket connection! ". If you run the software on the server and the client runs the software on the client, the client receives the string.
The software code of the server is as follows:
# 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 servport 3333/* server listening port number */
# Define backlog 10/* Maximum number of simultaneous connection requests */
Main ()
{
Int sockfd, client_fd;/* sock_fd: Listen to socket; client_fd: Data Transmission socket */
Struct sockaddr_in my_addr;/* local address information */
Struct sockaddr_in remote_addr;/* client address information */
If (sockfd = socket (af_inet, sock_stream, 0) =-1 ){
Perror ("socket creation error! "); Exit (1 );
}
My_addr.sin_family = af_inet;
My_addr.sin_port = htons (servport );
My_addr.sin_addr.s_addr = inaddr_any;
Bzero (& (my_addr.sin_zero), 8 );
If (BIND (sockfd, (struct sockaddr *) & my_addr, sizeof (struct sockaddr) =-1 ){
Perror ("BIND error! ");
Exit (1 );
}
If (Listen (sockfd, backlog) =-1 ){
Perror ("Listen error! ");
Exit (1 );
}
While (1 ){
Sin_size = sizeof (struct sockaddr_in );
If (client_fd = accept (sockfd, (struct sockaddr *) & remote_addr, & sin_size) =-1 ){
Perror ("Accept error ");
Continue;
}
Printf ("received a connection from % s \ n", inet_ntoa (remote_addr.sin_addr ));
If (! Fork () {/* child process code segment */
If (send (client_fd, "Hello, you are connected! \ N ", 26, 0) =-1)
Perror ("send error! ");
Close (client_fd );
Exit (0 );
}
Close (client_fd );
}
}
}
The workflow of the server is as follows: first call the socket function to create a socket, and then call the BIND function to bind it to the local address and a local port number, then, call listen to listen on the corresponding socket. When accpet receives a connection service request, a new socket is generated. The server displays the IP address of the client and sends the string "Hello, you are connected!" to the client through the new socket! ". Close the socket.
The fork () function in the Code instance generates a sub-process to process the data transmission part. The value returned by the fork () Statement for the sub-process is 0. Therefore, the IF statement containing the fork function is the sub-process code part, and it is executed concurrently with the parent Process Code part after the if statement.
The client code is as follows:
# Include <stdio. h>
# Include <stdlib. h>
# Include <errno. h>
# Include <string. h>
# Include <netdb. h>
# Include <sys/types. h>
# Include <netinet/in. h>
# Include <sys/socket. h>
# Define servport 3333
# Define maxdatasize 100/* maximum data transmission volume each time */
Main (INT argc, char * argv []) {
Int sockfd, recvbytes;
Char Buf [maxdatasize];
Struct hostent * Host;
Struct sockaddr_in serv_addr;
If (argc <2 ){
Fprintf (stderr, "Please enter the server's hostname! \ N ");
Exit (1 );
}
If (host = gethostbyname (argv [1]) = NULL ){
Herror ("gethostbyname error! ");
Exit (1 );
}
If (sockfd = socket (af_inet, sock_stream, 0) =-1 ){
Perror ("socket creation error! ");
Exit (1 );
}
Serv_addr.sin_family = af_inet;
Serv_addr.sin_port = htons (servport );
Serv_addr.sin_addr = * (struct in_addr *) Host-> h_addr );
Bzero (& (serv_addr.sin_zero), 8 );
If (connect (sockfd, (struct sockaddr *) & serv_addr ,\
Sizeof (struct sockaddr) =-1 ){
Perror ("Connect error! ");
Exit (1 );
}
If (recvbytes = Recv (sockfd, Buf, maxdatasize, 0) =-1 ){
Perror ("Recv error! ");
Exit (1 );
}
Buf [recvbytes] = '\ 0 ';
Printf ("Received: % s", Buf );
Close (sockfd );
}
The client first obtains the Server IP address through the server domain name, creates a socket, calls the connect function to establish a connection with the server, receives data sent from the server after the connection is successful, and closes the socket.
The gethostbyname () function completes domain name conversion. Because IP addresses are hard to remember and read/write, domain names are often used to represent hosts for convenience, which requires domain name and IP address conversion. Function prototype:
Struct hostent * gethostbyname (const char * Name );
The structure type returned by the function as hosten. Its definition is as follows:
Struct hostent {
Char * h_name;/* Host's official domain name */
Char ** h_aliases;/* an array of host aliases ending with null */
Int h_addrtype;/* return address type, in the Internet environment is AF-INET */
Int h_length;/* the length of the address in bytes */
Char ** h_addr_list;/* an array ending with 0, containing all addresses of the Host */
};
# Define h_addr h_addr_list [0]/* The first address in H-ADDR-list */
When gethostname () is called successfully, a pointer to struct hosten is returned. If the call fails,-1 is returned. When gethostbyname is called, you cannot use the perror () function to output error information. Instead, use the herror () function to output error information.
The principle of a connectionless client/server program is the same as that of a connected Client/Server. The difference between the two is that customers in a connectionless Client/Server generally do not need to establish a connection, when sending and receiving data, you must specify the address of the remote machine.
Blocking and non-blocking
The blocking function does not allow the program to call another function until it completes the specified task. For example, when a program executes a function call to read data, the next program statement is not executed until the function completes the read operation. When the server runs the accept statement without a customer connection request, the server stops waiting for the connection request on the accept statement. This is called blocking ). The non-blocking operation can be completed immediately. For example, if you want the server to check whether a client is waiting for a connection, you can accept the connection. Otherwise, you can continue to do other things by setting the socket to a non-blocking method. The non-blocking Socket enables the Accept call to return immediately when no client is waiting.
# Include <unistd. h>
# Include <fcntl. h>
......
Sockfd = socket (af_inet, sock_stream, 0 );
Fcntl (sockfd, f_setfl, o_nonblock );
......
By setting the socket as a non-blocking method, you can implement "polling" Several sockets. When you attempt to read data from a non-blocking socket without data waiting for processing, the function will return immediately, the returned value is-1, and the errno value is ewouldblock. However, this "Round Robin" will make the CPU in a busy waiting mode, thus reducing performance and wasting system resources. The call to select () can effectively solve this problem. It allows you to hook the process itself and enable the system kernel to listen to any activity of a set of file descriptors required by the system kernel, as long as the activity is confirmed on any monitored file descriptor, the Select () call will return information indicating that the file descriptor is prepared, so as to select random changes for the process, the CPU overhead does not need to be wasted because the process itself tests the input. The prototype of the select function is:
Int select (INT numfds, fd_set * readfds, fd_set * writefds,
Fd_set * required TFDs, struct timeval * timeout );
Readfds, writefds, and limit TFDs are the collection of read, write, and exception handling file descriptors monitored by select. If you want to determine whether data can be read from the standard input and a socket descriptor, you only need to add the file descriptor 0 and the corresponding sockdtfd of the standard input to the readfds set; the numfds value is the file descriptor with the highest number to be checked plus 1. In this example, the numfds value should be sockfd + 1. When the SELECT statement is returned, readfds will be modified, indicates that a file descriptor has been read. You can test it through fd_issset. To set, reset, and test the file descriptor corresponding to fd_set, it provides a set of macros:
Fd_zero (fd_set * Set) ---- clears a file descriptor set;
Fd_set (int fd, fd_set * Set) ---- Add a file descriptor to the file descriptor set;
Fd_clr (int fd, fd_set * Set) ---- clears a file descriptor from the file descriptor set;
Fd_isset (int fd, fd_set * Set) ---- try to determine whether the file descriptor is set.
The timeout parameter is a pointer to the struct timeval type, which enables select () to return if no file descriptor is ready after waiting for a long time. Struct timeval data structure:
Struct timeval {
Int TV _sec;/* seconds */
Int TV _usec;/* microseconds */
};
POP3 client instance
The following code instance is based on the POP3 client protocol and connects to the mail server and retrieves emails from a specified user account. Commands that interact with the email server are stored in the string array popmessage. The program sends these commands in sequence through a do-while loop.
# Include <stdio. h>
# Include <stdlib. h>
# Include <errno. h>
# Include <string. h>
# Include <netdb. h>
# Include <sys/types. h>
# Include <netinet/in. h>
# Include <sys/socket. h>
# Define pop3servport 110
# Define maxdatasalize 4096
Main (INT argc, char * argv []) {
Int sockfd;
Struct hostent * Host;
Struct sockaddr_in serv_addr;
Char * popmessage [] = {
"User userid \ r \ n ",
"Pass password \ r \ n ",
"Stat \ r \ n ",
"List \ r \ n ",
"Retr 1 \ r \ n ",
"DELE 1 \ r \ n ",
"Quit \ r \ n ",
Null
};
Int ilength;
Int imsg = 0;
Int iend = 0;
Char Buf [maxdatasize];
If (host = gethostbyname ("Your. Server") = NULL ){
Perror ("gethostbyname error ");
Exit (1 );
}
If (sockfd = socket (af_inet, sock_stream, 0) =-1 ){
Perror ("socket error ");
Exit (1 );
}
Serv_addr.sin_family = af_inet;
Serv_addr.sin_port = htons (pop3servport );
Serv_addr.sin_addr = * (struct in_addr *) Host-> h_addr );
Bzero (& (serv_addr.sin_zero), 8 );
If (connect (sockfd, (struct sockaddr *) & serv_addr, sizeof (struct sockaddr) =-1 ){
Perror ("Connect error ");
Exit (1 );
}
Do {
Send (sockfd, popmessage [imsg], strlen (popmessage [imsg]), 0 );
Printf ("have sent: % s", popmessage [imsg]);
Ilength = Recv (sockfd, BUF + iend, sizeof (BUF)-iend, 0 );
Iend + = ilength;
Buf [iend] = '\ 0 ';
Printf ("Received: % s, % d \ n", Buf, imsg );
Imsg ++;
} While (popmessage [imsg]);
Close (sockfd );
}