套接字(Socket)是计算机网络中进行数据通信的一种接口,它提供了一种在不同进程或不同主机之间进行通信的机制。套接字通信可以分为本地进程通信和网络通信两种。
本地进程通信:
本地进程通信(Local Process Communication)指的是在同一台机器上的不同进程之间进行通信。常见的本地进程通信方式有管道(Pipe)、命名管道(Named Pipe)、消息队列(Message Queue)、共享内存(Shared Memory)和信号(Signal)等。
套接字也可以用于本地进程通信,这通常是通过UNIX域套接字(UNIX Domain Socket)来实现的。UNIX域套接字提供了一种在同一台机器上的不同进程之间进行通信的机制,它类似于网络套接字,但是不需要经过网络协议栈,因此性能更高。
网络通信:
网络通信指的是在不同主机上的进程之间进行通信。网络通信通常使用TCP/IP协议族中的套接字来实现。TCP/IP协议族包括TCP(传输控制协议)和UDP(用户数据报协议)等协议。
网络通信中的套接字通信流程大致如下:
服务端:
创建一个套接字(Socket)对象。
绑定(Bind)套接字到一个本地地址和端口。
开始监听(Listen)来自客户端的连接请求。
接受(Accept)一个客户端连接请求,返回一个新的套接字用于与该客户端通信。
使用返回的套接字进行数据的发送和接收。
客户端:
创建一个套接字(Socket)对象。
连接到(Connect)服务端的地址和端口。
使用套接字进行数据的发送和接收。
通信原理:
无论是本地进程通信还是网络通信,套接字通信的基本原理都是基于“套接字地址”进行寻址和通信。每个套接字都有一个唯一的地址,这个地址由IP地址和端口号组成。服务端和客户端通过套接字地址建立连接,并通过这个连接进行数据的发送和接收。
掌握使用文件套接字实现本地进程通信的原理及实现
文件套接字(File Socket)或称为UNIX域套接字(UNIX Domain Socket)是一种特殊的套接字类型,它用于在同一台机器上的不同进程之间进行通信。文件套接字并不通过网络协议栈进行通信,而是通过在文件系统中创建一个特殊的文件节点来实现进程间的通信。
原理:
文件套接字的原理与网络套接字类似,但是它们之间的区别在于文件套接字在文件系统中有一个与之对应的文件节点(通常位于/tmp目录下)。当一个进程创建了一个文件套接字后,它可以通过该文件节点与其他进程进行通信。
实现:
使用文件套接字实现本地进程通信的步骤如下:
服务端:
创建一个文件套接字对象。
绑定(Bind)文件套接字到一个本地文件路径。
开始监听(Listen)来自客户端的连接请求。
接受(Accept)一个客户端连接请求,返回一个新的文件套接字用于与该客户端通信。
使用返回的文件套接字进行数据的发送和接收。
服务端代码:
python
import socket
import os
# 创建一个文件套接字
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# 绑定到一个本地文件路径
server_socket.bind('/tmp/mysocket')
# 开始监听连接请求
server_socket.listen(1)
print('Server started, waiting for connections...')
# 接受一个客户端连接请求
client_socket, addr = server_socket.accept()
print('Client connected:', addr)
# 接收数据
data = client_socket.recv(1024)
print('Received data:', data.decode())
# 发送数据
client_socket.send('Hello from server!'.encode())
# 关闭连接
client_socket.close()
server_socket.close()
客户端:
创建一个文件套接字对象。
连接到(Connect)服务端指定的文件路径。
使用文件套接字进行数据的发送和接收。
下面是一个简单的Python示例,演示了如何使用文件套接字实现本地进程通信:
客户端代码:
python
import socket
# 创建一个文件套接字
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# 连接到服务端指定的文件路径
client_socket.connect('/tmp/mysocket')
# 发送数据
client_socket.send('Hello from client!'.encode())
# 接收数据
data = client_socket.recv(1024)
print('Received data:', data.decode())
# 关闭连接
client_socket.close()
有关基础函数说明
(1)int socket(int domain, int type, int protocol);
参数说明:
domain(地址族/协议域):
AF_INET:IPv4协议。这是最常用的地址族,用于创建基于IPv4地址的套接字。
AF_INET6:IPv6协议。用于创建基于IPv6地址的套接字。
AF_UNIX 或 AF_LOCAL:Unix域套接字。用于同一台机器上的两个进程之间的通信。
其他地址族,如AF_PACKET、AF_BLUETOOTH等,用于特定类型的通信。
type(套接字类型):
SOCK_STREAM:提供面向连接的、可靠的字节流服务。这通常用于TCP协议。
SOCK_DGRAM:提供无连接的、尽最大努力的服务。数据以独立的数据包发送,不保证按顺序到达或不被重复。这通常用于UDP协议。
SOCK_RAW:允许直接访问底层协议,通常用于特殊应用,如网络诊断和协议开发。
SOCK_SEQPACKET:提供面向连接的、可靠的、基于记录的服务。与SOCK_STREAM类似,但保留了消息的边界。
其他套接字类型,如SOCK_RDM、SOCK_PACKET等,用于特定场景。
protocol(协议):
通常设置为0,让系统选择默认协议。对于AF_INET和SOCK_STREAM,默认协议通常是TCP。对于AF_INET和SOCK_DGRAM,默认协议通常是UDP。
如果指定非0值,它应该是一个有效的协议常量,如IPPROTO_TCP或IPPROTO_UDP。
返回值:
如果成功,socket函数返回一个非负整数,表示新创建的套接字的文件描述符。
如果失败,返回-1,并设置全局变量errno以指示错误原因。
(2)int bind(int socket, const struct sockaddr *address, socklen_t address_len);
参数说明:
socket:这是由socket函数返回的套接字文件描述符。
address:这是一个指向sockaddr结构体的指针,该结构体包含了要绑定的网络地址和端口信息。根据套接字类型(IPv4、IPv6等)和地址族(AF_INET、AF_INET6等),sockaddr结构体可以是sockaddr_in、sockaddr_in6或其他类型。
address_len:这是address参数指向的sockaddr结构体的大小,通常以字节为单位。对于IPv4地址,这个值通常是sizeof(struct sockaddr_in);对于IPv6地址,则是sizeof(struct sockaddr_in6)。
bind函数的工作流程:
检查参数:函数首先检查提供的套接字描述符是否有效,以及address指针和address_len参数是否合理。
分配地址:操作系统根据提供的地址信息(IP地址和端口号)在内部数据结构中为套接字分配相应的资源。对于IPv4地址,这通常涉及到将套接字与一个特定的IP地址和端口号组合;对于IPv6地址,过程类似但结构可能更复杂。
设置套接字状态:一旦地址分配成功,套接字的内部状态会更新为已绑定状态,意味着它现在与指定的网络地址和端口号相关联。
返回结果:如果bind函数成功执行,它将返回0;如果发生错误,它将返回-1,并设置全局变量errno以指示错误类型。
(3)套接字协议簇(Address Family)是套接字编程中用于区分不同网络通信协议的一个概念。其中,AF_UNIX和AF_INET是两种常见的协议簇,分别用于不同的通信场景。
AF_UNIX(或AF_LOCAL)
AF_UNIX(也被称为AF_LOCAL)是用于Unix/Linux系统上的进程间通信(IPC)的协议簇。它允许在同一台机器上的不同进程之间进行通信,而不需要通过网络协议栈。
特点:
本地通信:AF_UNIX套接字仅在同一台机器上的进程之间通信。
速度快:由于不需要经过网络协议栈,AF_UNIX套接字的通信速度通常比基于网络的套接字(如AF_INET)快得多。
文件系统路径:AF_UNIX套接字通常与一个文件系统路径相关联,例如/tmp/socket_file。客户端和服务器都通过这个路径来访问套接字。
安全性:由于AF_UNIX套接字仅在同一台机器上通信,因此相对于网络套接字,它们通常更安全。
AF_INET
AF_INET是用于IPv4网络通信的协议簇。它允许在不同机器上的进程之间进行通信,通过IP地址和端口号来标识目标进程。
特点:
网络通信:AF_INET套接字用于跨网络的进程间通信。
IP地址和端口:AF_INET套接字使用IP地址和端口号来标识网络中的进程。
TCP/UDP:AF_INET可以与TCP或UDP协议结合使用,提供面向连接或无连接的通信服务。
广泛应用:AF_INET是最常用的套接字协议簇之一,因为它支持跨网络的通信,并且IPv4地址在全球范围内是唯一的。
总结
AF_UNIX适用于同一台机器上的进程间通信,速度快且安全。
AF_INET适用于跨网络的进程间通信,使用IP地址和端口号进行标识。
在选择使用哪种协议簇时,应该根据应用的需求和场景来做出决策。如果需要在同一台机器上的不同进程之间进行通信,AF_UNIX是一个很好的选择。如果需要跨网络通信,则应该使用AF_INET。
(4)int listen(int socket, int backlog);
参数说明:
socket:这是由socket函数返回的套接字文件描述符,代表要监听的套接字。
backlog:这个参数指定了操作系统可以挂起(排队)的最大未完成连接数。换句话说,它定义了系统等待客户端完成三次握手(TCP连接建立过程)的最大连接请求数量。如果有一个新的连接请求到来,而当前队列已满,那么客户端可能会收到一个错误,表明服务器太忙或无法处理新的连接。
listen函数的工作流程:
检查参数:函数首先检查提供的套接字描述符是否有效,以及backlog参数是否合理。
设置套接字为监听状态:如果参数有效,操作系统会将套接字的状态设置为监听状态,这意味着套接字现在可以接受连接请求。
分配资源:为了支持多个并发的连接请求,操作系统会分配必要的资源,如连接队列等。
返回结果:如果listen函数成功执行,它将返回0;如果发生错误,它将返回-1,并设置全局变量errno以指示错误类型。
(5)int accept(int socket, struct sockaddr *address, socklen_t *address_len);
参数说明:
socket:这是服务器端的套接字描述符,通常是之前通过socket函数创建并通过bind和listen函数设置好的。
address:这是一个指向sockaddr结构体的指针,用于存储客户端的地址信息。这个参数通常不是NULL,因为服务器通常想知道是哪个客户端连接过来的。当accept函数成功返回后,这个结构体将被填充客户端的IP地址和端口号。
address_len:这是一个指向socklen_t类型变量的指针,用于传入address结构体的大小,并在accept函数返回时更新为实际填充的大小。这个参数通常是&address_len,其中address_len是一个socklen_t类型的变量,初始化为sizeof(struct sockaddr_in)或sizeof(struct sockaddr_in6),取决于你使用的是IPv4还是IPv6。
返回值:
如果accept函数成功,它将返回一个新的套接字描述符,这个描述符代表与客户端的连接。这个新描述符可以像常规的套接字描述符一样使用,比如用于读取和写入数据。
如果出现错误,accept将返回-1,并设置全局变量errno以指示错误类型。
(6)int connect(int socket, const struct sockaddr *address, socklen_t address_len);
参数说明:
socket:这是由socket函数返回的套接字文件描述符,代表要连接的客户端套接字。
address:这是一个指向sockaddr结构体的指针,该结构体包含了服务器的地址信息。根据使用的协议族(如IPv4或IPv6),你可能需要使用sockaddr_in(IPv4)或sockaddr_in6(IPv6)等结构体。
address_len:这是address结构体的大小,通常以字节为单位。对于sockaddr_in结构体,这个值通常是sizeof(struct sockaddr_in)。
connect函数的工作流程:
检查参数:函数首先检查提供的套接字描述符是否有效,以及地址结构体和地址长度是否合法。
建立连接:如果参数有效,connect函数会尝试与服务器建立连接。这通常涉及三次握手过程(对于TCP连接)或发送数据报(对于UDP连接,尽管UDP是无连接的)。
等待连接:对于TCP连接,connect函数会阻塞调用线程,直到连接建立成功或发生错误。对于UDP套接字,connect函数将设置套接字的默认目标地址,但不会阻塞,因为UDP是无连接的。
返回结果:如果连接建立成功,connect函数返回0。如果连接失败,函数返回-1,并设置全局变量errno以指示错误类型。
(7)int close(int socket);
参数说明:
socket:这是要关闭的套接字描述符,它是由 socket 函数创建并返回的一个整数。
close 函数的工作流程:
检查参数:首先,close 函数会检查提供的套接字描述符是否有效。如果描述符无效(例如,它是一个负值或者未被 socket 函数使用过),则函数可能会失败。
关闭套接字:如果套接字描述符有效,close 函数会关闭该套接字。这意味着任何与该套接字相关联的网络连接都会被断开,并且任何挂起的读/写操作都会收到一个错误。
释放资源:操作系统会释放与该套接字关联的所有资源,如文件描述符、内存缓冲区等。
返回结果:如果 close 函数成功执行,它将返回 0;如果发生错误,它将返回 -1,并设置全局变量 errno 以指示错误类型。