目录
2.3 监听连接: listen() (仅 TCP 服务器)
2.4 接受连接: accept() (仅 TCP 服务器) -- 阻塞
2.5 发起连接: connect() (TCP 客户端 / UDP 可选) -- 阻塞
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();
}
}
}