网络编程

socket

https://tva1.sinaimg.cn/large/007S8ZIlly1ggnbkjgktkj30ip0at0wi.jpg

https://tva1.sinaimg.cn/large/007S8ZIlly1gizdl86g9sj30da0dnwex.jpg

  1. 服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。
  2. 如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。
  3. 客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接。

三次握手

  1. 当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,此时connect进入阻塞状态
  2. 服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态
  3. 客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认,发送 ACK K+1
  4. 服务器收到ACK K+1时,accept返回

四次挥手

  1. 主动关闭的首先调用close主动关闭连接,这时TCP发送一个FIN M

  2. 接收到FIN M之后,被动关闭的一方执行被动关闭,对FIN进行确认。

    FIN也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程不会再接受到数据

  3. 接收到文件结束符的应用进程调用 close 关闭它的socket,然后它的TCP也发送一个FIN N

  4. 主动关闭的一方收到 FIN N, 并发送ACK N+1 确认

函数

connect( )

客户端主动连接服务器(阻塞),建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接,最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。

listen( )

非阻塞,仅仅将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后listen()函数就结束。

accept( )

从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept( ) 函数就会阻塞,直到取出队列中已完成的用户连接为止。

TCP服务端
sock = socket.socket()  # 创建一个套接字
sock.bind()  # 绑定端口
sock.listen()  # 监听连接
sock.accept()  # 接受新连接
sock.close()  # 关闭服务器套接字

TCP客户端
sock = socket.socket()  # 创建一个套接字
sock.connect()  # 连接远程服务器
sock.recv() # 读
sock.send()  # 写
sock.sendall()  # 完全写
sock.close()  # 关闭

JAVA DEMO

服务器端

public class SocketServer {
    public static void main(String[] args) throws IOException {
        // 端口号
        int port = 7000;
        // 在端口上创建一个服务器套接字
        ServerSocket serverSocket = new ServerSocket(port);
        // 监听来自客户端的连接
        Socket socket = serverSocket.accept();

        DataInputStream dis = new DataInputStream(
                new BufferedInputStream(socket.getInputStream()));

        DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(socket.getOutputStream()));
        do {
            double length = dis.readDouble();
            System.out.println("服务器端收到的边长数据为:" + length);
            double result = length * length;
            dos.writeDouble(result);
            dos.flush();
        } while (dis.readInt() != 0);

        socket.close();
        serverSocket.close();
    }
}

多线程服务器端

public class SocketServerM {
    public static void main(String[] args) throws IOException {
        int port = 7000;
        int clientNo = 1;
        ServerSocket serverSocket = new ServerSocket(port);
        // 创建线程池
        ExecutorService exec = Executors.newCachedThreadPool();
        try {
            while (true) {
                Socket socket = serverSocket.accept();
                exec.execute(new SingleServer(socket, clientNo));
                clientNo++;
            }
        } finally {
            serverSocket.close();
        }
    }
}

class SingleServer implements Runnable {
    private Socket socket;
    private int clientNo;
    public SingleServer(Socket socket, int clientNo) {
        this.socket = socket;
        this.clientNo = clientNo;
    }

    @Override
    public void run() {
        try {
            DataInputStream dis = new DataInputStream(
                    new BufferedInputStream(socket.getInputStream()));
            DataOutputStream dos = new DataOutputStream(
                    new BufferedOutputStream(socket.getOutputStream()));
            do {
                double length = dis.readDouble();
                System.out.println("从客户端" + clientNo + "接收到的边长数据为:" + length);
                double result = length * length;
                dos.writeDouble(result);
                dos.flush();
            } while (dis.readInt() != 0);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            System.out.println("与客户端" + clientNo + "通信结束");
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端

public class SocketClient {
    public static void main(String[] args) throws UnknownHostException, IOException {
        int port = 7000;
        String host = "localhost";
        // 创建一个套接字并将其连接到指定端口号
        Socket socket = new Socket(host, port);
        DataInputStream dis = new DataInputStream(
                new BufferedInputStream(socket.getInputStream()));
        DataOutputStream dos = new DataOutputStream(
                new BufferedOutputStream(socket.getOutputStream()));
        Scanner sc = new Scanner(System.in);
        boolean flag = false;

        while (!flag) {
            System.out.println("请输入正方形的边长:");
            double length = sc.nextDouble();
            dos.writeDouble(length);
            dos.flush();
            double area = dis.readDouble();
            System.out.println("服务器返回的计算面积为:" + area);

            while (true) {
                System.out.println("继续计算?(Y/N)");
                String str = sc.next();
                if (str.equalsIgnoreCase("N")) {
                    dos.writeInt(0);
                    dos.flush();
                    flag = true;
                    break;
                } else if (str.equalsIgnoreCase("Y")) {
                    dos.writeInt(1);
                    dos.flush();
                    break;
                }
            }
        }
        socket.close();
    }
}