目录
一、socket
二、传输协议
三、UDP数据发送
发送数据
接收数据
四、echo服务器
五、聊天程序
(该图片单单只为了学习一个英语单词)
socket也叫做套接字,是应用层和传输层之间的桥梁,利用socket我们可以通过网络完成进程间通信:
应用层封装好数据给socket,socket会将数据传递给传输层,经过一层层的传输,通过网络传给另一台电脑,再经过一层层解析,解析到socket后,socket将数据给应用层。
socket本质是编程接口(API),是对TCP/IP协议的封装,封装成的socket更容易使用TCP/IP协议,它是面向客户-服务器模型设计的,针对客户和服务器的程序提供了不同的系统调用。
服务器可以看做大型的计算机,功能更强,数据大部分都存放在服务器里,可以在自己的电脑或者手机下载一个客户端,客户端里面存放的数据量并不大,比如说,手机下载一个爱奇艺的客户端,想下载视频,那么需要socket发送一个请求到服务器,服务器接收到信号之后再将数据传输给手机,手机才能下载。
socket(套接字)之间的连接可以分为三个步骤:服务器监听(服务器等待别人连接)、客户端请求(告诉服务器自己想干什么)、连接确认(双方确认连接)。
接下来创建一个套接字对象:
from socket import *#导入套接字模块
s = socket(AF_INET,SOCK_DGRAM)#s是一个socket对象,拥有发送和接收网络数据的能力
函数里有两个参数必须写:AF_INET规定使用IPV4协议用于Internet进程间的通信,下一个参数可以是SOCK_STREAM(TCP协议)或者SOCK_DGRAM(UDP协议)
---TCP慢但是稳定不会丢失数据
---UDP快但是可能会丢失数据,被黑客攻击
UDP——User Datagram Protocol,用户数据报协议。
是无连接的、简单的、面向数据报的传输层协议,在发送数据时,不会跟对方建立连接,且不确定对方是否能收到,只是简单的将数据传输过去,并不能保证能不能到达目的地,因为UDP传输数据时不用建立连接,并且没有超时重发等机制,所以传输数据速度很快,适用于QQ之类的。
TCP——Transmission Control Protocol,传输控制协议
是一个面向连接的协议,就是说建立连接时,必须确定建立了一个连接,一个TCP的连接必须经过三次对话,主机A想跟主机B对话,则主机A向主机B发送一个连接请求的数据包,接着主机B向主机A发送一个同意的连接,主机A收到后,向B发送一个数据包,确认主机B的要求,三次对话完成,然后才传输正式的数据。TCP占用的资源更多结构更复杂,TCP可以更保证数据的正确性,可以保证数据传输的顺序。
网络通信时,双方的协议应该一样。
sendto函数可以实现数据发送,函数中添加元组,元组里是IP地址和端口号,通过IP地址和端口号找到接收方。(代码中的IP和端口是从网络调试助手看到的)
from socket import *
s = socket(AF_INET,SOCK_DGRAM)#IPv4协议,UDP协议
s.sendto("hello",("192.168.0.106",8080))#向IP是192.168.0.106的8080端口发送hello
我们利用网络调试助手接收数据,协议是UDP,跟发送方相同,端口号默认可用动态端口(8080),可以换成其他可用端口,编程中做相应的修改即可,点击打开。
程序运行后会报错:
因为网络之间数据发送要使用字节流,应使用encode(),就可以将前面的字符串转换为字节流 :
s.sendto("hello".encode(),("192.168.0.106",8080))
修改后会如下:
通过网络调试助手可以看到发送方的网络地址和端口号:
因为程序没有绑定端口号,所以每次运行程序发送数据的端口可能会变。
如果发送中文,会发现乱码,因为python中文默认是UTF-8编码,而网络调试助手是GB2312编码,发送的数据编码和解码不一样,所以会乱码,所以我们的python指定使用GB2312编码:
from socket import *
s = socket(AF_INET,SOCK_DGRAM)#IPv4协议,UDP协议
s.sendto("你好".encode("gb2312"),("192.168.0.106",8080))
然后就可以正常的发送信息:
可以将地址、内容都赋值给变量,进行优化 :
from socket import *
s = socket(AF_INET,SOCK_DGRAM)#IPv4协议,UDP协议
addr = ("192.168.0.106",8080)
date = input("请输入内容:")
s.sendto(date.encode("gb2312"),addr)
效果如下:
如果想使用VS接收数据,则
s.recvfrom(1024)
程序运行到这里时会阻塞,等待接收数据,1024表示本次接收所能接收的最大字节数,程序接收完后应该:
s.close()
因为对象s是占用一定空间的,当用不到s后,应该及时的关闭。代码如下:
from socket import *#导入模块
addr = ("192.168.0.106",8080)#准备接收方的地址
s = socket(AF_INET,SOCK_DGRAM)
s.sendto("你好鸭".encode("gb2312"),addr)#发送内容,转换为字节流
redate = s.recvfrom(1024)
print(redate)
s.close()
运行结果如下:
网络调试助手接收到信息后会自动得到对方的地址(如图中的远程主机),所以网络调试助手发送数据时知道往哪里发送。
收到的数据是一个元组,第一个元素是内容,开头是“b”,表示是一个字节流,想得到收到的消息,应该:
print(redate[0].decode())#0是第一个元素,decode转换为字符串
又因为网络调试助手是GB2312编码,所以应该指定为GB2312解码:
print(redate[0].decode("gb2312"))
效果如下:
ps:如果是字符串常量,在字符串前加”b”,效果跟加encode一样
s.sendto(b"hhhh",addr)
内容可以是 ASCII范围内的字符和其它十六进制形式的字符数据,但不能是中文等非ASCII字符。
前面是网络端口先接受数据,这样网络端口发送数据时才能知道往哪里发,如果是网络端口直接发送,则编程时应该先绑定一个IP地址和端口号:
s.bind(("",8888))
IP会自动是本机IP,所以没必要指定。但发现网络调试助手不能主动跟别人发送数据,所以还是应该主动给网络调试助手发送一个数据,此时,得到发送数据的端口就是8888。
echo服务器是非常有用的用于调试和检测的工具,协议的作用也很简单,就是接收到什么原样发回。
新建一个python文件写入:
from socket import *#导入模块
udpSock = socket(AF_INET,SOCK_DGRAM)
udpSock.bind(("",8888))#绑定一个端口
while True:#使其不停的做收发
recvData = udpSock.recvfrom(1024)#首先接收发送来的数据
udpSock.sendto(recvData[0],recvData[1])#将数据发送回去
udpSock.close()
原程序为:
from socket import *#导入模块
s = socket(AF_INET,SOCK_DGRAM)
s.bind(("",7777))
addr = ("113.54.204.147",8888)
s.sendto("hello,你好".encode(),addr)
redata = s.recvfrom(1024)
print(redata[0].decode())
都运行之后会发现发送什么,就会返还什么,程序的作用就是可以测试网络通不通。
一个文件写入:
from socket import *#导入模块
udpSock = socket(AF_INET,SOCK_DGRAM)
udpSock.bind(("",8888))#绑定一个端口
addr = ("113.54.204.147",7777)
while True:
recvData = udpSock.recvfrom(1024)
print(recvData[0].decode())
data = input("想说啥:")
udpSock.sendto(data.encode(),addr)
udpSock.close()
另一个写入:
from socket import *#导入模块
s = socket(AF_INET,SOCK_DGRAM)
s.bind(("",7777))
addr = ("113.54.204.147",8888)
while True:
data = input(":")
s.sendto(data.encode(),addr)
recvData = s.recvfrom(1024)
print(recvData[0].decode())
s.close()
因为都是python编写,用的是utf-8,所以不用再指定。
此时是一个半双工程序,就是说收发消息不能同时进行,因为执行接收时会阻塞,不能发送消息。
其实可以通过并发编程来实现,但是在多进程中不能使用input()。