thanks to http://www.liaoxuefeng.com
1. IP协议:IP协议是重要的网络协议,对应了每台计算机的唯一标识:IP地址,实际上就是每台计算器链接网络的接口,通常是网卡。IP协议负责把数据从一台计算机通过网络传到另外一台计算机,数据被分割成小块,然后通过IP包(IP包包含数据、源IP地址和目标IP地址、源端口和目标端口),通过两台计算机之间连接的线路发送出去,但是IP协议并不保证数据准确到达,同时不保证顺序到达。
2. TCP协议:TCP协议建立在IP协议之上,将两台计算机通过握手,建立可靠的连接,并且对每个IP包编号,确保数据顺序可靠到达,如果发生丢包,则自动重发。在TCP协议的基础上,有更多高级的协议:HTTP、SMTP等常见协议。
3. UDP协议:UDP协议不需要建立可靠连接,直接发送包,但是不保证能够准确到达。UDP协议的优点就是速度快,在某一些不要求可靠达到的数据,可以使用UDP协议。
建立网络连接的基础是创建一个socket,socket指定了目标计算机的IP地址和端口号,以及协议类型。
在客户端发起TCP连接,需要指定socket的协议为TCP/IP协议:
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
socket类在实例化时,接受4个参数:family=AF_INET,type=SOCK_STREAM,proto=0,_sock=None
第四个参数_sock是一个_realsocket 对象,如果指定了该对象,则会从该对象中继承family、type、photo属性,并且继承delegate方法:("recv", "recvfrom", "recv_into", "recvfrom_into","send", "sendto”)。 _realsocket类是c实现的,所以只能看到头文件_realsocket.py。
前三个参数依次为:地址协议、流协议、接口协议(?)
创建了socket对象之后,通过connet方法建立连接:
s.connet((‘www.baidu.com’),80)
connet方法要求传入一个地址,如果是IP类的socket,则需要传入一个元组,包含地址(会被dns解析)和端口
建立连接之后,就可以发送请求。TCP协议是双向连接的,双方都可以发送数据,但是具体发送的规则有更加高层的协议,如http协议决定(http协议规则:必须有客户端发送请求给服务器,服务器收到后才发数据给客户端):
s.send('GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
如上发送一条HTTP 1.1协议请求,获取百度首页的内容,请求发送后,百度服务器就会响应请求,并且向我们发送首页的内容,通过recv方法接收服务器发来的信息,并且指定单次接收的最大字节数:
buffer = []
while True:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = ''.join(buffer)
如上data中包含了从服务器中接收的全部数据,包括一个http头,和真正需要接收的网页本身,再发送请求时,我们以两个回车换行来分割http头和接收到的网页,所以将其从新分离即可得到百度首页的内容:
header, html = data.split('\r\n\r\n', 1)
将其写到文件中:
with open('baidu.html', 'wb') as f:
f.write(html)
用浏览器打开baidu.html,即可看到百度首页的内容了
总脚本如下:
__author__ = 'liangzb'
import socket
s = socket.socket(family=2,type=socket.SOCK_STREAM)
print type(s.family)
s2 = socket.socket(_sock=s)
print s2.family
s.connect(('www.baidu.com',80))
s.send('GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n')
buffer = []
while True:
d = s.recv(1024)
if d:
buffer.append(d)
else:
breakdata = ''.join(buffer)
s.close()
# print data
header, html = data.split('\r\n\r\n', 1)
# print header
with open('baidu.html', 'w') as f:
f.write(html)
服务器和客户端的区别就是服务器需要接受来自不同客户端的请求,并且分别作出响应,为了实现该过程,需要先建立一个绑定,绑定服务器的ip地址和端口:
s.bind(('127.0.0.1', 9999))
之后,调用listen方法监听来自该端口消息,传入参数指定最大等待连接的数量:
listen(max_connection)
接下来,服务器应该保持运行状态,启用一个死循环来接收来自客户端的连接,accept方法可以接收来自客户端的连接,并且返回客户端的socket对象和地址。需要注意的是,对于每一个请求,都应该新开一条线程或者进程(win下只能开线程)来处理:
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
编写处理函数来响应请求:
def tcplink(sock, addr):
print 'Accept new connection from %s:%s...' % addr
sock.send('Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if data == 'exit' or not data:
break
sock.send('Hello, %s!' % data)
sock.close()
print 'Connection from %s:%s closed.' % addr
服务端总脚本如下:
__author__ = 'liangzb'
import threading
import socket
import time
def tcplink(sock, addr):
print 'Accept new connection from %s:%s...' % addr
sock.send('Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if data == 'exit' or not data:
break
sock.send('Hello, %s!' % data)
sock.close()
print 'Connection from %s:%s closed.' % addr
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 9999))
s.listen(5)
print 'Waiting for connection...'
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
可以通过另外一个客户端给该服务端发送消息来测试,注意,这里绑定的ip地址是127.0.0.1,是本地地址,只能是本机访问
客户端程序参考:
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 9999))
print s.recv(1024)
for data in ['Michael', 'Tracy', 'Sarah']:
s.send(data)
print s.recv(1024)
s.send('exit')
s.close()
源代码来自:http://www.liaoxuefeng.com/ ,thanks to liaoxuefeng for share
运行效果:
协议是不确定连接可靠性的,socket流协议应该设置为:SOCK_DGRAM:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
由于UDP协议的特征,UDP的服务端在建立连接时,不需要调用listen方法,而是直接对接收到的请求作出回应,而客户端的信息,则又recvfrom方法返回的元组得到。在发送消息时,也只需要通过sendto方法,将客户端的地址作为第二个参数传入。
而在UDP协议的客户端,同样不需要先经过连接,而是直接将准备好的请求发送到某一个地址,利用sendto方法,将服务端的地址作为第二个参数传入,其他接收方式则和TCP相同。