The basic concept
Socket originated from Unix, and one of the basic philosophies of Unix/Linux is that “everything is a file”, which can be operated in “open – > read/write/read – > Close” mode. Socket is an implementation of this pattern, Socket is a special file, some Socket functions are operations on it (read/write IO, open, close). To put it plainly, Socket is the intermediate software abstraction layer of communication between application layer and TCP/IP protocol family. It is a group of interfaces. In the design mode, Socket is actually a facade mode, it hides the complex TCP/IP protocol family behind the Socket interface, for users, a simple set of interfaces is all, let the Socket to organize data, in accordance with the specified protocol. The Socket is between the application layer and the transport layer, as shown in the figure:
With the application of Unix, socket has been introduced into Windows and other operating systems. Sockets typically exchange data only with sockets in the same region, and Windows sockets support only one communication region: the Internet domain (AF_INET), which is used by communication processes using forgotten protocol clusters.
The type of the socket
- Stream socket (SOCK_STREAM) : provides connection-oriented and reliable data transfer service. Data is sent error-free and duplicate-free, and is received in the order of transmission (TCP).
- Datagram socket (SOCK_DGRAM) : provides connectionless service, in which packets are sent as separate packets, without any guarantees, data may be lost, and the order of receiving is out of order (UDP)
- Raw socket (SOCK_RAM)
socket API
Socket () int socket(int domain, int type, int protocol);
The socket function corresponds to the normal file opening operation. Normal file opening operations return a file descriptor, while socket() is used to create a socket descriptor that uniquely identifies a socket. The socket descriptor, like the file descriptor, is used for subsequent operations. It is used as a parameter to perform some read and write operations. Just as you can pass different parameter values to fopen to open different files. When creating a socket, you can also specify different parameters to create different socket descriptors. The three parameters of the socket function are:
- domain: protocol domain, also known as protocol family. Common protocol families are,
AF_INET, AF_INET6
,AF_LOCAL
(or theAF_UNIX
.Unix
The domainsocket
),AF_ROUTE
And so on. The protocol family determines the address type of the socket. The corresponding address must be used in communication, for exampleAF_INET
Decided to useIpv4 address (32-bit) and port number (16-bit)
A combination of,AF_UNIX
Decided to use an absolute pathname for the address. - type: Specifies the socket type. The common socket types are:
SOCK_STREAM, SOCK_DGRAM
,SOCK_RAW, SOCK_PACKET
,SOCK_SEQPACKET
, etc.). - protocol: Hence the name think meaning, is designated agreement. Common protocols are,
IPPROTO_TCP, IPPTOTO_UDP
,IPPROTO_SCTP, IPPROTO_TIPC
And so on, they correspond separatelyTCP transport protocol
,UDP transport protocol
,STCP transport protocol
,TIPC transfer protocol
.
The bind () function
As mentioned above, bind() assigns a specific address in an address family to the socket. For example, AF_INET and AF_INET6 assign an ipv4 or ipv6 address and port number to the socket.
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); The three parameters of the function are:
- Sockfd: the socket description word, which is created using the socket() function and uniquely identifies a socket. The bind() function will give the description a name.
- addrA:
const struct sockaddr*
Pointer to the protocol address to bind to SockFD. The address structure varies according to the address protocol family used to create the socket. For example, ipv4 corresponds to:
struct sockaddr_in {
sa_family_t sin_family; /* address family: AF_INET */
in_port_t sin_port; /* port in network byte order */
struct in_addr sin_addr; /* internet address */
};
/* Internet address. */
struct in_addr {
uint32_t s_addr; /* address in network byte order */
};
Copy the code
Ipv6 corresponds to:
struct sockaddr_in6 {
sa_family_t sin6_family; /* AF_INET6 */
in_port_t sin6_port; /* port number */
uint32_t sin6_flowinfo; /* IPv6 flow information */
struct in6_addr sin6_addr; /* IPv6 address */
uint32_t sin6_scope_id; /* Scope ID (new in 2.4) */
};
struct in6_addr {
unsigned char s6_addr[16]; /* IPv6 address */
};
Copy the code
The Unix domain corresponds to:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
Copy the code
- Addrlen: Corresponds to the length of the address.
Usually when the server is started, it will bind a well-known address (such as IP address + port number) to provide services. Customers can connect to the server through it. The client does not need to specify, the system automatically assigns a port number and its OWN IP address combination. This is why it is common for the server to call bind() before LISTEN, while the client does not, but the system randomly generates one at connect().
Listen (), connect() functions
If a server calls socket() and bind(), it calls listen() to listen to the socket, and if the client calls connect() to make a connection request, the server receives the request.
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
The first argument to listen is the socket description to listen on, and the second argument is the maximum number of connections that the socket can queue. The socket() function creates a socket of an active type by default. The Listen function changes the socket to a passive type, waiting for a connection request.
The first argument to connect is the client’s socket description, the second argument is the server’s socket address, and the third argument is the length of the socket address. The client establishes a connection to the TCP server by calling the connect function.
Accept () function
After the TCP server calls socket(), bind(), and listen(), it listens for the specified socket address. The TCP client sends a connection request to the TCP server after calling socket() and connect() in sequence. After the TCP server listens for the request, it calls the accept() function to accept the request and the connection is established. Then network I/O operations can begin, that is, read and write I/O operations similar to normal files.
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); The first argument to accept is the socket description of the server, the second argument is a pointer to struct sockaddr *, which returns the protocol address of the client, and the third argument is the length of the protocol address. If ACCPET succeeds, the return value is a brand new descriptor automatically generated by the kernel to represent the TCP connection to the returning customer.
Note: The first argument to accept is the server’s socket descriptor, which is generated when the server starts calling the socket() function. The accept function returns the description of the connected socket. A server usually only creates a listening socket descriptor that lasts for the life of the server. The kernel creates a connected socket descriptor for each client connection accepted by the server process. When the server finishes serving a client, the corresponding connected socket descriptor is closed.
Read (), write() functions, etc
Everything is all right, so far the server and the client have established a good connection. Network I/O can be called to read and write operations, that is, to achieve communication between different processes in the network role! Network I/O operations have the following groups:
- read()/write()
- recv()/send()
- readv()/writev()
- recvmsg()/sendmsg()
- recvfrom()/sendto()
I recommend using the recvmsg()/sendmsg() functions, which are the most generic I/O functions and can actually be replaced with all the other functions above. Their declaration is as follows:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
Copy the code
The read function is responsible for reading from a FD. On success, read returns the actual number of bytes read, with a value of 0 indicating that the end of the file has been read and less than 0 indicating an error. If the error is EINTR, the read was interrupted, and if ECONNREST, the network connection is faulty.
The write function writes the nbytes in buf to the file descriptor fd. Returns the number of bytes written on success. Returns -1 on failure and sets the errno variable. In a network program, there are two possibilities when we write to a socket file descriptor. 1) If the return value of write is greater than 0, some or all data is written. 2) The value returned is less than 0. We have to deal with it according to the error type. If the error is EINTR, an interrupt error occurred while writing. If it is EPIPE, there is a problem with the network connection (the other party has closed the connection).
Close () function
After the connection between the server and the client is established, some read and write operations will be carried out. After the read and write operations are completed, the corresponding socket description word should be closed. For example, after the operation, the opened file should be closed by calling fclose.
#include <unistd.h>
int close(int fd);
Copy the code
Close the default behavior of a TCP socket, marking the socket as closed, and immediately returning to the calling process. This descriptor can no longer be used by the calling process, that is, as the first argument to read or write.
Socket programming
TCP
The flow chart of the TCP client and server is as follows:
The whole process of TCP transmission is shown in the following figure:
Client code:
#import "DemoViewController.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#define PORT 8040 //
#define ADDR "127.0.0.1@interface DemoViewController () @end @implementation DemoViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. int iSocketFD = 0; // Socket handle unsigned int iRemoteAddr = 0; struct sockaddr_in stRemoteAddr = {0}; Char buf[4096] = {0}; ISocketFD = socket(AF_INET, SOCK_STREAM, 0); / / set up the socketif(0 > iSocketFD)
{
printf("Failed to create socket! \n");
return; } stRemoteAddr.sin_family = AF_INET; stRemoteAddr.sin_port = htons(PORT); inet_pton(AF_INET, ADDR, &iRemoteAddr); stRemoteAddr.sin_addr.s_addr=iRemoteAddr; // Connect method: pass in handle, destination address, and sizeif(0 > connect(iSocketFD, (void *)&stRemoteAddr, sizeof(stRemoteAddr)))
{
NSLog(@"Connection failed!");
}else{
NSLog(@"Connection successful");
recv(iSocketFD, buf, sizeof(buf), 0);
NSLog(@"Received:%s", buf); } close(iSocketFD); // Close socket} @endCopy the code
Server:
#import "ServerViewController.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#define PORT 8040 //
#define BACKLOG 5 //
@interface ServerViewController ()
@end
@implementation ServerViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
int iSocketFD = 0; //socket句柄
int iRecvLen = 0; //接收成功后的返回值
int new_fd = 0; //建立连接后的句柄
char buf[4096] = {0}; //
struct sockaddr_in stLocalAddr = {0}; //本地地址信息结构图,下面有具体的属性赋值
struct sockaddr_in stRemoteAddr = {0}; //对方地址信息
socklen_t socklen = 0;
iSocketFD = socket(AF_INET, SOCK_STREAM, 0); //建立socket
if(0 > iSocketFD)
{
NSLog(@"Failed to create socket!");
return; } stLocalAddr.sin_family = AF_INET; / / stLocalAddr. Sin_port = htons(PORT); / / stLocalAddr. Sin_port = htons(PORT); / / stlocalAddr.sin_addr.s_addr =htonl(INADDR_ANY); / / stlocalAddr.sin_addr.s_addr =htonl(INADDR_ANY); /*IP, the parentheses represent the local IP*/ // bind the address structure to the socketif(0 > bind(iSocketFD, (void *)&stLocalAddr, sizeof(stLocalAddr)))
{
NSLog(@"Binding failed!");
return; } // Enable listening. The second parameter is the maximum number of listenersif(0 > listen(iSocketFD, BACKLOG))
{
NSLog(@"Monitor failed!");
return;
}
NSLog(@"iSocketFD: %d", iSocketFD); New_fd = accept(iSocketFD, (void *) &StremoteAddr, &socklen);if(0 > new_fd)
{
NSLog(@"Receive failed!");
return;
}else{
NSLog(@"Received successfully!"); // Send (new_fd, new_fd, new_fd,"This is the message sent back by the server after receiving successfully!", sizeof("This is the message sent back by the server after receiving successfully!"), 0);
}
NSLog(@"new_fd: %d", new_fd);
iRecvLen = (int)recv(new_fd, buf, sizeof(buf), 0);
if(0 >= iRecvLen) // The peer end closes the connection and returns 0 {NSLog(@"Receiving failed or the peer end closes the connection!");
}else{
NSLog(@"buf: %s", buf);
}
close(new_fd);
close(iSocketFD);
}
@end
Copy the code
UDP
Udp client and server concepts are not very prominent, to put it bluntly udp as long as the realization of receiving and sending functions can be
Client:
#import "UdpClientViewController.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>
#define PORT 8040 //
#define ADDR "127.0.0.1
@interface UdpClientViewController ()
@end
@implementation UdpClientViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
int clientSocketId;
ssize_t len;
socklen_t addrlen;
struct sockaddr_in client_sockaddr;
char buffer[256] = "Hello, server, how are you?"; ClientSocketId = Socket (AF_INET, SOCK_DGRAM, 0);if(clientSocketId < 0) {
NSLog(@"creat client socket fail\n");
return;
}
addrlen = sizeof(struct sockaddr_in);
bzero(&client_sockaddr, addrlen);
client_sockaddr.sin_family = AF_INET;
client_sockaddr.sin_addr.s_addr = inet_addr(ADDR);
client_sockaddr.sin_port = htons(PORT);
int count = 10;
do {
bzero(buffer, sizeof(buffer));
sprintf(buffer, "%s"."Hello, server, how are you?"); // Step 2: // Note :UDP is connectionless, Len = sendto(clientSocketId, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_sockaddr, addrlen);if (len > 0) {
NSLog(@"Sent successfully");
} else {
NSLog(@"Send failed"); } // Step 3: Receive the message returned from the server // Receive the string bzero(buffer, sizeof(buffer)); len = recvfrom(clientSocketId, buffer, sizeof(buffer), 0, (struct sockaddr *)&client_sockaddr, &addrlen); NSLog(@"receive message from server: %s", buffer);
count--;
} while(count >= 0); Close (clientSocketId); // clientSocketId (clientSocketId); } @endCopy the code
Server:
#import "UdpServerViewController.h"
#import <sys/socket.h>
#import <netinet/in.h>
#import <arpa/inet.h>@interface UdpServerViewController () @end @implementation UdpServerViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. int serverSockerId = -1; ssize_t len = -1; socklen_t addrlen; char buff[1024]; struct sockaddr_in ser_addr; ServerSockerId = socket(AF_INET, SOCK_DGRAM, 0); sockerId = socket(AF_INET, SOCK_DGRAM, 0);if(serverSockerId < 0) {
NSLog(@"Create server socket fail");
return; } addrlen = sizeof(struct sockaddr_in); bzero(&ser_addr, addrlen); ser_addr.sin_family = AF_INET; ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); ser_addr.sin_port = htons(1024); // Step 2: Bind the port numberif(bind(serverSockerId, (struct sockaddr *)&ser_addr, addrlen) < 0) {
NSLog(@"server connect socket fail");
return;
}
do{ bzero(buff, sizeof(buff)); Len = recvfrom(serverSockerId, buff, sizeof(buff), 0, (struct sockAddr *)&ser_addr, &addrlen); // Display the network address NSLog(@) of the client"receive from %s\n", inet_ntoa(ser_addr.sin_addr)); // Display the string NSLog(@) from the client"recevce:%s", buff); // Step 4: Sendto (serverSockerId, buff, len, 0, (struct sockAddr *)&ser_addr, addrlen); sendto(serverSockerId, buff, len, 0, (struct sockAddr *)&ser_addr, addrlen); }while (strcmp(buff, "exit")! = 0); // Step 5: Close socket close(serverSockerId); } @endCopy the code
Refer to the article
Socket programming (C language) — based on TCP protocol, based on UDP protocol (multithreading, loop monitoring) (network communication AF_INET, the typical TCP/IP four-type model of communication process) Socket programming and API introduction