如果你学过编程语言,一定接触过socket套接字,即使你不懂tcp/ip协议,你也可以用socket通过网络发送和接收数据
Socket
socket
套接字,对网络中不同主机上的应用进程之间进行双方通信的端点的抽象,提供了应用层进程利用网络协议交换数据的机制
套接字向上连接应用程序,向下连接网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议进行交互的接口
为什么要有socket?
如果没有socket,应用程序的程序员要精通底层硬件操作和网络通信底层的协议,应用程序需要和网络协议、硬件驱动直接打交道,要在程序里实现非常多的代码来驱动协议和硬件,这不是一个科学的流程
操作系统把协议和这些底层的东西打包起来,形成一个方便的接口,你只需要调用这个接口,就能实现协议栈以下的功能,这就是socket的好处
TcpSocket流程
-
Server端应用程序创建socket,调用Bind(ip, port)函数绑定自己的地址和端口号,随后调用Listen()函数监听在(ip, port)上,等待Client连接请求
-
Client端应用程序创建socket,调用Bind(ip, port)函数绑定自己的地址和端口号,然后调用Connect(dstIP, dstPort)驱动协议栈进行tcp的连接请求
-
Server端调用Accept()函数驱动协议栈对tcp连接响应,实现三次握手
-
Client和Server在连接成功后,通过Send(Data)和Recv()函数实现数据的发送和接收
-
数据传输完后,Client调用Close()函数关闭连接,释放端口号等资源
Listen()函数是一直在监听,Accept()如果没有连接请求,会一直阻塞在这里,Connect()和Accept()是tcp连接建立后,才算是执行完毕
UdpSocket的流程没有握手连接,也就没有Connect()、Listen()和Accept()
socket选项
套接字是应用层用来驱动协议栈和驱动层工作的接口,有时可能需要对下层协议头部中的字段进行赋值或者获取,比如说IP层的TTL值,或者TCP层的MSS值,或者快速重用端口,这些都是通过对套接字对象进行选项设置或者获取来完成
socket对象如果想设置option,首先需要调用正确的方法,capl中有SetSocketOption(),python中有setsockopt(),传入的参数有三个level、option、value,level是定义的协议层或socket应用本身,option是定义的协议层的某个字段或某个功能,value是选项的值
socket对象也可以获取option的值,也需要调用正确的方法,capl中有GetSocketOption(), python中有getsockopt()
网络层
以capl为例,网络层可设置的选项有哪些
IPv4
这里面最常用的应该是IP_TTL,设置或者获取网络层IPv4协议头部中的TTL的值
IPv6
传输层
TCP
这里面最常用的应该是TCP_MAXSEG,设置或者获取TCP协议的MSS的值
UDP
可以把UDP checksum设置为0
socket
socket自己也可以设置选项
SO_REUSEADDR
一个(ip, port)只允许一个socket绑定并使用的,如果别的socket想使用,原先的socket必须调用close()函数断开连接并释放端口资源。对于tcp来说,断开连接的最后一步发ACK后需要等待2MSL = 2TTL,可能这个时间还没到,脚本里就已经有socket重用这个端口了,对socket设置端口重用选项,就可以避免端口还处于上一个连接的2MSL阶段而无法使用的问题
SO_KEEPALIVE
实现在服务器侧,缺省超时时间120分钟,SO_KEEPALIVE实现在TCP协议栈,只要TCP连接没有任何数据、控制字传输,定时器就开始启动,超时后服务器会给客户端发SO_KEEPALIVE,来检测连接是否存活,如果N次重传keepalive,客户端没有响应,则服务器删除连接,释放资源。如果有数据传输,会不停刷新定时器,重新走表
这个选项是为了防止客户端没有发FIN主动断开而出现的客户端关机等,造成服务器会有很多不可用的TCP连接,从而占用资源
SO_BROADCAST
通过在socket层设置广播选项发送广播报文,只能用在UDP
SO_SNDBUF
设置发送缓存区大小
SO_RCVBUF
设置接收缓存区大小
附上python中要如何使用的代码
import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #ipv4, tcp
sock.bind(('127.0.0.1', 5001))
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #端口重用
python如何新建socket
客户端
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 3333))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
服务器
import socket
import time
import threading
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 3333))
s.listen(6)
print('Waiting for connection...')
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' % addr)
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
以上代码来自廖雪峰官网