套接字
套接字是计算机网络数据结构,网络应用程序必须创建套接字,没有套接字就无法进行网络通信。套接字最初出现在伯克利版本unix(BSD unix)中,是为了实现进程间的通讯。有两种类型的套接字,基于文件的和基于网络的,因为两个进程是在同一个计算机上的,所以这些套接字是基于文件的AF_LOCAL(AF_UNIX是AF_LOCAL的前身,python中任然在使用AF_UNIX)。不同主机间的通讯是基于网络的套接字AF_INET,(AF_INET6是基于IPv6寻址的版本)。python等大多数受欢迎的平台上都使用术语地址家族AF,但一些比较旧的系统可能会用协议家族PF。套接字地址:主机-端口对
一个网络地址由主机地址和端口号组成,有效的端口号地址是0-65535(0-1024端口号预留给系统),在POSIX兼容系统中,可以在/etc/services中找到预留的端口号列表(服务器/协议和套接字类型)。
如下图:21号端口是tp服务,22号端口是ssh服务,23号端口是telnet服务
也可以在此网站中查看众所周知的服务端口号列表:http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
面向连接的套接字和无连接的套接字
不管采用哪种地址家族,都会有两种不同的套接字连接,面向连接的套接字(虚拟电路或流套接字)和无连接的套接字。面向连接的通信提供序列化的,可靠的和不重复的数据交付,但是没有边界记录,这种方式将数据差分成多个片段,确保每个片段都能够到达目的地,然后按顺序拼在一起,传递给应用程序。它是基于传输控制协议(TCP)实现的,为了创建TCP 套接字,必须使用SOCK_STREAM作为套接字类型。无连接的套接字(数据报类型套接字)它在通信开始之前并不需要建立连接,它无法保证顺序性,可靠性和重复性,但是它会记录边界信息,也就是信息是以整体的形式发送的而不是切分成片段。它不能保证数据能够成功到达,而且为了将其添加到并发通信中(同时和多个对象通信),在网络中也可能存在重复消息。UDP套接字的套接字类型是SOCK_DGRAM(datagram数据报)。
python中的网络编程
套接字模块名为socket,所以在python网络编程中要先导入socket模块。
创建套接字:
socke(socket_family,socket_type,protocol)
其中socket_family是SOCKET_UNIX或SOCKET_IENT,socket_type是SOCKET_SRTEAM或SOCKET_DGRAM,protocol表示套接字协议,通常省略,默认为0。
常见的套接字内置方法和属性
服务器端:
s.bind(addr) //将主机名和端口号绑定到套接字上
s.listen(int) //设置并启动tcp监听器,参数为在连接被拒绝或转接之前,传入连接请求的最大数
s.accept() //被动接受tcp客户端连接,一直等待直到连接到达(阻塞)
客户端:
s.connect(addr) //连接到服务器
s.connect_ex() //connect 的扩展版本,此时会以错误码的形式返回问题,而不是抛出一个异常
普通的套接字方法:
s.recv() //接收tcp消息
s.recv_into() //接收tcp消息到指定的缓冲区
s.send() //发送tcp消息
s.sendall() //完整地发送tcp消息
s.recvfrom() //接收udp消息
s.recvfrom_into() //接收udp消息到指定缓冲区
s.sendto() //发送udp消息
s.getpeername() //连接到套接字(tcp)的远程地址
s.getsockname() //当前套接字的地址
s.setsockopt() //返回给定套接字选项的值
s.setsockopt() //设置套接字选项的值
s.shotdown() //关闭连接
s.close() //关闭套接字
s.setblocking() //设置套接字的阻塞和非阻塞模式
s.makefile() //创建与套接字关联的文件对象
server.py
__author__ = 'wenhai.dai'
from socket import *
import time
import threading
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)
tcpserv = socket(AF_INET, SOCK_STREAM)
tcpserv.bind(('', 9999))
tcpserv.listen(5)
while True:
sock, addr = tcpserv.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
Accept new connection from 127.0.0.1:65211...
Connection from 127.0.0.1:65211 closed.
client.py
__author__ = 'wenhai.dai'
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 9999))
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()
Welcome!
Hello, Michael!
Hello, Tracy!
Hello, Sarah!
udp套接字通信示例
server.py
__author__ = 'wenhai.dai'
from socket import *
from time import ctime
s = socket(AF_INET, SOCK_DGRAM)
s.bind(('127.0.0.1', 12345))
while True:
print('waiting for message...')
data, addr = s.recvfrom(1024)
s.sendto(ctime().encode('utf-8'), addr)
if data.decode('utf-8') == 'exit':
break
print('receive from and return to :', addr)
s.close()
waiting for message...
receive from and return to : ('127.0.0.1', 63861)
waiting for message...
receive from and return to : ('127.0.0.1', 63861)
waiting for message...
receive from and return to : ('127.0.0.1', 63861)
waiting for message...
Process finished with exit code 0
client.py
__author__ = 'wenhai.dai'
from socket import *
c = socket(AF_INET, SOCK_DGRAM)
ADDR = ('127.0.0.1', 12345)
while True:
data = input('>>')
c.sendto(data.encode('utf-8'), ADDR)
if data == 'exit':
break
data, addr = c.recvfrom(1024)
if not data:
pass
else:
print(data.decode('utf-8'), data)
c.close()
>>dai
Sat Oct 22 20:50:21 2016 b'Sat Oct 22 20:50:21 2016'
>>wen
Sat Oct 22 20:50:27 2016 b'Sat Oct 22 20:50:27 2016'
>>hai
Sat Oct 22 20:50:29 2016 b'Sat Oct 22 20:50:29 2016'
>>exit
Process finished with exit code 0
UDP通信和TCP通信的主要实现上的区别是UDP服务端不需要调用listen()函数来进行端口监听,服务端向客户端发送信息时调用服务端套接字对象的sendto()函数,把目标地址写在函数的参数中,而TCP实现是服务器端套接字对象的accept()函数返回客户端的套接字对象和地址,向客户端发送信息是调用客户端套接字对象的send()函数。在客户端的区别是UDP方式客户端套接字对象不需要调用connect()函数,地址都是在数据的发送和接受时作为参数来传递的。