一. 网络通信
1. 网络---UDP
网络通信
使用网络能够把多方链接在一起,然后可以进行数据通信。
所谓的网络编程就是,让在不同的电脑上的软件能够进行数据传输,即进程之间的通信。
ip地址
地址就是标记地点的。网络中ip地址来标记,用来标记网络上的一台电脑。
socket简介(套接字)
TCP/IP协议簇的网络层的IP地址可以唯一标识网络中的主机,传输层的协议+端口可以唯一标识主机中的应用进程。
socket是进程通信的一种方式,他与其他进程通信的主要不同是:他能实现不同主机间的进程通信,我们网络中各种各样的服务大多数都是属于socket来完成通信的。如浏览网页,QQ 微信等。
创建socket
python中使用socket模块的函数socket就可以完成。
函数socket.socket创建一个socket,该函数有两个参数:socket.socket(AddressFamily,Type)
- AddressFamily:可以选择AF_INET(用于Internet进程间通信)或者AF_UNIX(用于同一台及其进程间通信),实际工作中常用AF_INET。
- Type:套接字类型,可以是SOCK_STREAM(流式套接字,主要用于TCP协议)或者SOCK_DGRAM(数据报套接字,主要用于UDP协议)。
创建TCP套接字:
import socket
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#这里是使用套接字的功能
s.close()
#不用的时候 关闭套接字
b前缀表示:后面字符串是bytes类型
如果一台计算机上运行的虚拟机和我们的主机IP不同,改成桥接方式。ping 一个IP地址 确定能否和这个IP地址通信
UDP套接字发数据:
如果发送数据之前没有绑定端口,操作系统会自己给你分配一个
import socket
def demo():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 这里是使用套接字收发数据
while True:
send_data = input('请输入发送的数据: ')
if send_data=='exit':#循环退出
break
#udp_socket.sendto(发送的内容,对方IP以及port)
#发送的数据用utf-8编码
s.sendto(send_data.encode('utf-8'),('192.168.33.53',8080))
s.close() # 不用的时候 关闭套接字
if __name__=='__main__':
demo()
接收数据:
bind绑定端口
import socket
def demo():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#绑定本地信息
localaddr = ('',7878)#''绑定本机的一个ip和port
s.bind(localaddr)
#接收对方的数据
recv_data = s.recvfrom(1024)#1024表示本次接收的最大字节数
#recv_data变量中存储的是一个元组
recv_mes = recv_data[0]
id = recv_data[1]
print('%s %s' % (recv_mes.decode('gbk'),id))#window默认编码是gbk
s.close() #不用的时候 关闭套接字
if __name__=='__main__':
demo()
同一个电脑上,同一个ip不同程序也可以发送信息传输数据。
lo 本地环回127.0.0.1 可以完成自己电脑上的数据共享。
套接字可以收数据也可以发数据,不需要创建两个。
import socket
def demo():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#绑定端口 用于接收数据
s.bind('',7878)
# 获取对方ip和port
dest_ip = input("请输入对方ip: ")
dest_port = int(input("输入对方port: "))
send_data = input('请输入发送的数据: ')
#udp_socket.sendto(发送的内容,对方IP以及port)
#发送的数据用utf-8编码
s.sendto(send_data.encode('utf-8'),(dest_ip,dest_port))
#接收对方回复的数据
recv_data = s.recvfrom(1024)
print("%s %s" % (str(recv_data[1]),recv_data[0].decode('gbk')))
s.close() # 不用的时候 关闭套接字
if __name__=='__main__':
demo()
udp聊天室
import socket
def send_mes(s):
# 获取对方ip和port
dest_ip = input("请输入对方ip: ")
dest_port = int(input("输入对方port: "))
send_data = input('请输入发送的数据: ')
#udp_socket.sendto(发送的内容,对方IP以及port)
#发送的数据用utf-8编码
s.sendto(send_data.encode('utf-8'),(dest_ip,dest_port))
def recv_mes(s):
#接收对方回复的数据
recv_data = s.recvfrom(1024)
print("%s %s" % (str(recv_data[1]),recv_data[0].decode('gbk')))
def demo():
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
#绑定端口 用于接收数据
s.bind('',7878)
while True:
print("1 发送数据")
print("2 接收数据")
print("0 退出")
id = input("请输入功能: ")
if id == "1":
#发送
send_mes(s)
elif id == "2":
#接收并显示
recv_mes(s)
elif id == "0":
break
else:
print("输入有误")
s.close() # 不用的时候 关闭套接字
if __name__=='__main__':
demo()
当对方发送数据给你时,操作系统会将数据缓存起来,应用程序调用recvfrom时,有需要的数据就给你,没有时就堵塞。
TCP
TCP协议,传输控制协议,是一种面向连接的,可靠的,基于字节流的传输层的通信协议。
TCP通信需要创建连接,数据传送,终止连接三个步骤。不适合用于广播的应用程序,广播的应用程序用UDP。
TCP客户端
import socket
def main():
#创建套接字
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#连接服务器
server_ip = input("输入连接的服务器ip: ")
server_port = int(input("输入端口: "))
tcp_socket.connect((server_ip,server_port))
#发送数据
send_data = input("发送的数据:")
tcp_socket.send(send_data.encode("utf-8"))
#关闭套接字
tcp_socket.close()
if __name__ == "__main__":
main()
TCP服务器
服务器需要绑定端口和ip,服务器先运行
import socket
def main():
#创建套接字
#这个套接字相当于监听
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定本地信息 bind
tcp_socket.bind(('',7892))
#让默认套接字有主动改为被动(因为默认的是主动) listen
tcp_socket.listen(128)
#等待客户端的连接 accept(会返回一个新的套接字为客户端服务,还返回客户端的ip和端口
new_client_socket,client_addr = tcp_socket.accept()#元组拆包
#接收客户端发送的请求
recv_data = new_client_socket.recv(1024)
print(recv_data)#recv_data就是一个数据,并不是像UDP一样是元组,因为已经知道了客户端的ip(提前建立连接了)
#回送数据给客户端
new_client_socket.send("----ok----".encode('utf-8'))
#关闭套接字
tcp_socket.close()
new_client_socket.close()
if __name__ == "__main__":
main()
循环为多个客户端服务
import socket
def main():
#创建套接字
#这个套接字相当于监听
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#绑定本地信息 bind
tcp_socket.bind(('',7892))
#让默认套接字有主动改为被动(因为默认的是主动) listen
tcp_socket.listen(128)
while True:#来个客户端就为其服务,服务结束关闭此套接字
#如果到来的客户端太多,就会排队,等待一个一个接收服务
#等待客户端的连接 accept(会返回一个新的套接字为客户端服务,还返回客户端的ip和端口
new_client_socket,client_addr = tcp_socket.accept()#元组拆包
while True:#循环多次 为同一个客户端服务多次
#接收客户端发送的请求
recv_data = new_client_socket.recv(1024)
print(recv_data)#recv_data就是一个数据,并不是像UDP一样是元组,因为已经知道了客户端的ip(提前建立连接了)
if recv_data:
#如果recv解堵塞 那么有两种可能 1客户端发送过来数据 2客户端调用close recv解堵塞
#回送数据给客户端
new_client_socket.send("----ok----".encode('utf-8'))
else:
break
new_client_socket.close()#关闭accept返回的套接字 意味着不会为此客户端服务
#关闭套接字
tcp_socket.close()
if __name__ == "__main__":
main()
案例:文件下载器
客户端:
import socket
def main():
tcp_socket = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
dest_ip = input("输入服务器ip:")
dest_port = int(input("输入服务器端口: "))
#连接服务器
tcp_socket.connect((dest_ip,dest_port))
download_file = input("输入下载的文件:")
#把文件名字发送到服务器
tcp_socket.send(download_file.encode('utf-8'))
#接收文件中的数据
recv_data = tcp_socket.recv(1024)#1024 1K 1024*1024 1M
if recv_data:
#保存收到的数据到一个文件中
with open(download_file,'wb') as f:#用with最后不需要close,而且如果没有该文件,也会自己创建一个
f.write(recv_data)
tcp_socket.close()
if __name__ == "__main__":
main()
服务器:
import socket
def send_file(new_client_socket, client_addr):
# 接收客户端发送的请求
file_name = new_client_socket.recv(1024).decode('utf-8')
print('客户端%s需要下载的文件是: %s' % (str(client_addr),file_name)) # recv_data就是一个数据,并不是像UDP一样是元组,因为已经知道了客户端的ip(提前建立连接了)
file_content = None
try:
f = open(file_name,'rb')
file_content = f.read()
f.close()
except Exception as ret:
print('没有要下载的文件 %s '% file_name)
if file_content:
new_client_socket.send(file_content)
def main():
# 创建套接字
# 这个套接字相当于监听
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地信息 bind
tcp_server_socket.bind(('', 7892))
# 让默认套接字有主动改为被动(因为默认的是主动) listen
tcp_server_socket.listen(128)
while True:
# 等待客户端的连接 accept(会返回一个新的套接字为客户端服务,还返回客户端的ip和端口
new_client_socket, client_addr = tcp_server_socket.accept() # 元组拆包
# 调用发送文件函数 回送数据给客户端
send_file(new_client_socket, client_addr)
new_client_socket.close()
# 关闭套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
TCP注意点:
- tcp服务器一般情况下都需要绑定,否则客户端找不到这个服务器。
- tcp客户端一般不绑定,因为主动连接服务器,所以只要确定好服务器的ip,port等信息就好,本地客户端可以随机。
- tcp服务器中通过listen可以将socket创建出来的主动套接字变为被动,这是tcp服务器必须要做的。
- 当客户端需要连接服务器时,就需要使用content连接,udp是不需要连接而是直接发送,tcp必须先连接,只有连接成功才能通信。
- 当一个tcp客户端连接服务器时,服务器端会有一个新的套接字,这个套接字用来标记这个客户端,单独为这个客户端服务。
- listen后的套接字是被动套接字,用来接收新的客户端用来接收新的客户端请求,而accept返回的套接字是标记这个新的客户端。
- 关闭listen后的套接字意味着被动套接字关闭了,会导致新的客户端不能连接诶服务器,但是之前已经连接成功的客户端正常通信。
- 关闭accept返回的套接字意味着这个客户端已经服务完毕。
- 当客户端的套接字调用close后,服务器段会recv解堵塞,并且返回的长度为0,因此服务器可以通过返回的数据的长度来区别客户端是否已经下线。