preface
We mainly use HTTP/HTTPS requests for front-end development. Such requests are one-way from the perspective of data update, that is, only when users initiate requests can they obtain the latest data. But sometimes, some state and data changes need to be pushed to the front end in real time. For example, in the O2O industry, consumers place orders -> THE O2O company receives the order -> the delivery boy immediately receives the order -> consumers receive the distance between the delivery boy and themselves in real time. Among them, the last two steps to receive information immediately, you have to use Socket programming to maintain a long connection. For example, message push, voice chat, etc.
Note: HTTP can also be used to establish long connections, using Connection:keep-alive. HTTP 1.1 uses persistent connections by default. The biggest difference between HTTP1.1 and HTTP1.0 is the addition of persistent connection support (it looks like the latest HTTP1.0 can display keep-alive), but it is still stateless, or untrustworthy.
1. How do processes communicate with each other on the network?
There are many different ways of local interprocess communication (IPC), but they can be summarized in the following four categories:
- Messaging (pipes, FIFO, message queues)
- Synchronization (mutex, condition variable, read/write lock, file and write record lock, semaphore)
- Shared memory (anonymous and named)
- Remote procedure calls (Solaris door and Sun RPC)
How do processes communicate with each other on the network? The first problem to solve is how to uniquely identify a process, otherwise communication is impossible! A process can be uniquely identified locally by a process PID, but this is not possible on a network. In fact, the TCP/IP protocol family has helped us to solve this problem. The “IP address” of the network layer can uniquely identify the host in the network, and the “protocol + port” of the transport layer can uniquely identify the application program (process) in the host. In this way, a triplet (IP address, protocol, port) can be used to identify the process of the network, and the process communication in the network can use this flag to interact with other processes.
Applications using TCP/IP typically use application programming interfaces (APIS) : Sockets for UNIX BSD and TLI for UNIX System V (deprecated) to communicate between network processes. At present, almost all applications are using sockets, and now it is the network era, process communication is everywhere in the network, that is why I say “everything socket”.
2. What is a Socket
2.1 Socket Socket:
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 mode. A Socket is a special file, and 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 the user, a simple set of interfaces is all, let the Socket to organize data to conform to the specified protocol.
Note: socket does not have a layer concept, it is just a facade design pattern applied to make programming easier. Is a software abstraction layer. In network programming, we use a lot of socket implementation.
2.2 socket descriptors
The most familiar handles are 0, 1, and 2. 0 is standard input, 1 is standard output, and 2 is standard error output. 0, 1, 2 are integers, and the corresponding FILE * structures are stdin, stdout, and stderr
The socket API was originally developed as part of the UNIX operating system, so the socket API is integrated with the system’s other I/O devices. In particular, when an application creates a socket for Internet communication, the operating system returns a small integer as a descriptor to identify the socket. The application then takes that descriptor as a transfer parameter and calls a function to do something (such as send data over a network or receive incoming data).
On many operating systems, socket descriptors are integrated with other I/O descriptors, so applications can perform socket I/O or I/O read/write operations on files.
When an application creates a socket, the operating system returns a small integer as a descriptor, which the application uses to refer to the socket requiring I/O requests requesting the operating system to open a file. The operating system creates a file descriptor that the application can access. From an application’s perspective, a file descriptor is an integer that applications can use to read and write files. The following figure shows how the operating system implements the file descriptor as an array of Pointers to internal data structures.
There is a separate table for each program system. Specifically, the system maintains a separate list of file descriptors for each running process. When a process opens a file, the system writes a pointer to the file’s internal data structure to the file descriptor table and returns the index value of the table to the caller. The application just needs to remember the descriptor and use it later when it manipulates the file. The operating system uses this descriptor as an index to access the process descriptor table, using Pointers to find the data structure that holds all the information in the file.
System data structures for sockets:
Socket API has a socket function, which is used to create a socket. The general idea of socket design is that a single system call can create any socket, because sockets are fairly generic. Once the socket is created, the application needs to call additional functions to specify the details. For example, calling the socket creates a new descriptor entry:
2) Although the socket’s internal data structure contains many fields, most of the numeric fields are left unfilled after the socket is created. After the application creates a socket, it must call additional procedures to populate these fields before the socket can be used.
3. Basic Socket interface functions
The server first initializes/creates the Socket, then binds/binds the port, listens to the port, calls Accept to block/wait continuously, and waits for the client to connect. At this time, if a client initializes a Socket and then connects to the server (CONNECT), if the connection is successful, then the connection between the client and the server is established. The client sends a data request, the server receives and processes the request, then sends the response data to the client, the client reads the data, and finally closes the connection, ending an interaction.
3.1 socket function
Function prototype:
int socket(int protofamily, int type, int protocol);
Copy the code
Return value: // Return sockfd Sockfd is a descriptor similar to the open function.
Function function:
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.
Function parameters:
Protofamily: indicates a protocol domain, which is also called a protocol family. Common protocol families include AF_INET(IPV4), AF_INET6(IPV6), 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.
3.2. Bind ()
The bind() function assigns a specific address in an address family to the socket, or binds the IP port to the socket. For example, AF_INET and AF_INET6 assign an ipv4 or ipv6 address and port number to the socket.
Function prototype:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Copy the code
Function parameters:
- The three arguments to 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.
- Addr: a const struct sockaddr * pointer to the protocol address to be bound to sockfd. This address structure varies depending on the address protocol family used to create the socket.
- Addrlen: Corresponds to the length of the address.
Listen (), connect() functions
If a server calls socket() and bind(), it calls listen() to listen to the socket. If the client calls connect(), the server receives the connection request. Function prototype:
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
Copy the code
Function parameters: The first parameter of listen is the description of the socket to listen on, and the second parameter is the maximum number of connections that the corresponding 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. Returns 0 on success or -1 on failed connection.
3.4. Accept () function
Function The TCP server calls the socket(), bind(), and listen() in sequence to listen to 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. Function prototype:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); // Return the connection connect_fd
Copy the code
Function parameters: sockfd: The parameter sockfd is the listening socket explained above. This socket is used to listen for a port number that is associated with the socket when a client is connected to the server. Of course the client does not know the details of the socket, it only knows an address and a port number. Addr: This is a result parameter that accepts a return value that specifies the address of the client, which of course is represented by an address structure that the user should know about. If you are not interested in the customer’s address, you can set this value to NULL. Len: As you might expect, this is also the result parameter that accepts the size of the structure of the addr mentioned above. It specifies the number of bytes that the addr structure occupies. Again, it can be set to NULL. If accept returns successfully, the server and the client have been properly connected, and the server communicates with the client through the socket returned by Accept.
Note:
Accept blocks by default until a client connection is established and returns a newly available socket, which is a connection socket. At this point we need to distinguish between two types of sockets: listening socket: A listening socket, like the accept parameter sockfd, is a listening socket. After the listen function is called, the server starts calling the socket() function to generate the listening socket description (listening socket) connection socket: a socket is changed from an actively connected socket to a listening socket. The Accept function returns the connected socket descriptor (a connection socket), which represents a dot connection that already exists on the network. 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. The connection socket socketfd_new does not use the new port to communicate with the client, using the same port number as the listening socket socketfd
3.5 recv()/send() function
Of course, you can use other functions to transfer data, such as read and write.
3.5.1 track of the send
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
Copy the code
Both client and server applications use the SEND function to send data to the other end of the TCP connection.
The client typically sends requests to the server using send, and the server typically sends responses to the client using send. The first parameter specifies the sender socket descriptor; The second parameter specifies a buffer to hold the data the application wants to send; The third parameter specifies the actual number of bytes of data to send; The fourth argument is usually set to 0.
3.5.2 recv
int recv( SOCKET s, char FAR *buf, int len, int flags );
Copy the code
Both client and server applications use the RECV function to receive data from the other end of the TCP connection. The first argument to this function specifies the receiver socket descriptor; The second parameter specifies a buffer to hold the data received by the recV function. The third parameter specifies the length of the BUF; The fourth argument is usually set to 0.
3.6. Close () function
Function function: After the connection between the server and the client is established, some read and write operations will be performed. After the read and write operations are completed, the corresponding socket description word will be closed, such as the operation of the open file to close the open file to call fclose. Function prototype:
#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.
Note: The close operation only causes the reference count of the corresponding socket description to be -1, and only when the reference count is 0 triggers the TCP client to send a termination request to the server.
4, Linux Socket programming example
In Xshell5, we open two sessions to run socket_server and socket_client respectively.
4.1 write socket_server. C
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// Import the standard library for Socket programming
// The library: Linux data types (size_t, time_t, etc.)
#include <sys/types.h>
// Provide socket functions and data structures
#include <sys/socket.h>
// Sockaddr_in
#include <netinet/in.h>
//IP address conversion function
#include <arpa/inet.h>
// Define the server
#define SERVER_PORT 9999
int main(a){
// Step 2: Create socket
// Server socket
int server_socket_fd;
/ / the client
int client_socket_fd;
// Server network address
struct sockaddr_in server_addr;
// Client network address
struct sockaddr_in client_addr;
// Initialize the network address
$server_addr = $server_addr = $server_addr
// Parameter two: start
// Parameter three: size
// Initializes the server network address
memset(&server_addr,0.sizeof(server_addr ));
// Initialize the client network address
//memset(&client_addr,0,sizeof(client_addr));
// Set server network address - protocol family (sin_family)
//AF_INET:TCP/IP, UDP
/ / AF_ISO: ISO protocol
server_addr.sin_family = AF_INET;
// Set the server IP address (automatically obtain the system's default local IP address, automatically assigned)
server_addr.sin_addr.s_addr = INADDR_ANY;
// Set the server port
server_addr.sin_port = htons(SERVER_PORT);
// Create a server socket
// family: communication domain (e.g. IPV4->PF_INET, IPV6, etc......)
/ / parameter (type) : communication type (for example: TCP - > SOCK_STREAM, UDP - > SOCK_DGRAM, etc...)
// Parameter three (protocol) : specifies the protocol to use (default: 0)
// The default protocol is 0, so I use whatever the system supports
//TCP->IPPROTO_TCP
//UDP->IPPROTO_UDP
//SCTP->IPPROTO_SCTP
server_socket_fd = socket(PF_INET,SOCK_STREAM,0);
// Check whether the vm is successfully created
if(server_socket_fd <0) {printf("create error!");
return 1;
}
printf("Server created successfully! \n");
// Bind the server address
// Parameter 1: server socket
// Parameter 2: network address
// Parameter three: data type size
/ / socketaddr and sockaddr_in
bind(server_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
// Listen for client connections (server listening for client connections)
// Parameter one: listen to the server socket
// Number of clients (number of unprocessed queues)
listen(server_socket_fd,6);
// Receive client connections
// Parameter one (sockfd): server
// Addr: client
// Parameter three (addrlen): size
socklen_t sin_size = sizeof(struct sockaddr_in);
// Get a client
client_socket_fd= accept(server_socket_fd,(struct sockaddr*)&client_socket_fd,&sin_size);
// Check whether the client is successfully connected
if(client_socket_fd < 0) {printf("Connection failed");
return 1;
}
// Successful connection: client data is read
/ / BUFSIZ: the default value
char buffer[BUFSIZ];
int len=0;
while(1) {// Read client data (data source)
// Where to read (we want to read buffer)
// Parameter three: how much BUFSIZ is read each time
// Argument 4: Where do I start reading 0
len = recv(client_socket_fd,buffer,BUFSIZ,0);
if(len > 0) {// Data has been read
printf("%s\n",buffer); }}// Close the server and client sockets
// Parameter one: close the source
// Parameter 2: Type to close (set permissions)
//SHUT_RD: closes read (write only, read not allowed)
//SHUT_WR: closes write (read only, write not allowed)
//SHUT_RDWR: both read and write are closed (no writing is allowed)
shutdown(client_socket_fd,SHUT_RDWR);
shutdown(server_socket_fd,SHUT_RDWR);
printf("server end..... \n");
getchar(a);return 0;
}
Copy the code
4.2 perform socket_server. C
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -c socket_server.c
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -o socket_server socket_server.oRoot @ jdu4e00u53f7: / usr/kpioneer/pthread# / socket_server server to create success!Copy the code
4.3 write socket_client. C
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// Import the standard library for Socket programming
// This library: Linux data types (size_t, time_t, etc......)
#include<sys/types.h>
// Provide socket functions and data structures
#include<sys/socket.h>
// Data structure (sockaddr_in)
#include<netinet/in.h>
// IP address conversion function
#include<arpa/inet.h>
// Define the port number of the server
#define SERVER_PORT 9999
int main(a){
// Client socket
int client_socket_fd;
// Server network address
struct sockaddr_in server_addr;
// Client network address
struct sockaddr_in client_addr;
// Initialize the network address
$server_addr = $server_addr = $server_addr
// Parameter two: start position
// Parameter three: size
// Initializes the server network address
memset(&server_addr,0.sizeof(server_addr ));
//AF_INET:TCP/IP, UDP
/ / AF_ISO: ISO protocol
server_addr.sin_family = AF_INET;
// Set the server IP address (automatically obtain the system's default local IP address, automatically assigned)
server_addr.sin_addr.s_addr = INADDR_ANY;
// Set the server port
server_addr.sin_port = htons(SERVER_PORT);
// Create a client
client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
// Check whether the vm is successfully created
if(client_socket_fd < 0) {printf("create error!!!");
return 1;
}
// Connect to the server
// Parameter one: which client
// Parameter 2: address of the connection server
// Parameter three: address size
int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(con_result<0) {printf("connect error!");
return - 1;
}
printf("create Socket Client\n ");
// Send a message (to the server)
char buffer[BUFSIZ] = "Hello, Socket Server!";
// Parameter 1: Specify the client
// Specify a buffer (where data is read)
Strlen (buffer)(read until "\0")
// Parameter 4: Where to start reading
send(client_socket_fd,buffer,strlen(buffer),0);
/ / close
shutdown(client_socket_fd,SHUT_RDWR);
printf("client--- end-----\n");
return 0;
}
Copy the code
4.4 perform socket_client. C
root@jdu4e00u53f7:/usr/kpioneer/pthread# gcc -o socket_client socket_client.o
root@jdu4e00u53f7:/usr/kpioneer/pthread# ./socket_client
create Socket Client
client--- end-----
Copy the code
4.5 Checking socket_Server Information Again
We see the server created successfully! Socket Server! Socket Server! The program runs successfully.
5. Android Socket programming example (JNI implementation)
Let’s create a Java project and an Android project to run socket_server and Socket_client, respectively.
5.1 Java Engineering Side
5.1.1 write SocketServer. Java
package com.haocai;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer {
public static void main(String[] args) {
try{
ServerSocket serverSocket = new ServerSocket(a); serverSocket.bind(new InetSocketAddress("192.168.90.221".9998));
System.out.println("Server Start...);
while(true) {// Get the connection client
Socket socket = serverSocket.accept(a);// Read the content
new ReaderThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
static class ReaderThread extends Thread{
BufferedReader bufferedReader;
public ReaderThread(Socket socket){
try {
bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run(a) {
super.run(a);// loop through the content
String content = null;
while(true) {try {
while((content = bufferedReader.readLine())! =null){ System.out.println("Received client:"+content); }}catch (IOException e) {
e.printStackTrace(a); } } } } }Copy the code
5.1.2 run SocketServer
The server Start...Copy the code
5.2 Android Engineering side
5.2.1 Writing Java JNI declarations
package com.haocai.socketclient;
public class SocketUtil {
public native void startClient(String serverIp,int serverPort);
static {
System.loadLibrary("socketlib"); }}Copy the code
5.2.2 write socket_client. C
#include"com_haocai_socketclient_SocketUtil.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
// Import the standard library for Socket programming
// This library: Linux data types (size_t, time_t, etc......)
#include<sys/types.h>
// Provide socket functions and data structures
#include<sys/socket.h>
// Data structure (sockaddr_in)
#include<netinet/in.h>
// IP address conversion function
#include<arpa/inet.h>
#include <android/log.h>
#define LOG_TAG "socket_client"
#define LOGI(FORMAT,...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,FORMAT,##__VA_ARGS__);
#define LOGE(FORMAT,...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,FORMAT,##__VA_ARGS__);
#define LOGD(FORMAT,...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,FORMAT, ##__VA_ARGS__);
JNIEXPORT void JNICALL Java_com_haocai_socketclient_SocketUtil_startClient
(JNIEnv *env, jobject jobj, jstring server_ip_jstr, jint server_port){
const char* server_ip = (*env)->GetStringUTFChars(env, server_ip_jstr, NULL);
// Client socket
int client_socket_fd;
// Server network address
struct sockaddr_in server_addr;
// Initialize the network address
$server_addr = $server_addr = $server_addr
// Parameter two: start position
// Parameter three: size
// Initializes the server network address
memset(&server_addr,0.sizeof(server_addr));
//AF_INET:TCP/IP, UDP
/ / AF_ISO: ISO protocol
server_addr.sin_family = AF_INET;
// Set the server IP address (automatically obtain the system's default local IP address, automatically assigned)
server_addr.sin_addr.s_addr = inet_addr(server_ip);
// Set the server port
server_addr.sin_port = htons(server_port);
// Create a client
client_socket_fd = socket(PF_INET,SOCK_STREAM,0);
// Check whether the vm is successfully created
if(client_socket_fd < 0) {LOGE("create error!");
return ;
}
// Connect to the server
// Parameter one: which client
// Parameter 2: address of the connection server
// Parameter three: address size
int con_result = connect(client_socket_fd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(con_result<0) {LOGE("connect error!");
return ;
}
// Send a message (to the server)
char buffer[BUFSIZ] = "Hello Socket Server!";
// Parameter 1: Specify the client
// Specify a buffer (where data is read)
Strlen (buffer)(read until "\0")
// Parameter 4: Where to start reading
send(client_socket_fd,buffer,strlen(buffer),0);
/ / close
shutdown(client_socket_fd,SHUT_RDWR);
LOGI("client--- end-----");
(*env)->ReleaseStringUTFChars(env, server_ip_jstr, server_ip);
return ;
}
Copy the code
5.2.3 Calling the main program MainActivity
package com.haocai.socketclient;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
public class MainActivity extends AppCompatActivity {
public static final String SERVER_IP = "192.168.90.221";
public static final int SERVER_PORT = 9998;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void startSocket(View v){
new Thread(new Runnable() {
@Override
public void run() {
SocketUtil socketUtil = new SocketUtil(a); socketUtil.startClient(SERVER_IP,SERVER_PORT); }}).start();
}
}
Copy the code
5.3 Viewing the Java Project Log Again
Received: Hello Socket Server!Copy the code