Socket在英文中的意思是“凹槽(连接两个物品)”,和eyesocket一样,是“眼窝”的意思,也有“插座”的意思。在计算机科学中,套接字通常指连接的两个端点。这里的连接可以在同一台机器上,如unix域套接字,也可以在不同的机器上,如网络套接字。本文重点介绍最常用的网络套接字,包括其在网络模型中的地位、API编程范式、常见错误等,最后给出几个使用Python语言的套接字API实现的实际例子。Socket中文一般译为“套接字”。不得不说这是一个令人费解的翻译。没想到还有“xindaya”的翻译,所以这篇文章直接用英文表达。本文中的所有代码都可以在socket.py存储库中找到。概述Socket作为一个通用的技术规范,最初是伯克利大学于1983年为4.2BSDUnix提供的,后来逐渐演变为POSIX标准。SocketAPI是操作系统提供的编程接口,允许应用程序控制套接字技术的使用。在Unix哲学中,万物皆文件,所以socket和file的API用法非常相似:可以进行read、write、open、close等操作。目前的网络体系是分层的。理论上有OSI模型,工业上有TCP/IP协议簇。对比如下:每一层都有对应的协议,socketAPI不属于TCP/IP协议簇,而是操作系统提供的网络编程接口,工作在应用层和传输层之间:我们通常browse网站使用的http协议,用于收发邮件的smtp和imap都是基于socketAPI构建的。一个套接字包含两个必要的组成部分:地址,由ip和端口组成,如192.168.0.1:80。Protocol,socket使用的传输协议,目前有三种:TCP、UDP、rawIP。地址和协议可以确定一个套接字;一台机器上只允许存在一个相同的套接字。TCP53端口的套接字和UDP53端口的套接字是两个不同的套接字。根据socket传输数据的方式不同(使用不同的协议),可分为以下三种:Streamsockets,也称为“面向连接”的sockets,使用TCP协议。实际通信前需要连接,传输的数据没有特定的结构,所以高层协议需要自己定义数据分隔符,但它的优点是数据可靠。数据报套接字,也称为“无连接”套接字,使用UDP协议。在实际通信之前不需要连接。一个优点是UDP数据包本身是自定界的,也就是说每个数据包都标记了数据的开始和结束。缺点是数据不可靠。原始套接字通常用于路由器或其他网络设备。这种socket不经过TCP/IP协议簇中的传输层(transportlayer),直接从网际层(Internetlayer)通向应用层(Applicationlayer),所以此时,数据数据包将不包含tcp或udp标头信息。在PythonsocketAPIPython中,使用(ip,port)的元组表示socket的地址属性,使用AF_*表示协议类型。数据通信有两组动词可供选择:send/recv或read/write。read/write方式也被Java采用。此处对该方法不做过多解释,但需要注意的是:读写操作是一个带有缓冲区的“文件”,因此读写后需要调用flush。方法实际发送或读取数据,否则数据将保留在缓冲区中。TCPsocketTCPsocket比UDPsocket负责,因为它需要在连接前建立连接。具体如下:各个API的具体含义这里不再赘述,可以查看手册,这里是用Python语言实现的echoserver。#echo_server.py#coding=utf8importsocketsock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#设置SO_REUSEADDR后,socketsock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)sock.bind(('',5500))sock.listen(5)defhandler(client_sock,addr):print('newclientfrom%s:%s'%addr)msg=client_sock.recv(1024)client_sock.send(msg)client_sock.close()print('客户端[%s:%s]socketclosed'%addr)if__name__=='__main__':while1:client_sock,addr=sock.accept()handler(client_sock,addr)#echo_client.py#coding=utf8importsocketsock=socket.socket(socket.AF_INET,socket.SOCK_STREAM)sock.connect(('',5500))sock.send('hellosocketworld')printsock.recv(1024)上面简单的echoserver代码有一点需要注意:SO_REUSEADDR是为服务器端的套接字设置为1。目的是立即使用处于TIME_WAIT状态的socket,那么TIME_WAIT是什么意思呢?后面在讲解tcp状态变化图的时候会详细介绍。UDPsocketUDPsocket服务端代码绑定后不需要调用listen方法。#udp_echo_server.py#coding=utf8importsocketsock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)#设置SO_REUSEADDR后,socketsock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)sock.bind(('',5500))#Nocalllistenif__name__=='__main__':while1:data,addr=sock.recvfrom(1024)print('newclientfrom%s:%s'%addr)sock.sendto(data,addr)#udp_echo_client.py#coding=utf8importsocketudp_server_addr=('',5500)if__name__=='__main__':sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)data_to_sent='helloudpsocket'try:sent=sock.sendto(data_to_sent,udp_server_addr)data,server=sock.recvfrom(1024)print('receivedata:[%s]from%s:%s'%((data,)+server))finally:sock.close()Commontrapignorereturn这个echoserver的例子由于篇幅限制,文章也忽略了返回值。网络通信是一个非常复杂的问题,通常无法保证通信双方的网络状态,发送/接收数据时极有可能失败或部分失败。所以有必要检查发送/接收函数的返回值。本文tcpechoclient发送数据时,正确的写法应该是:total_send=0content_length=len(data_to_sent)whiletotal_send
