Socket 通信是一种网络通信的基本方式,它允许位于不同主机上的应用程序通过网络进行数据交换。Socket 使用 IP 地址和端口号来标识网络上的应用程序,确保数据准确地从一个程序传送到另一个程序(端到端)。我在多进程实现并发服务器-CSDN博客 处有进行更详细的代码介绍,建议看完这一节内容后,基于本节内容再去看看,一定会有非常深刻的理解!
总结成一句话:Socket套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。
Socket组成:
一个Socket可以看作是(IP地址,端口号,协议类型)的组合,它定义了一个网络上的端点,用于识别发送或接收数据的进程。这个组合确保了网络中的数据传输可以精确地从一个特定的源传输到一个特定的目标。
1. IP地址
IP地址用于标识网络中的主机。每个通过网络互连的设备都分配有一个唯一的IP地址,用于网络通信时识别发送方和接收方。在IPv4中,它是一个32位的数字,通常表示为点分十进制格式(例如,192.168.1.1)。IPv6地址更长,能够提供更多的地址空间。
2. 端口号
端口号是一个16位的数字,用于在同一主机内区分不同的进程或应用程序。由于一台主机上可能运行多个网络应用,端口号确保数据能够被送达到正确的程序。标准端口号范围是从0到65535,其中0到1023是为知名端口号,预留给特定的服务使用(例如HTTP服务通常使用端口80)。
3. 协议类型
在创建Socket时,需要指定使用的协议类型。常见的协议类型包括TCP(传输控制协议)和UDP(用户数据报协议)。TCP提供一种可靠的连接服务,包括数据传输的顺序保证和重传控制,适合需要可靠传输的应用。UDP则是一种简单的无连接协议,适用于对速度要求高但可以容忍一定丢包的应用。
Socket工作流程:
Socket 通信的工作流程可以分为服务器端和客户端两部分,下面是其基本步骤:
服务器端
1、创建 Socket:
- 服务器端应用程序首先需要创建一个 socket,指定通信的协议类型(通常是 TCP 或 UDP)。此时这个socket可以看成是一个文件描述符fd。
2、绑定 (Bind):
- 服务器将创建的 socket 绑定到一个本地地址和端口上。这个地址通常是服务器机器的 IP 地址(或者是一个特殊的地址,如 INADDR_ANY,表示接受任何来自本机网络接口的连接),而端口号是一个 16 位的数字,用于区分不同的服务。
3、监听 (Listen):
- 绑定之后,服务器需要调用 listen 函数将该 socket 设置为监听模式。在调用 listen 函数时,通常需要指定一个“backlog”参数,这个参数决定了服务器端 socket 在开始拒绝新的连接请求之前,可以排队等待接受的最大连接数。
-
int listen(int sockfd, int backlog);
sockfd
是要监听的 socket 的文件描述符。backlog
是等待接受的连接队列的最大长度。
- 当一个应用程序调用
listen
函数时,它告诉操作系统它愿意接受连接请求,但还没有准备好立即接受这些请求。这时,操作系统会为该 socket 维护两个队列:-
半连接队列(SYN队列):存放那些已经接收到客户端 SYN(连接请求)但服务器还没有完全建立连接的请求。这些是半开连接(TCP 三次握手中的第二步还未完成)。
-
全连接队列(ACCEPT队列):存放那些已经完成了三次握手过程、等待应用程序调用
accept
函数接受的连接。
-
4、接受连接 (Accept):
- 服务器端接受客户端的连接请求,建立一个新的通信连接。在 TCP 协议中,这一步会创建一个新的 socket,专门用于与该客户端的通信。
#include <sys/types.h> /* 包含socket类型定义 */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd
:这是一个处于监听状态的套接字(socket),之前已经通过调用socket
、bind
和listen
函数进行了创建和配置。addr(传出参数)
:这是一个指向struct sockaddr
结构的指针,用于接收新连接的客户端地址信息。当accept
函数返回时,这个结构会被填充。addrlen
:这是一个指向socklen_t
变量的指针,表示传入传出参数addr
结构的大小。当accept
函数返回时,这个变量将被设置为addr
结构的实际长度。
调用后发生的行为:
- 当
accept
函数调用时,它会检查指定的监听 socket 的等待连接队列。如果队列中有等待的连接请求,accept
就会创建一个新的 socket,这个新 socket 与发起连接的客户端之间建立了连接,然后accept
返回这个新 socket 的描述符。此后,服务器就可以通过这个新的 socket 与客户端进行通信了。 - 如果等待连接队列为空,行为则取决于监听 socket 的阻塞与否。对于阻塞模式的 socket,
accept
会阻塞调用线程,直到有新的连接请求;对于非阻塞模式的 socket,accept
会立即返回,通常是一个错误码,表示目前没有连接请求。
5、数据交换 (Receive/Send):
- 通过建立的连接,服务器端与客户端可以互相发送和接收数据。
- 这里要注意,我们用
recv()
或read()
接收数据时,是从内核态
操作系统内核的网络栈读取数据。如果有数据可用,recv()
会将数据从内核空间复制到应用程序提供的缓冲区中。同理,用send()
或write()
发送数据时,
该函数将应用程序的数据发送到操作系统内核的网络栈,然后由网络协议栈处理数据并通过网络发送出去。 -
无论是
read()/write()
还是send()/recv()
,当这些函数被调用时,数据会在用户空间(应用程序使用的内存)和内核空间(操作系统核心组件使用的内存)之间进行复制: - 在 写入操作(
write()
或send()
)中,数据从用户空间缓冲区复制到内核空间的缓冲区,然后由操作系统管理的网络栈处理发送过程。 - 在 读取操作(
read()
或recv()
)中,内核空间中接收到的数据被复制到用户空间的应用程序缓冲区中,以便应用程序进一步处理。
6、关闭连接:
- 数据传输完成后,服务器可以关闭与客户端的连接,结束通信。在某些情况下,客户端可能是先发起关闭连接的一方。
客户端
-
创建 Socket:
与服务器端类似,客户端应用程序首先创建一个 socket。此时这个socket可以看成是一个文件描述符fd -
连接 (Connect):
客户端使用创建的 socket,向服务器的指定地址和端口发起连接请求。 -
数据交换 (Send/Receive):
连接建立后,客户端可以发送数据给服务器,并接收服务器回发的数据。 -
关闭连接:
数据传输完成后,客户端可以发起关闭连接,结束与服务器的通信。