Socket programming based on TCP/UDP

---- socket overview:

Socket is an abstraction layer between the application layer and the transport layer. It abstracts the complex operations of the TCP/IP layer into several simple interfaces for the application layer to call the realized process to communicate in the network.

Socket originated from UNIX. Under the Unix philosophy that everything is a file, socket is an implementation of the "open-read/write-close" mode. The server and client each maintain a "file". After the connection is established and opened, you can Write content to your own file for the other party to read or read the content of the other party, and close the file when the communication ends.

---- Interface introduction:

socket(): create a socket

bind(): Bind the socket to the local address and port, usually called by the server

listen(): dedicated to TCP, enable listening mode

accept(): dedicated to TCP, the server waits for the client to connect, usually in a blocking state

connect(): dedicated to TCP, the client actively connects to the server

send(): TCP dedicated, send data

recv(): TCP dedicated, receive data

sendto(): UDP dedicated, send data to the specified IP address and port

recvfrom(): UDP dedicated, receive data, return data remote IP address and port

closesocket(): close the socket

---- The process is as follows:

The interface is explained in detail, and the commonly used system calls are as follows:

>> socket() : creating  a socket 

A socket is an abstraction of a communication endpoint. Just as they would use file descriptors to access files, applications use socket descriptors to access sockets. To create a socket, we call the socket() function.

原型:int socket(int domain, int type, int protocol);

Return value: returns file (socket) descriptor if OK, -1 on error.

domain:AF_INET, AF_INET6, AF_UNIX, AF_UNSPEC (address format)

type:SOCK_DGRAM, SOCK_RAW, SOCK_STREAM, SOCK_SEQPACKET

protocol:IPPROTO_IP, IPPROTO_IPV6, IPPROTO_TCP, IPPROTO_UDP

The protocol argument is usually zero, to select the default protocol for the given domain and socket type. The default protocol for a SOCK_STREAM socket in the AF_INET communication domain is TCP(Transmission Control Protocol). The default protocol for a SOCK_DGRAM socket in the AF_INET communication domain is UDP(User Datagram Protocol).

NOTE: UDP -- datagram (datagram), connectionless, no logical connection exist between peers for them to communicate. A datagram is a self-contained (independent) message. Similar to (analogous) sending email, you can send Multiple emails, but there is no guarantee that the emails will arrive and the order in which the emails will arrive. Each email contains the recipient's address.

TCP -- byte stream A byte stream (SOCK_STREAM), in contrast, before transmitting data, a connection needs to be established, and connection-oriented communication is similar to making a phone call. A point-to-point connection includes source and destination.

Communication on a socket is bidirectional. We can disable I/O on a socket with the shutdown function.

>> shutdown()   

Prototype: int shutdown(int sockfd, int how);

Return value: returns 0 if OK, -1 on error.

The shutdown() system call closes one or both channels of the socket sockfd, depending on the value of how, which is specified as one of the following:

how:  SHUT_RD, then reading from the socket is disabled.  SHUT_WR, then we can't use the socket for transmitting data. We can use SHUT_RDWR to disable both data transmission and reception.

shutdown() differs from close() in another important respect: it closes the socket channels regardless of whether there are other file descriptors referring to the socket. For example, sockfd refers to a connected stream socket. If we make the following calls, then the connection remains open, and we can still perform I/O on the connection via the file descriptor fd2:

1.    fd = dup(sockfd);

2.    close(sockfd);

However, if we make the following sequence of calls, then both channels of the connection are closed, and I/O can no longer be performed via fd2:

1.    fd2 = dup(sockfd);

2.    shutdown(sockfd,SHUT_RDWR);

Note that shutdown() doesn't close the file descriptor, even if how is specified as SHUT_RDWR. To close the file descriptor, we must additionally call close().

>> bind() : binding a socket to an address    

The bind() system call binds a socket to an address.

原型:int bind(int sockfd, const struct sockaddr * addr, socklen_t addrlen);

Return value: returns 0 on success, or -1 on error.

The sockfd argument is a file descriptor obtained from a previous call to socket(). The addr argument is a pointer to a structure specifying the address to which this socket is to be bound. The type of structure passed in this argument depends on the socket domain. The addrlen argument specifies the size of the address structure.

Typically, we bind a server's socket to a well-known address - that is, a fixed address that is known in advance to client applications that need to communicate with that server.

>> listen() : listening for incoming connections    

原型:int listen(int sockfd, int backlog); // returns 0 on success, or -1 on error.

The listen() system call marks the stream socket referred to by the file descriptor sockfd as passive. The socket will subsequently be used to accept connections from other(active) sockets.

The client may call connect() before the server calls accept(). This could happen, for example, because the server is busy handling some other clients. This results in a pending connection, as illustrated in Figure 56-2.

The kernel must record some information about each pending connection request so that a subsequent accept() can be processed. The backlog argument allows us to limit the number of such pending connections. Further connection requests block until a pending connection is accepted(via accept()), and thus removed from the queue of pending connections.

>> accept() : accepting a connection   

The accept() system call accepts an incoming connection on the listening stream socket referred to by the file descriptor sockfd. If there are no pending connections when accept() is called, the call blocks until a connection request arrives when the sockfd in block mode. If sockfd is in nonblocking mode, accept() will return -1 and set errno to either EAGAIN or EWOULDBLOCK.

原型:int accept(int sockfd, struct sockaddr * restrict addr, socklen_t * restrict len);

Return value: return file(socket) descriptor if OK, -1 on error.

This function extracts the first connection from the waiting connection queue of s, creates a new socket of the same type as s and returns the handle. If there is no waiting connection in the queue and the socket is in blocking mode, accept() blocks the calling process until a new connection appears. If the socket is non-blocking and there are no waiting connections in the queue, accept() returns an error code WSAEWOULDBLOCK. Sockets that have accepted connections cannot be used to accept new connections, and the original listening socket remains open.

The key point to understand about accept() is that it creates a new socket, and this new socket that is connected to the peer socket that performed the connect(). This new socket descriptor has the same socket type and address family as the  original socket(sockfd). A file descriptor for the connected socket is returned as the function result of the accept() call. The listening socket(sockfd) remains open, and can be used to accept further connections. A typical server application creates one listening socket, binds it to a well-known address, and then handles all client requests by accepting connections via that socket.

The remaining arguments to accept() return the address of the peer socket. (client)

If we don't care about the client's identity, we can set the addr and len parameters to NULL. Otherwise, before calling accept, we need to set the addr (指向一个buffer) parameter to a buffer large enough to hold the address and set the integer pointed to by len to the size of the buffer in bytes. On return, accept will fill in the client's address in the buffer and update the integer pointed to by len to reflect the size of the address.

>> connect() : connecting to a peer socket  

原型:int connect(int sockfd, const struct sockaddr * addr, socklen_t addrlen);

Return value: returns 0 on success, or -1 on error.

The connect() system call connects the active socket referred to by the file descriptor sockfd to the listening socket whose address is specified by addr and addrlen.

>> send() : send data of TCP type 

原型:int send(int sockfd, const void * msg, int len, int flags);

Each TCP socket has a send buffer whose size can be changed with the SO_SNDBUF option. The process of calling the send function is actually the process of the kernel copying the user data (msg) to the send buffer of the TCP socket. If len is greater than the size of the send buffer, return -1. Otherwise, check whether the remaining space in the buffer can accommodate the length of len to be sent. If not, copy a part and return the copy length (referring to non-blocking send, if If it is blocking send, it must wait for all data to be copied to the buffer before returning, so the return value of blocking send must be equal to len). If the buffer is full, it waits for sending, and copies to the buffer after there is remaining space. If an error occurs during the copying process, it returns -1. For the cause of the error, check the value of errno.

Note: The successful return of send does not mean that the other party has received the data. If a network error occurs during the subsequent protocol transmission, the next send will return -1 sending error. The data sent by TCP to the other party must be confirmed by the other party before the data in the sending buffer can be deleted. Otherwise, it will be cached in the buffer until the sending is successful.

Parameter explanation:

sockfd -- sender socket descriptor (non-listening descriptor)

msg -- the buffer of the data to be sent (copy the len length of its content to the send buffer of the socket)

len -- the byte length of the data to be sent.

flags -- usually set to 0.

>> recv() : TCP type data reception 

原型:int recv(int sockfd, void *buf, int len, unsigned int flags);

recv() copies data from the receive buffer. On success, returns the number of bytes copied, on failure, returns -1. In blocking mode, recv() will block until there is at least one byte in the buffer before returning, and it will sleep when there is no data. If it is not blocked, it will return immediately, if there is data, it will return the copied data size, otherwise it will return an error -1.

Parameter explanation:

sockfd -- Receiver socket descriptor (non-listening descriptor)

buf -- the base address of the received data (copy the contents of the socket's receive buffer to buf)

len -- the number of bytes of data received

flags -- usually set to 0.

>> sendto() : send data of UDP type 

原型:int sendto(int sockfd, const void * msg, int len, unsigned int flags, const struct sockaddr * dst_addr, int addrlen);

It is used for data sending of unreliable connection (UDP), because the UDP method does not establish a connection socket, so the address of the destination socket needs to be specified.

You can use the same UDP socket descriptor sockfd to communicate with different destination addresses. However, TCP needs to establish a connection in advance, and each connection will generate a different socket descriptor, which is reflected in: the client needs to use a different fd to connect, and the server generates a different fd every time it accepts.

UDP does not have a real send buffer, because it is an unreliable connection, and it is not necessary to save the data copy of the application process. When the data of the application process is passed down the protocol stack, it is copied to the kernel buffer in some form. When the data link layer After the data is transmitted, the copy of the data in the kernel buffer is deleted. Therefore it does not need a send buffer.

For sendto(), the dest_addr and addrlen arguments specify the socket to which the datagram is to be sent. These arguments are employed in the same manner as the corresponding arguments to connect(). The dest_addr argument is an address structure suitable for this communication domain. It is initialized with the address of the destination socket. The addrlen argument specifies the size of addr.

>> recvfrom() : UDP type data reception 

原型:int recvfrom(int sockfd, void * buf, size_t len, int flags, struct sockaddr * src_addr, int * addrlen);

Parameter explanation:

sockfd -- the socket description of the receiving end;

buf -- the application buffer address for receiving data;

len -- indicates the buffer size;

flags -- usually 0;

src_addr -- the address of the data source (IP address, Port number).

fromlen -- when used as input, fromlen is usually set to sizeof(struct sockaddr).

For recvfrom(), the src_addr and addrlen arguments return the address of the remote socket used to send the datagram. (These arguments are analogous to the addr and addrlen arguments of accept(), which return the address of a connecting peer socket.) Prior to the call(在调用之前), addrlen should be initialized to the size of the structure pointed to by src_addr(结构的大小); upon return(在返回时), it contains the number of bytes actually written to this structure

Embedded Internet of Things needs to learn a lot. Don't learn the wrong route and content, which will cause your salary to go up!

Share a data package with everyone, about 150 G. The learning content, face-to-face scriptures, and projects in it are relatively new and complete! (Click to find a small assistant to receive)

Guess you like

Origin blog.csdn.net/m0_70911440/article/details/131548104