Socket API:传输层向应用层提供的编程接口

目录

1、概念

2、核心函数

2.1 创建套接字: socket()

2.2 绑定地址: bind()

2.3 监听连接: listen() (仅 TCP 服务器)

2.4 接受连接: accept() (仅 TCP 服务器) -- 阻塞

2.5 发起连接: connect() (TCP 客户端 / UDP 可选) -- 阻塞

2.6 发送数据:send() -- 阻塞

2.7 接收数据:read() -- 阻塞

2.8 关闭连接 / 释放套接字:close()

3、代码实现(TCP)


1、概念

Socket(套接字):

  • 可以想象成是网络通信的一个端点,是两台主机之间逻辑连接的端点。

  • 它是操作系统内核提供的一个抽象对象,代表了一个通信通道的一端。

  • 它包含进行网络通信必须的五种信息:连接使用的协议(TCP/UDP/本机)、本地主机IP地址、本地进程的协议端口、远程主机的IP地址、远程进程的协议端口。

Socket API:

  • 本质上提供了一组函数(API),让开发者能够创建、配置、使用和管理网络通信的端点(“Socket”)

2、核心函数

2.1 创建套接字: socket()

  • int socket(int domain, int type, int protocol);

  • 创建一个新的套接字对象。

  • 返回一个套接字描述符(非负整数),用于后续操作。失败返回 -1

2.2 绑定地址: bind()

  • int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • 将套接字(sockfd)与一个特定的本地地址(addr)和端口号关联起来。

  • 服务器端必须调用,以指定在哪个 IP 和端口上监听连接。

  • 客户端通常不需要显式调用 bind(),系统会在连接时自动分配一个临时端口和合适的 IP。

2.3 监听连接: listen() (仅 TCP 服务器)

  • int listen(int sockfd, int backlog);

  • 将之前 bind 的套接字标记为被动套接字,使其准备好接受传入的连接请求。

  • backlog 参数指定等待连接队列的最大长度(已完成三次握手等待 accept 的连接数)。超过此长度的新连接可能会被拒绝。

2.4 接受连接: accept() (仅 TCP 服务器) -- 阻塞

  • int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • 从监听套接字(sockfd)的等待连接队列中,提取第一个连接请求。

  • 创建一个全新的套接字,专门用于与这个新客户端通信。

  • 返回这个新套接字的描述符。

  • 如果提供了 addr 和 addrlen,它们会被填充为发起连接的对端(客户端)的地址信息。

  • 这是一个阻塞调用(默认情况下),如果没有连接请求,它会一直等待。

2.5 发起连接: connect() (TCP 客户端 / UDP 可选) -- 阻塞

  • int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • (TCP): 尝试与 addr 指定的服务器建立连接(发起 TCP 三次握手)。连接建立后,sockfd 就代表这个连接。

  • (UDP): 指定默认的目标地址。之后在此套接字上调用 send() (而不是 sendto()) 会发送到该地址,recv() (而不是 recvfrom()) 只接收来自该地址的数据。但 UDP 本身仍是无连接的。

2.6 发送数据:send() -- 阻塞

  • TCP (send() / write())

    • ssize_t send(int sockfd, const void *buf, size_t len, int flags);

    • 通过已连接的 TCP 套接字发送数据。数据被放入内核发送缓冲区,由 TCP 协议保证可靠、有序传输。flags 可控制行为(如 MSG_OOB 发送带外数据)。

  • UDP (sendto())

    • ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

    • 向 dest_addr 指定的目标地址发送一个 UDP 数据报。每次发送都需要指定目标地址(除非之前 connect 过)。

2.7 接收数据:read() -- 阻塞

  • TCP (recv() / read())

    • ssize_t recv(int sockfd, void *buf, size_t len, int flags);

    • 已连接的 TCP 套接字接收数据。从内核接收缓冲区读取数据流。flags 可控制行为(如 MSG_PEEK 查看数据但不移除)。

  • UDP (recvfrom())

    • ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

    • 接收一个 UDP 数据报。如果提供了 src_addr 和 addrlen,它们会被填充为发送数据报的对端地址。

2.8 关闭连接 / 释放套接字:close()

  • close() / closesocket()

    • int close(int sockfd); (POSIX)

    • int closesocket(SOCKET s); (Winsock)

    • 关闭套接字描述符,释放相关资源。对于 TCP,会发送 FIN 段发起四次挥手关闭连接。

  • 优雅关闭: shutdown()

    • int shutdown(int sockfd, int how);

    • 更精细地控制关闭过程:

      • SHUT_RD: 关闭读端(不再接收数据)。

      • SHUT_WR: 关闭写端(发送 FIN,不再发送数据)。

      • SHUT_RDWR: 同时关闭读写(相当于 close,但更显式)。

    • 常用于通知对端“我已经发送完数据了”,但仍可以接收对端发送的最后数据。

3、代码实现(TCP)

服务端

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

public class TCPServer {
    public static void main(String[] args) throws Exception {
        //1.创建一个线程池,如果有客户端连接就创建一个线程, 与之通信
        ExecutorService executorService = Executors.newCachedThreadPool();
        //2.创建 ServerSocket 对象
        ServerSocket serverSocket = new ServerSocket(9999);
        System.out.println("服务器已启动");
        while (true) {
            //3.监听客户端
            Socket socket = serverSocket.accept();
            System.out.println("有客户端连接");
            //4.开启新的线程处理
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    handle(socket);
                }
            });
        }
    }
    public static void handle(Socket socket) {
        try {
            System.out.println("线程ID:" + Thread.currentThread().getId()
                + " 线程名称:" + Thread.currentThread().getName());
            //从连接中取出输入流来接收消息
            InputStream is = socket.getInputStream();
            byte[] b = new byte[1024];
            int read = is.read(b);
            System.out.println("客户端:" + new String(b, 0, read));
            //连接中取出输出流并回话
            OutputStream os = socket.getOutputStream();
            os.write("响应信息".getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                //关闭连接
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;
public class ClientDemo {
    public static void main(String[] args) throws Exception {
        while (true) {
            //1.创建 Socket 对象
            Socket s = new Socket("127.0.0.1", 9999);
            //2.从连接中取出输出流并发消息
            OutputStream os = s.getOutputStream();
            System.out.println("请输入:");
            Scanner sc = new Scanner(System.in);
            String msg = sc.nextLine();
            os.write(msg.getBytes());
            //3.从连接中取出输入流并接收回话
            InputStream is = s.getInputStream();
            byte[] b = new byte[1024];
            int read = is.read(b);
            System.out.println("服务端说:" + new String(b, 0, read).trim());
            //4.关闭
            s.close();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

熙客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值