Socket网络编程

1、什么是网络编程

      使用IP地址或域名和端口连接到另一台计算机上对应的程序,按照规定的协议(数据格式)来交换数据。

网络模型图:

 

2、TCP与UDP在概念上的区别

UDP:

  1. 面向无连接, 将数据封装成数据包中
  2. 每个数据报的大小在限制64k内
  3. 因无连接,是不可靠协议
  4. 不需要建立连接,速度快

TCP:

  1. 建立连接,形成传输数据的通道.
  2. 在连接中以字节流方式进行大数据量传输
  3. 通过三次握手完成连接,是可靠协议
  4. 必须建立连接,效率会稍低

3、UDP通讯案例

     通过UDP协议实现客户端与服务器端进行传输

package com.zhang.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

//socket服务器端
public  class UdpSocketServer {

    public static void main(String[] args) throws IOException {
        System.out.println("udp服务器端启动连接....");
        DatagramSocket datagramSocket=new DatagramSocket(8099);
        byte[] bytes=new byte[1024];
        DatagramPacket datagramPacket=new DatagramPacket(bytes,bytes.length);
        // 阻塞,等待接受客户端发送请求
        datagramSocket.receive(datagramPacket);
        System.out.println("来源:"+datagramPacket.getAddress()+",端口号:"+datagramPacket.getPort());
        // 获取客户端请求内容
        String string=new String(datagramPacket.getData(),0,datagramPacket.getLength());
        System.out.println(string);
datagramSocket.close();
} }
package com.zhang.socket;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
//客户端
public class UdpClient {

     public static void main(String[] args) throws IOException {
         System.out.println("udp客户端启动连接....");
         DatagramSocket ds = new DatagramSocket();
         String str="哈哈哈";
         byte[] bytes= str.getBytes();
         DatagramPacket dp= new DatagramPacket(bytes, bytes.length, InetAddress.getByName("127.0.0.1"),8099);
         ds.send(dp);
         ds.close();
    }
    
}

4、TCP三次握手协议
第一次握手:建立连接时,客户端发送SYN包(SYN=J)到服务器,并进入SYN_SEND状态,等待服务器确认; 
第二次握手:服务器收到SYN包,必须确认客户的SYN(ACK=J+1),同时自己也发送一个SYN包(SYN=K),即SYN+ACK包,此时服务器V状态; 
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ACK=K+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手,客户端与服务器开始传送数据。

5、TCP四次分手协议

       由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

  1. 客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。
  2. 服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
  3. 服务器B关闭与客户端A的连接,发送一个FIN给客户端A。
  4. 客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

6、为什么建立连接协议是三次握手,而关闭连接却是四次握手呢

       这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以 未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报 文和FIN报文多数情况下都是分开发送的。

7、为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态

       这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。

8、TCP通讯案例

package com.zhang.socket;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

//服务端
class TcpServer {

    public static void main(String[] args) throws IOException {
        System.out.println("socket tcp服务器端启动....");

        ServerSocket serverSocket = new ServerSocket(8099);
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int len = inputStream.read(bytes);
        String string = new String(bytes, 0, len);
        System.out.println(string);
        serverSocket.close();

    }
}
package com.zhang.socket;

import java.io.OutputStream;
import java.net.Socket;

//客户端
public class TcpClient {
    public static void main(String[] args) throws Exception {
        System.out.println("socket tcp 客户端启动....");
        Socket socket = new Socket("127.0.0.1", 8099);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("哈哈哈".getBytes());
        socket.close();
    }
}

9、使用多线程支持多个请求

服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善

package com.zhang.tcp;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

//tcp服务器端...
class TcpServer {

    public static void main(String[] args) throws Exception {
        System.out.println("socket tcp服务器端启动....");
        ServerSocket serverSocket = new ServerSocket(8099);
        try {
            while (true) {
                final Socket socket = serverSocket.accept();
                new Thread(new Runnable() {
                    public void run() {
                        try {
                            InputStream stream = socket.getInputStream();
                            byte[] bytes = new byte[1024];
                            int len = stream.read(bytes);
                            String string = new String(bytes, 0, len);
                            System.out.println("接收到的数据:"+string);

                        } catch (Exception e) {
                            e.fillInStackTrace();
                        }

                    }
                }).start();
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            serverSocket.close();
        }
    }

}
package com.zhang.tcp;

import java.io.OutputStream;
import java.net.Socket;
//客户端
public class TcpClient {
    public static void main(String[] args) throws Exception {
        System.out.println("socket tcp 客户端启动....");
        Socket socket = new Socket("127.0.0.1", 8099);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("哈哈哈hhhhh".getBytes());
        socket.close();
    }
}

利用线程池

package com.zhang.tcp;

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//tcp服务器端...
class TcpServer2 {

    public static void main(String[] args) throws Exception {
        System.out.println("socket tcp服务器端启动....");
        ExecutorService newCachedThreadPool=Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(8099);
        try {
            while (true) {
                final Socket socket = serverSocket.accept();
                newCachedThreadPool.execute(new Runnable() {
                    public void run() {
                        try {
                            InputStream stream = socket.getInputStream();
                            byte[] bytes = new byte[1024];
                            int len = stream.read(bytes);
                            String string = new String(bytes, 0, len);
                            System.out.println("接收到的数据:"+string);

                        } catch (Exception e) {
                            e.fillInStackTrace();
                        }
                    }
                });
            }
        } catch (Exception e) {
            e.fillInStackTrace();
        } finally {
            serverSocket.close();
        }
    }

}

10、TCP为什么是三次握手,为什么不是两次或者四次

       首先,我们要知道TCP是全双工的,即客户端在给服务器端发送信息的同时,服务器端也可以给客户端发送信息。而半双工的意思是A可以给B发,B也可以给A发,但是A在给B发的时候B不能给A发,即不能同时。 单工为只能A给B发,B不能给A发; 或者是只能B给A发,不能A给B发。

我们假设A和B是通信的双方。我理解的握手实际上就是通信,发一次信息就是进行一次握手。

  1. 第一次握手: A给B打电话说,你可以听到我说话吗
  2. 第二次握手: B收到了A的信息,然后对A说: 我可以听得到你说话啊,你能听得到我说话吗
  3. 第三次握手: A收到了B的信息,然后说可以的,我要给你发信息啦

在三次握手之后,A和B都能确定这么一件事: 我说的话,你能听到; 你说的话,我也能听到。 这样,就可以开始正常通信了。

如果两次,那么B无法确定B的信息A是否能收到,所以如果B先说话,可能后面的A都收不到,会出现问题 。

如果四次,那么就造成了浪费,因为在三次结束之后,就已经可以保证A可以给B发信息,A可以收到B的信息; B可以给A发信息,B可以收到A的信息。

 

原文地址:https://www.cnblogs.com/zhangjinru123/p/10429816.html