一文搞懂Socket网络编程

本文详细介绍了Socket的概念、作用,以及其在TCP/IP协议中的地位。重点讲解了TCP三次握手和四次挥手过程,并提供了客户端和服务端的示例代码,展示了Socket在网络编程中的应用,以及服务器如何利用多线程和线程池优化性能。
摘要由CSDN通过智能技术生成

🥑 一文搞懂Socket

一、什么是 Socket?

本质上就是两个进程之间的网络通信。其中一个进程必须充当服务器端,它会主动监听某个指定的端口,另一个进程必须充当客户端,它必须主动连接服务器的IP地址和指定端口,如果连接成功,服务器端和客户端就成功地建立了一个TCP连接,双方后续就可以随时发送和接收数据。

因此,当Socket连接成功地在服务器端和客户端之间建立后:

  • 对服务器端来说,它的Socket是指定的IP地址和指定的端口号;
  • 对客户端来说,它的Socket是它所在计算机的IP地址和一个由操作系统分配的随机端口号。

二、为什么要使用 Socket?有什么作用?

因为仅仅通过IP地址进行通信是不够的,同一台计算机同一时间会运行多个网络应用程序,例如浏览器、QQ、邮件客户端等。当操作系统接收到一个数据包的时候,如果只有IP地址,它没法判断应该发给哪个应用程序,所以,操作系统抽象出Socket接口,每个应用程序需要各自对应到不同的Socket,数据包才能根据Socket正确地发到对应的应用程序。

三、TCP/IP 协议

socket 是基于 tcp/ip 协议上的网络编程,下图描绘了 socket 在 osi 模型中大致的位置
socket在 osi 模型中的位置

从图上看,socket 抽象层位于传输层和应用层之间,使用tcp/ip中的三次握手、四次挥手进行建立连接、断开连接。

TCP三次握手

三次握手

客户端想建立连接,发送一个 SYN 包给服务端,服务端收到以后返回一个 SYN 包和 ACK 包,客户端接收到以后,发送 ACK 包到服务端,连接建立。因为这个过程中,发生了三包数据,所以叫三次握手。

四次挥手

四次挥手

客户端与服务端都可发起断开连接,这里距离是客户端主动发起断开连接

第一次挥手:客户端发一包 FIN 包,自身进入断开等待状态 1,表示要断开连接。

第二次挥手:服务端向客户端发送一个 ACK 包,自身进入关闭等待状态,客户端进入等待状态 2,此时客户端还是可以发送消息,服务端可以接收消息。

第三次挥手:服务端发送最终确认 FIN 包,确认是否断开连接,自身进入等待确认状态。

第四次挥手:客户端发送 ACK 包,确认断开连接。自身进入超时等待状态,到时间关闭连接。服务端一旦收到 ACK 包,立即断开连接。「这里客户端进入超时等待状态而不是立马断开连接,是因为怕 ACK 包丢失后,服务端就一直处于等待关闭确认状态了」

Socket 通信

在这里插入图片描述

根据socket 接口绘制的,结合下面的代码相互映照的看。

🌵四、实现代码

客户端代码

package com.xiezc.socketdemo.client;

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;

/**
 * SocketClient
 *
 * @author XieZhongCai
 * @version v1.0.0
 * @description 客户端代码
 * @date 2024/3/26 16:17
 **/
public class SocketClient {
    public static void main(String[] args) throws IOException {
        Socket sock = new Socket("localhost", 6666); // 连接指定服务器和端口
        try (InputStream input = sock.getInputStream()) {
            try (OutputStream output = sock.getOutputStream()) {
                handle(input, output);
            }
        }
        sock.close();
        System.out.println("disconnected.");
    }

    private static void handle(InputStream input, OutputStream output) throws IOException {
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        Scanner scanner = new Scanner(System.in);
        System.out.println("[server] " + reader.readLine());
        while (true) {
            System.out.print(">>> "); // 打印提示
            String s = scanner.nextLine(); // 读取一行输入
            writer.write(s);
            writer.newLine();
            writer.flush();
            String resp = reader.readLine();
            System.out.println("<<< " + resp);
            if (resp.equals("bye")) {
                break;
            }
        }
    }
}

创建一个socket 客户端,将输入结果发送出去,同时打印接受的消息,当输入“bye”断开连接

writer.newLine();

这里加 newLine()就是加一个换行符,这是跨平台的写法。等效windows下面的writer.write(“\r\n”);

readLine()必须读到一个\n才会返回,两边都要加,如果调用了 flush()之后,消息没有发出,一直在读,可能就是代码里面少了一个换行符。

writer.flush();

以流的形式写入数据的时候,并不是一写入就立刻发送到网络,而是先写入内存缓冲区,直到缓冲区满了以后,才会一次性真正发送到网络,这样设计的目的是为了提高传输效率。如果缓冲区的数据很少,而我们又想强制把这些数据发送到网络,就必须调用flush()强制把缓冲区数据发送出去。

服务端代码

package com.xiezc.socketdemo.server;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

/**
 * SocketServer
 *
 * @author XieZhongCai
 * @version v1.0.0
 * @description 服务端代码
 * @date 2024/3/26 16:17
 **/
public class SocketServer {
    public static void main(String[] args) throws IOException {
        ServerSocket ss = new ServerSocket(6666);
        System.out.println("server is running...");
        for (;;) {
            Socket sock = ss.accept();
            System.out.println("connected from " + sock.getRemoteSocketAddress());
            Thread t = new Handler(sock);
            t.start();
        }
    }
}

class Handler extends Thread {
    Socket sock;

    public Handler(Socket sock) {
        this.sock = sock;
    }

    @Override
    public void run() {
        try (InputStream input = this.sock.getInputStream()) {
            try (OutputStream output = this.sock.getOutputStream()) {
                handle(input, output);
            }
        } catch (Exception e) {
            try {
                this.sock.close();
            } catch (IOException ioe) {
            }
            System.out.println("client disconnected.");
        }
    }

    private void handle(InputStream input, OutputStream output) throws IOException {
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
        BufferedReader reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
        writer.write("hello\n");
        writer.flush();
        for (;;) {
            String s = reader.readLine();
            if (s.equals("bye")) {
                writer.write("bye\n");
                writer.flush();
                break;
            }
            writer.write("ok: " + s + "\n");
            writer.flush();
        }
    }
}

创建一个 socket 服务端,当接受到消息的处理方式是返回接受到的消息,如果接收到 bye,返回并且关闭连接。

ServerSocket ss = new ServerSocket(6666);

服务端指定监听端口,创建一个 Socket 对象,ServerSocket()里面内包含了,Socket()、bind()、listen()方法。

for (;;) {
  Socket sock = ss.accept();
  System.out.println("connected from " + sock.getRemoteSocketAddress());
  Thread t = new Handler(sock);
  t.start();
}

有新的客户端连接进来,就创建一个线程去处理,可以达到多线程并发处理的效果,但不要像上面显示的创建线程,一定要用线程池去处理。

五、总结

  • 服务器端用ServerSocket监听指定端口;
  • 客户端使用Socket(InetAddress, port)连接服务器;
  • 服务器端用accept()接收连接并返回Socket
  • 双方通过Socket打开InputStream/OutputStream读写数据;
  • 服务器端通常使用多线程同时处理多个客户端连接,利用线程池可大幅提升效率;
  • flush()用于强制输出缓冲区到网络。

六、参考

[1]. https://blog.csdn.net/fuhanghang/article/details/114528098 (顺其自然)

[2]. https://www.liaoxuefeng.com/wiki/1252599548343744/1305207629676577 (廖雪峰)

  • 49
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
网络技术中,端口号是用来标识应用程序或服务的特定通信端口的数字。socket是一种用于在计算机网络中进行通信的编程接口。它通过使用端口号来建立网络连接。 具体来说,端口号是一个16位的数字,范围从0到65535。其中,0到1023的端口号被称为知名端口(well-known port),用于常见的应用程序和服务,如HTTP(端口号80)、FTP(端口号21)等。而1024到49151的端口号被称为注册端口,用于分配给用户自定义的应用程序和服务。最后,49152到65535的端口号被称为动态或私有端口,用于临时分配给客户端应用程序。 在 socket 通信中,服务器通常会监听一个特定的端口号,用于接受来自客户端的连接请求。而客户端则会选择一个未被使用的端口号来与服务器进行通信。这样,通过不同的端口号组合,可以实现多个应用程序或服务之间的并行通信。 总结来说,socket是一种网络编程接口,而端口号则用于标识应用程序或服务的特定通信端口,从而实现网络连接和通信。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [socket连接方式及端口Port](https://blog.csdn.net/z15732621582/article/details/79603565)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [socket IP地址和端口号](https://blog.csdn.net/y_dd6011/article/details/120931161)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值