补充知识:字符串的join和split
一、TCP
每打开(创建)一个Socket就表示“打开了一个网络链接”,而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型即可。
Python的接口是对Unix系统调用与Socket函数的库接口函数的直译,非常易于上手,例如:函数socket()返回一个 socket object 套接字对象,通过作为方法来调用这个对象就可以使用各式各样的系统套接字调用函数。
如:
# 导入socket库:
import socket
# 创建一个socket object:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接,客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号:
s.connect(('www.sina.com.cn', 80)) #注意参数是一个tuple,包含地址和端口号。80端口是Web服务的标准端口
注:
1. 端口号小于1024的是Internet标准服务的端口,端口号大于1024的,可以任意使用;
2. socket.SOCK_STREAM是可靠的TCP连接,而socket.SOCK_DGRAM则指定了这个Socket的类型是UDP。
例1:
完整的请求新浪首页的(客户端)程序如下:
# -*- coding:utf-8 -*-
# 导入socket库:
import socket
# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('www.sina.com.cn', 80))
# 发送数据:
s.send('GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
#必须发送符合HTTP标准的文本格式
# 接收数据:
buffer = []
while True:
# 每次最多接收1k字节:
d = s.recv(1024)
#如果d非空,就加入buffer,否则退出不再接收
if d:
buffer.append(d)
else:
break
data = ''.join(buffer) #相当于把buffer字符串化
#注意,join函数仅针对字符串有效(字符串形式的list也可以)
# 关闭连接:
s.close()
#把接受到的数据分割1次(其实,不分割也没有问题)
#
#不分割
#header, html = data.split('\r\n\r\n', 1)
#print header
#with open('sinax.html', 'wb') as f:
# f.write(buffer)
header, html = data.split('\r\n\r\n', 1)
print header
# 把接收的数据写入文件:
with open('sina.html', 'wb') as f:
f.write(html)
得到sina.html文件,打开即是新浪首页。
注:'\r'是回车,'\n'是换行,前者使光标到行首,后者使光标下移一格,通常敲一个回车键,即是回车,又是换行(\r\n)。
Unix中每行结尾只有“<换行>”,即“\n”;Windows中每行结尾是“<换行><回车>”,即“\n\r”;Mac中每行结尾是“<回车>”
那么连续两个‘\r\n\r\n’,很显然是表示非空行之后的空行!
例2:
注意,服务器刚开始会创建一个socket,后来每接受一个客户端请求时又会创建一个socket(这个socket是因为 s.accept() 而产生),这两个socket不是一回事。此外,服务器为响应客户端而创建的socket和客户端的socket也不是同一个。
虽然,服务器和客户端分别建立的套接字对象不是同一个,
但是服务器会根据客户端的服务器地址、服务器端口、客户端地址、客户端端口来唯一确定一个Socket,并始终用这个socket和客户端进行通信。
服务器端代码:
# -*- coding:utf-8 -*-
# Echo server program
import socket,time,threading
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#监听指定端口
s.bind(('127.0.0.1',9999))
print 'I am binding...'
s.listen(5)
print 'I am listening...'
def func(sock,addr):
print 'accept a new connection from %s:%s' %addr
print 'the port is %s' %sock
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 is over!'
while True: #服务器端始终在监听,死循环。一旦监听到有客户端请求连接,立即accept,并创建一个线程来和这个客户端进行通信
sock,addr=s.accept() #s.accept()返回请求连接的客户端和IP地址,并创建一个socket与客户端进行通信,之后的操作都是根据这个socket来进行
thread=threading.Thread(target=func,args=(sock,addr))
thread.start()
# -*- coding:utf-8 -*-
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#print s
#连接指定服务器端口(和服务器地址)
s.connect(('127.0.0.1',9999))
print s.recv(1024)
s.send('zhuma')
print s.recv(1024)
s.send('exit')
s.close()
二、UDP
TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。
使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。
服务器端:
# -*- coding:utf-8 -*-
# Echo server program
import socket
s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#绑定端口
s.bind(('127.0.0.1',9999))
print 'bind udp on 9999...'
while True:
data,addr=s.recvfrom(1024)
print 'receive from %s:%s' %addr
s.sendto('hello,%s' %data,addr)
相比于TCP,UDP所用的接收数据方法变为s.recvfrom(1024),直接返回客户端IP以及接收到数据;发送数据方法变为s.sendto(string,address),因为是无连接通信,所以必须有发送地址(TCP要s.connect()时指定了通信地址,所以后来send()不需要地址)。
客户端:
# -*- coding:utf-8 -*-
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发送数据,省略了connnect,但sendto要带地址:
s.sendto('zhuma', ('127.0.0.1', 9999))
# 接收数据:
print s.recv(1024)
s.close()