In Java, a series of Socket apis are provided, which can easily establish a connection between two hosts and read data. Few people care about the underlying implementation. This is ultimately done by calling the Socket interface provided by the operating system (TCP/IP is implemented by the operating system).

We will not discuss TCP’s three-way handshake, four-way wave, etc. We will only discuss the interface provided by the operating system, and the use of these interfaces, as well as how the Java Socket is done at the bottom.

First, let’s take a look at the Socket programming interface provided by the operating system.

Functions provided by the operating system

Windows, for example, provides socket, bind, listen, accept, connect, send, recv and other functions. If you know socket programming, you should know at a glance what these functions are, such as connect to connect, send to send data. Recv receives data. (I was lucky enough to study some of them four years ago, and then switched to Java and didn’t go much further, but it still seems familiar to me now)…

(And Linux also has this set of functions)

Then we try to use these functions to create a simple communication program, for simplicity, easy to understand, will use VB programming. (or recall the original language), and people who know other languages can easily translate VB into their own language.

The first thing you need to do is declare some of the functions you’re going to use, as follows, and this is the painful part.

Private Type SOCKADDR sin_family As Integer sin_port As Integer sin_addr As Long sin_zero As String * 8 End Type Private  Declare Function socket Lib"ws2_32.dll" (ByVal af As Long, ByVal lType As Long, ByVal protocol As Long) As Long
Private Declare Function bind Lib "ws2_32.dll" (ByVal s As Long, ByRef addr As SOCKADDR, ByVal namelen As Long) As Long
Private Declare Function listen Lib "ws2_32.dll" (ByVal s As Long, ByVal backlog As Long) As Long
Private Declare Function recv Lib "ws2_32.dll" (ByVal s As Long, ByVal buf As String, ByVal lLen As Long, ByVal flags As Long) As Long
Private Declare Function accept Lib "ws2_32.dll" (ByVal s As Long, ByRef addr As SOCKADDR, ByRef addrlen As Long) As Long
Private Declare Function send Lib "ws2_32.dll" (ByVal s As Long, ByVal buf As String, ByVal lLen As Long, ByVal flags As Long) As Long
Private Declare Function closesocket Lib "ws2_32.dll" (ByVal s As Long) As Long
Private Declare Function connect Lib "ws2_32.dll" (ByVal s As Long, ByRef name As SOCKADDR, ByVal namelen As Long) As Long

Private Const WS2API_DECNET_MAX As Long = 10

Private Const sockaddr_size = 16
Private Const WSA_DESCRIPTIONLEN = 256
Private Const WSA_DescriptionSize = WSA_DESCRIPTIONLEN + 1
Private Const WSA_SYS_STATUS_LEN = 128
Private Const WSA_SysStatusSize = WSA_SYS_STATUS_LEN + 1
Private Declare Function WSAGetLastError Lib "ws2_32.dll" () As Long
Private Type WSAData
    wVersion As Integer
    wHighVersion As Integer
    szDescription As String * WSA_DescriptionSize
    szSystemStatus As String * WSA_SysStatusSize
    iMaxSockets As Integer
    iMaxUdpDg As Integer
    lpVendorInfo As Long
End Type
Private Declare Function WSAStartup Lib "ws2_32.dll" (ByVal wVersionRequired As Integer, ByRef lpWsAdata As WSAData) As Long
Private Declare Function WSACleanup Lib "ws2_32.dll"() As Long Private Const AF_INET As Long = 2 Private Const SOCK_STREAM As Long = 1 Private Const IPPROTO_TCP As Long = 6  Private Declare Function inet_addr Lib"ws2_32.dll" (ByVal cp As String) As Long
Private Declare Function htons Lib "ws2_32.dll" (ByVal hostshort As Integer) As Integer

Private Const SOMAXCONN As Long = &H7FFFFFFF
Private Const SOCKET_ERROR As Long = -1

Private Const AF_INET6 As Long = 23
Copy the code

Next we create a server Socket that accepts the client request and returns a string of characters. In Java, the server-side writing process should be clear: 1. Create ServerSocket, 2. Call ServerSocket and bind(). 3. Accept () to wait and return the client Socket.

This is probably the same process with Windows APIS. Let’s start with a simple flow chart.

0.WSAStartup

WSAStartup must be called to initialize the Winsock service before calling the API for socket programming, otherwise subsequent API calls will fail. The first parameter is to specify the winsock version number to load. The high byte is the minor version and the low byte is the major version, which can be specified by MAKEWORD (l, h). But VB doesn’t have this, so you have to write one yourself.

Private Function MakeWord(ByVal bLow As Byte, ByVal bHigh As Byte) As Integer
    MakeWord = bLow + bHigh * 256
End Function
Copy the code

LpWSAData: Pointer to the lpWSAData structure, which returns information about the dynamic library that was eventually loaded.

1. Create a Socket

The socket function takes three parameters: address family or protocol family, socket type, and transport protocol. Address family: indicates the IP address type. There are two common types: AF_INET(IPv4) and AF_INET6(IPv6). Socket type: there are SOCK_STREAM socket (face connection) and SOCK_DGRAM datagram socket (no connection), transport protocol: Proto_tcp (TCP transmission) and IPPROTO_UDP(UDP transmission) are commonly used. If the value is 0, the value is automatically selected based on the socket type specified above.

Dim lpWsAdata As WSAData
WSAStartup(MakeWord(4, 4), lpWsAdata)
hSocket = socket(AF_INET, SOCK_STREAM, 0)
Copy the code

2. The binding

The binding also requires three parameters, namely the socket descriptor, sockADDR, and sockADDR size. The first parameter is the return value of the socket function. The second sockaddr is a structure in which the information to be bound is stored. The assignment is performed using htons and inet_ADDR (or some other function). The third argument can be obtained by len(vb), sizeof(c).

 Dim lSocketAddress As SOCKADDR, hBind As Long
 lSocketAddress.sin_family = AF_INET
 lSocketAddress.sin_port = htons(2002)
 lSocketAddress.sin_addr = inet_addr("127.0.0.1")
 hBind = bind(hSocket, lSocketAddress, Len(lSocketAddress))
Copy the code

3. Listen

The listen function puts a socket in a state to listen for connection requests, and when called, the status can be viewed using the netstat command. If not called, subsequent accept will fail and result in a direct return.

In layman’s terms, if the size of the server queue is 10, if 10 people send requests to the server, and the server does not call Accept for the time being. An exception is thrown when there are other client requests until the server calls Accept to remove one from the queue to make room for subsequent requests.

 listen(hSocket, SOMAXCONN)
Copy the code

This may not need to be called manually in Java, but when we call the serversocket.bind () binding, it immediately calls the Listen method, which defaults to 50 if backlog is not specified.

4. Grant the request and return data

In Java, the Accept () method blocks until a connection is received, as is the Accept function, which blocks if there is no connection in the queue. Accept also takes three parameters: the socket descriptor, the second, the address of the client where the connection request is stored, and the third, the size of the second parameter. The return value is the socket descriptor for the requester.

The send function is used to send data to the socket. The parameters are the socket descriptor, the buffer to send data to, and the size of the data to send. The last parameter is usually 0.

  Dim lpAddR As SOCKADDR, hClientSocket As Long
  hClientSocket = accept(hSocket, lpAddR, LenB(lpAddR))
  
  Dim mBufData As String
  mBufData = "Hello Window Socket" + vbCrLf
  send hClientSocket, mBufData, LenB(mBufData), 0
Copy the code

The top of the server is basically done, the next is the client. The connect parameter is the same as the bind parameter on the server. The recv parameter is the socket descriptor, the buffer to store the received data, the size of the cache, and the last parameter is also 0. The return value is the length of the received data.

Dim lpWsAdata As WSAData

WSAStartup(MakeWord(4, 4), lpWsAdata)
Dim hSocket As Long
hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
If hSocket = 0 Then
    MsgBox WSAGetLastError
Else
   Dim lSocketAddress As SOCKADDR, mSocketConnectResult As Long
    lSocketAddress.sin_family = AF_INET
    lSocketAddress.sin_port = htons(2002)
    lSocketAddress.sin_addr = inet_addr("127.0.0.1")
    mSocketConnectResult = connect(hSocket, lSocketAddress, LenB(lSocketAddress))
    If mSocketConnectResult = 0 Then
        Dim sBuff As String * 255
        recv hSocket, sBuff, Len(sBuff), 0
        MsgBox sBuff
    Else
        MsgBox "Connection error"
    End If
End If
Copy the code

The overall parameters of each function are relatively simple, the following run. If you start the client after the server is started, the client receives data from the server.

  public static void main(String[] args) {
      try {
          Socket socket = new Socket("127.0.0.1".2002);
          BufferedReader bufferedReader =new BufferedReader(new InputStreamReader(socket.getInputStream()));
          System.out.println("Data."+bufferedReader.readLine());
          bufferedReader.close();
      } catch(IOException e) { e.printStackTrace(); }}Copy the code

When run, the effect is as follows

Java Socket analysis

When we analyze what the underlying call Java methods, are often found to be some native method, so, we need a its source code, can be downloaded at http://hg.openjdk.java.net/.

Before doing so, it is important to understand the inheritance structure.

SetImpl () is called directly in the empty constructor, where factory is assigned only after setSocketImplFactory is called, so ignore that and most importantly look at what is done in SocksSocketImpl.

 void setImpl(a) {
     if(factory ! =null) {
         impl = factory.createSocketImpl();
         checkOldImpl();
     } else {
         impl = new SocksSocketImpl();
     }
     if(impl ! =null)
         impl.setSocket(this);
 }
Copy the code

Nothing is done in the SocksSocketImpl constructor, but this is superficial. Remember that our class has its initialization order, so we need to see what its parent does.

  SocksSocketImpl() {
      // Nothing needed
  }
Copy the code

The PlainSocketImpl static code snippet identifies the Java runtime version and the preferIPv4Stack. If IPv6 is available on the operating system, the underlying native socket will by default be an IPv6 socket that enables applications to connect to and accept connections from both IPv4 and IPv6 hosts. However, this property can be set to true if the application would rather use an IPv4 only socket. This means that applications will not be able to communicate with ipv6-only hosts. The default value is false.

 PlainSocketImpl() {
     if (useDualStackImpl) {
         impl = new DualStackPlainSocketImpl(exclusiveBind);
     } else {
         impl = newTwoStacksPlainSocketImpl(exclusiveBind); }}Copy the code

The spacetime constructor probably ends up there, which doesn’t seem to do much, and of course, the connection logic is in socket.connect().

Leave out some of the judgment logic and just get to the point.

public void connect(SocketAddress endpoint, int timeout) throws IOException {...if(! created) createImpl(true);
    if(! oldImpl) impl.connect(epoint, timeout);else if (timeout == 0) {
        if (epoint.isUnresolved())
            impl.connect(addr.getHostName(), port);
        else
            impl.connect(addr, port);
    } else
        throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)"
    connected = true;
}
Copy the code

Connect (addr.gethostName (), port); The impl is an instance of the class that was initialized in the constructor, SocksSocketImpl, but createImpl(true) cannot be ignored; Create (stream), but SocksSocketImpl doesn’t override the create method, so we need to look in its parent class.

 void createImpl(boolean stream) throws SocketException {
    if (impl == null)
        setImpl();
    try {
        impl.create(stream);
        created = true;
    } catch (IOException e) {
        throw newSocketException(e.getMessage()); }}Copy the code

The impl. Create is called in its parent class PlainSocketImpl, where we already know who the IMPL implementation class is and how to determine it. The Create method is not overridden in the DualStackPlainSocketImpl.

 protected synchronized void create(boolean stream) throws IOException {
     impl.create(stream);
     // set fd to delegate's fd to be compatible with older releases
     this.fd = impl.fd;
 }
Copy the code

So AbstractPlainSocketImpl, it defaults to streaming, and the key is socketCreate, which is an abstract method that has to be implemented like this

 protected synchronized void create(boolean stream) throws IOException {
     this.stream = stream;
     if(! stream) { .......... }else {
         fd = new FileDescriptor();
         socketCreate(true); }... }abstract void socketCreate(boolean isServer) throws IOException;
Copy the code

So we’re going to go back to the DualStackPlainSocketImpl and look at socketCreate, and that’s where it ends, and the key point here is that socket0 is a local method.

  void socketCreate(boolean stream) throws IOException {
      if (fd == null)
          throw new SocketException("Socket closed");
      int newfd = socket0(stream, false /*v6 Only*/);
      fdAccess.set(fd, newfd);
  }
static native int socket0(boolean stream, boolean v6Only) throws IOException;
Copy the code

So let’s start looking at what’s going on in Socket0. It’s in DualStackPlainSocketImpl. C. There are a few key points here, AF_INET6, SOCK_STREAM, SOCK_DGRAM, which are very similar to the parameters we specified when we created the socket at the beginning. But it calls NET_Socket, so we’ll have to keep looking at what the NET_Socket method does.

JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_socket0
  (JNIEnv *env, jclass clazz, jboolean stream, jboolean v6Only /*unused*/) {
    int fd, rv, opt=0;
    / / create a Socket
    fd = NET_Socket(AF_INET6, (stream ? SOCK_STREAM : SOCK_DGRAM), 0); . I'm going to omit somereturn fd;
}
Copy the code

We traced it back to net_util_mD.c, and found that it was the socket function. And its parameters are created the same way we did.

Socket is already at the bottom, at the bottom is the implementation of the operating system of socket.

int NET_Socket (int domain, int type, int protocol) {
    SOCKET sock;
    sock = socket (domain, type, protocol);
    if(sock ! = INVALID_SOCKET) { SetHandleInformation((HANDLE)(uintptr_t)sock, HANDLE_FLAG_INHERIT, FALSE); }return (int)sock;
}
Copy the code

This not line, is not enough light to see the socket function, connect, listen, etc?

Slowly and connect directly in DualStackPlainSocketImpl. C can be seen, is comprised of socketConnect DualStackPlainSocketImpl method calls, the first parameter is the socket descriptor. The underlying call to NET_InetAddressToSockaddr converts the InetAddress to a SOCKETADDRESS, but the SOCKETADDRESS is not provided by Windows, it is defined by Windows itself, and this structure contains the familiar sockaddr.

typedef union {
    struct sockaddr     sa;
    struct sockaddr_in  sa4;
    struct sockaddr_in6 sa6;
} SOCKETADDRESS;
Copy the code
JNIEXPORT jint JNICALL Java_java_net_DualStackPlainSocketImpl_connect0 (JNIEnv *env, jclass clazz, jint fd, jobject iaObj, jint port) { SOCKETADDRESS sa; int rv, sa_len = 0; if (NET_InetAddressToSockaddr(env, iaObj, port, &sa, &sa_len, JNI_TRUE) ! = 0) { return -1; } rv = connect(fd, &sa.sa, sa_len); if (rv == SOCKET_ERROR) { int err = WSAGetLastError(); if (err == WSAEWOULDBLOCK) { return java_net_DualStackPlainSocketImpl_WOULDBLOCK; } else if (err == WSAEADDRNOTAVAIL) { JNU_ThrowByName(env, JNU_JAVANETPKG "ConnectException", "connect: Address is invalid on local machine, or port is not valid o } else { NET_ThrowNew(env, err, "connect"); } return -1; // return value not important. } return rv; }Copy the code

See listen again, also see DualStackPlainSocketImpl. C, a familiar figure, familiar with the parameters, but only the server Socket listen0 would call, namely ServerSocket.

JNIEXPORT void JNICALL Java_java_net_DualStackPlainSocketImpl_listen0
  (JNIEnv *env, jclass clazz, jint fd, jint backlog) {
    if (listen(fd, backlog) == SOCKET_ERROR) {
        NET_ThrowNew(env, WSAGetLastError(), "listen failed"); }}Copy the code

The recv function is in socketinputStream.c. So socket.getinputStream () returns a SocketInputStream.

There’s a lot of work going on here, but we’re still going to look at recV. Or familiar figure, familiar parameters, familiar 0.

JNIEXPORT jint JNICALL
Java_java_net_SocketInputStream_socketRead0(JNIEnv *env, jobject this,
                                            jobject fdObj, jbyteArray data,
                                            jint off, jint len, jint timeout)
{... nread = recv(fd, bufP, len,0); . }Copy the code

Otherwise, the process is similar if the Socket’s parameter constructor is called. You need to debug the trace code yourself.

For Windows, which may be different under Linux, the following is the inheritance structure of Linux, mainly implemented in a series of native methods in PlainSocketImpl. The corresponding C file, plainSocketimpl. c,

Personal public account