This chapter explores ipv4 – based sockets

A socket is a SET of C language apis encapsulated by TCP/IP and UDP/IP. It is called a socket, through which both ends of communication transfer information

Server Demo —- Client Demo

Note: Start the server before starting the client

Socket

Socket can be used by clients and servers. A client can connect to only one server, and a server can be connected by multiple clients

The client and server initialization and interaction steps are shown in the following figure

Create a socket

Note that this is TCP fixed writing method, and ipv4 protocol, ipv6 protocol fixed place to add 6, details can refer to ipv6 adaptation, or refer to GCDAsyncSocket(pod import CocoaAsyncSocket source code)

 /** 1: creates the socket parameter domain: protocol domain, also known as protocol family. Common protocol families include AF_INET, AF_INET6, AF_LOCAL (or AF_UNIX, Unix domain Socket), and AF_ROUTE. The protocol family determines the address type of the socket, and the corresponding address must be used in communication. For example, AF_INET determines the combination of ipv4 address (32-bit) and port number (16-bit), and AF_UNIX determines an absolute pathname as the address. Type: indicates the Socket type. Common socket types include SOCK_STREAM, SOCK_DGRAM, SOCK_RAW, SOCK_PACKET, and SOCK_SEQPACKET. A stream Socket (SOCK_STREAM) is a connection-oriented Socket for connection-oriented TCP service applications. A datagram Socket (SOCK_DGRAM) is a connectionless Socket corresponding to a connectionless UDP service application. Protocol: specifies a protocol. Common protocols include IPPROTO_TCP, IPPROTO_UDP, IPPROTO_STCP, and IPPROTO_TIPC, which correspond to TCP transport protocol, UDP transport protocol, STCP transport protocol, and TIPC transport protocol respectively. Note: 1. Type and protocol can not be combined at will. For example, SOCK_STREAM can not be combined with IPPROTO_UDP. When the third parameter is 0, the system automatically selects the default protocol corresponding to the second parameter type. Return value: Returns the descriptor of the newly created socket on success or INVALID_SOCKET on failure (-1 on Linux failure) */
self.clientId = socket(AF_INET, SOCK_STREAM, 0);
if (self.clientId == -1) {
    NSLog(@"Failed to create socket");
    return;
}
Copy the code

Setting IP Information

You can set the IP address to the IP address of the server to be connected to ensure that the correct server can be accessed

/** __uint8_t sin_len; If there is no member, one of its bytes is incorporated into the sin_family member sa_family_t sin_family; Generally speaking, AF_INET (address family) PF_INET (protocol family) in_port_t sin_port; Struct in_addr sin_addr; // ip char sin_zero[8]; No real meaning, just to align with the SOCKADDR structure in memory */

struct sockaddr_in socketAddr;
socketAddr.sin_family = AF_INET;
//htons: converts an unsigned short host value to network byte order,
// Different cpus are in different order (big-endian big-tail, little-endian small-tail)
socketAddr.sin_port = htons(8040);// Set the port number
//inet_addr is a computer function that converts a dotted decimal IPv4 address to a long integer
socketAddr.sin_addr.s_addr =  inet_addr("172.26.105.76"); / / set the IP
Copy the code

The connection socket

Start connecting to the server, and when the connection is complete, you can send and receive messages as if you were communicating

Before connecting to the server, ensure that the test server is started. Otherwise, the connection fails

/** Parameters parameter 1: socket descriptor Parameter 2: pointer to data structure sockaddr, including destination port and IP address Parameter 3: Sizeof (struct sockaddr) returns 0 on success, non-0 on failure, GetLastError(). * /
int result = connect(self.clientId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));

if(result ! =0) {
    NSLog(@"Connection failed");
    return;
}else {
    NSLog(@"Connection successful");
}
Copy the code

Send and receive messages

After the above steps, after the connection is successful, we can actively send and receive messages. After we complete the connection, we can immediately enter the state of receiving messages. In order to ensure that the message is received in time, we need to keep reading the contents of the cache through recV method to avoid content accumulation

Receives the message

The loop that receives the message is placed in the child thread, and the timer is enabled to enable the runloop(for testing only, it can be set as required), or through a while loop

Note that the size of the buffer represents how much data can be read at one time. After each read, the data is removed from the buffer and the next read is the new content

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [self reciveMessage];
});
// The system starts to receive messages
- (void)reciveMessage {
    // 4. Receive data
    /** recv parameter 1> client socket 2> address of the received content buffer 3> Length of the received content cache 4> Receive mode. 0 indicates blocking and must wait for the server to return data
    // You can start a child thread in which runloop is enabled to receive messages periodically
    NSTimer *timer = [NSTimer timerWithTimeInterval:0.2 repeats:YES block:^(NSTimer * _Nonnull timer) {
        uint8_t buffer[1024];
        The received message is in the buffer. Note the size of the buffer. When the buffer is full, the data will be lost
        ssize_t recvLen = recv(self.clientId, buffer, sizeof(buffer), 0);
        if (recvLen == 0) {
            NSLog(@"Blank message received");
            return;
        }
        NSData *recvData = [NSData dataWithBytes:buffer length:recvLen];
        NSString *recvString = [[NSString alloc] initWithData:recvData encoding:NSUTF8StringEncoding];
        
        NSLog(@"Received message :%@", recvString);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
 }
Copy the code

Send a message

Sending a message is much easier than reading it, calling the send method directly to the send interface

const char *msg = self.tfMessage.text.UTF8String;
ssize_t sendLen = send(self.clientId,msg, strlen(msg), 0);
_tfMessage.text = @"";
NSLog(@"%ld bytes sent successfully", sendLen);
Copy the code

In this way, client socket communication can be achieved

The end of the communication

When the client ends, the client ID is passed in. When the server ends, the socketId is passed in. When the server ends the connection with a client, the socketId of that client is passed in

close(socketId)
Copy the code

Supplement the server part

The client differs from the server in that it binds, listens, receives client connections, and then can begin receiving messages from the client, or sending messages to a client

Server side binding

This is similar to connect on the client side, except that bind is called and socketId is passed in on the server side

int bindResult = bind(self.serverId, (const struct sockaddr *)&socketAddr, sizeof(socketAddr));
if (bindResult == -1) {
    NSLog(@"Failed to bind socket");
    return;
}
Copy the code

Server-side listening

When the server binding socket, you can monitor the client connections, here you can set the maximum number of client links, to ensure the normal operation of the server, to avoid server load running

// Enable the listener on the server and set the maximum number of connections to 5
int listenResult = listen(self.serverId, 5);
if (listenResult == -1) {
    NSLog(@"Listening failed.");
    return;
}
Copy the code

Start waiting for the client to connect

After the monitoring function is enabled, the server can prepare to receive client connections. In this case, all the connection channels are directly enabled. In fact, the connection channels can be dynamically expanded based on the number of connected channels to receive client connections

// Start receiving client connections
for (int i = 0; i < 5; i++) {
    [self acceptClientConnect];
}
    
    // Listen for client connections
- (void)acceptClientConnect {
    dispatch_async(dispatch_get_global_queue(0.0), ^{
        struct sockaddr_in client_address;
        socklen_t address_len;
        / / the accept function
        int clientSocketId = accept(self.serverId, 
            (struct sockaddr *)&client_address, &address_len);
        self.currentClientId = clientSocketId;
        
        if (clientSocketId == -1) {
            NSLog(@"Error accepting client: %u", address_len);
        }else{
            NSLog(@"Accept client success"); [self receiveMessage:clientSocketId]; }}); }Copy the code

The last

In this way, the server and client socket introduction is complete

Note: this is an exploration of ipv4 socket, if you want to compatible with ipv6, you can search for compatible solutions, you can also view the source code to solve the problem

Demo: Server Demo —- Client demo