本篇包括网络编程概述、UDP简介、TFTP简介、TCP编程等。
目录
一、tcp/ip协议简介
二、端口
三、IP地址
四、mac地址
五、socket简介
六、UDP网络通信过程
七、模拟QQ聊天-多线程实现
八、wireshark抓包工具的使用
九、tftp下载器的使用(tftpd64或tftpd32)
十、UDP广播
十一、TCP服务器、客户端简介及实现
11.1 TCP简介
11.2 TCP和UDP通信模型
11.3 Python实现tcp服务器和客户端
一、tcp/ip协议简介
tcp/ip不是两个协议,而是一个协议组,实际为4层,逻辑上可以为7层,如下图所示:
二、端口
为什么使用端口?只有ip地址时只知道发往哪个电脑而不知道发往哪个程序,端口用来辨识要发往的具体程序。
为什么不用PID辨识进程?因为进程是动态的,远端电脑可能不知道本地的pid号。
知名端口:大家都知道的约定好的端口,如80端口为HTTP服务,21端口为FTP服务,范围为0~1023。
动态端口:用户自己定义的端口,范围为1024~65535.
查看端口命令:netstat -an
注意:在同一个OS中,端口不允许相同,如果某个端口已经被使用了,那么在这个进程释放这个端口之前,其他进程不能使用这个端口。因为端口用来区分一个进程。
三、IP地址
用来逻辑上表示网络上的唯一一台电脑。
注意:一个电脑可以有多个网卡,即多个IP地址!
IP地址分类
其中网络号固定不变,表示位于同一网络中的电脑,主机号为当前网络中的电脑号。
主机号为0时表示网段号,主机号为255时为网关。
D类用于多播(不是广播),例如视频会议,只有一些人可以看到。
E类实验和开发用。
私有ip
用于局域网中,访问公网时不能使用,需要转换为公有ip访问外网。范围如下:
注意
IP地址127.0.0.1~127.255.255.255用于回路测试,即测试当前电脑tcp/ip协议能不能用,例如ping 127.0.0.1,即使拔掉网线也能ping得通。
四、MAC地址
网卡的序列号,形如XX:XX:XX:XX:XX:XX,六组十六进制数,前三组表示厂商序列号,后三组表示网卡序列号。
五、socket简介
socket:通过网络使进程间通信。
注意:一个进程可以有多个socket!
python测试程序如下:
端口绑定(只能绑定自己的端口!)
上面程序每次运行时操作系统为它分配的端口不一样,这导致了远端电脑不知道每次运行的端口,不能发送信息到本地。
python程序如下:
注意:bindAddr中第一个参数为空,因为该参数表示本地IP地址,但本地可能有多个IP,空表示任意ip都进行绑定。
六、UDP网络通信过程
应用层填写需要发送的数据;传输增加上端口号等;网络层加上目的ip等;链路层加上目的mac等;如下图:
七、模拟QQ聊天-多线程实现
全双工实现QQ聊天,代码如下:
from threading import Thread
from socket import *
#1. 收数据,然后打印
def recvData():
while True:
recvInfo = udpSocket.recvfrom(1024)
print(">>%s:%s"%(str(recvInfo[1]), recvInfo[0]))
#2. 检测键盘,发数据
def sendData():
while True:
sendInfo = input("<<")
udpSocket.sendto(sendInfo.encode("gb2312"), (destIp, destPort))
udpSocket = None
destIp = ""
destPort = 0
def main():
global udpSocket
global destIp
global destPort
destIp = input("对方的ip:")
destPort = int(input("对方的ip:"))
udpSocket = socket(AF_INET, SOCK_DGRAM)
udpSocket.bind(("", 4567))
tr = Thread(target=recvData)
ts = Thread(target=sendData)
tr.start() #启动接收数据线程
ts.start() #启动发送数据线程
tr.join() #等待两个线程结束
ts.join()
if __name__ == "__main__":
main()
八、wireshark抓包工具的使用
wireshark工具可以抓取当前电脑中所有网络数据,具体如图所示:
九、tftp下载器的使用(tftpd64或tftpd32)
tftp是tcp/ip协议族中用来将客户端和服务器之间进行简单文件传输的协议。
特点如下:
基于UDP实现,可能会丢包,实现过程为收到-回复,下载过程如下:
TFTP数据表格式如下:
读写请求格式:操作码为1或2,分别表示读或写;文件名为文件名称;0为固定写法;模式有几种,最常用的为octet;最后跟一个0。
数据表格式:操作码固定为3;文件名为文件名称;块编号为文件分割的块编号;数据为详细数据。
确认格式:操作码固定为4;块编号为确认收到的文件分块编号。
错误表格式:操作码固定为5;差错码为固定好的错误编号;后面接具体差错信息;最后跟一个0;
注意:确认包发往的是服务器发送本地进程时分配的随机端口!
怎样确定数据已经发送完毕了?
规定, 当客户端接收到的数据⼩于516(2字节操作码+2个字节的序号+512字节数据) 时, 就意味着服务器发送完毕了。
怎样保证包中每个码的字节数?
python中组包代码如下:
!:表示网络中的数据,网络中的数据用大端表示。
H:占用2个字节,对应后面的1。
8s:占用8个字节,对应后面的"test.jpg"。
b:占用1个字节,对应后面的0。
5s:占用5个字节,对应后面的"octet"。
b:占用1个字节,对应后面的0。
表格式如图所示:
使用python从tftp服务器中下载文件
1)首先启动tftpd64应用程序,设置好下载的目录和ip地址。
2)python代码如下:
# -*- coding:utf-8 -*-
import struct
from socket import *
import time
import os
def main():
#0. 获取要下载的文件名字:
downloadFileName = raw_input("请输入要下载的文件名:")
#1.创建socket
udpSocket = socket(AF_INET, SOCK_DGRAM)
requestFileData = struct.pack("!H%dsb5sb"%len(downloadFileName), 1, downloadFileName, 0, "octet", 0)
#2. 发送下载文件的请求
udpSocket.sendto(requestFileData, ("192.168.119.215", 69))
flag = True #表示能够下载数据,即不擅长,如果是false那么就删除
num = 0
f = open(downloadFileName, "w")
while True:
#3. 接收服务发送回来的应答数据
responseData = udpSocket.recvfrom(1024)
# print(responseData)
recvData, serverInfo = responseData
opNum = struct.unpack("!H", recvData[:2])
packetNum = struct.unpack("!H", recvData[2:4])
print(packetNum[0])
# print("opNum=%d"%opNum)
# print(opNum)
# if 如果服务器发送过来的是文件的内容的话:
if opNum[0] == 3: #因为opNum此时是一个元组(3,),所以需要使用下标来提取某个数据
#计算出这次应该接收到的文件的序号值,应该是上一次接收到的值的基础上+1
num = num + 1
# 如果一个下载的文件特别大,即接收到的数据包编号超过了2个字节的大小
# 那么会从0继续开始,所以这里需要判断,如果超过了65535 那么就改为0
if num==65536:
num = 0
# 判断这次接收到的数据的包编号是否是 上一次的包编号的下一个
# 如果是才会写入到文件中,否则不能写入(因为会重复)
if num == packetNum[0]:
# 把收到的数据写入到文件中
f.write(recvData[4:])
num = packetNum[0]
#整理ACK的数据包
ackData = struct.pack("!HH", 4, packetNum[0])
udpSocket.sendto(ackData, serverInfo)
elif opNum[0] == 5:
print("sorry,没有这个文件....")
flag = False
# time.sleep(0.1)
if len(recvData)<516:
break
if flag == True:
f.close()
else:
os.unlink(downloadFileName)#如果没有要下载的文件,那么就需要把刚刚创建的文件进行删除
if __name__ == '__main__':
main()
十、UDP广播
UDP广播不是对每个用户轮流发送数据,而是发送到交换机,交换机负责同时发送给每个用户。
广播可用于动态获取ip地址。
单播----点对点;多播----一对多;广播----一无所有。
注意:广播只用于UDP中,TCP不能广播!
python简单实现:
十一、TCP服务器、客户端简介及实现
11.1 TCP简介
tcp:传输控制协议
特点:1、稳定;2、相对udp而言要慢一些;3、web服务器都是使用的tcp;
udp:用户数据包协议
特点:1、不稳定;2、相对tcp而言要快一些;
11.2 TCP和UDP通信模型
udp通信模型:相当于写信;
tcp通信模型:相当于打电话;
socket创建出来的套接字,默认为主动套接字,即发送数据给别人。listen()将主动套接字变为被动套接字。
TCP服务器端:
1、买个手机 socket(xxx);
2、插入手机卡 bind(xxx);
3、设置手机为响铃模式 listen();
4、等待别人的电话,准备好接听 accept();
TCP客户端:
1、买个手机 socket(xxx);
2、拨打电话 connect(xxx);
11.3 python实现tcp服务器和客户端
tcp服务器端实现(简单原理实现,非实际的多进程)如下:
注意:
accept用来接收客户端请求,并重新创建一个socket为新的客户服务,然后等待下一个客户端的请求。
clientSocket用来专门为新的客户端服务。
代码解释:
第一个while循环用来监听是否有新客户接入,并为它分配服务资源。
第二个while循环为新的客户端服务。注意:当客户端下线时,newSocket.recv(1024)这句可以解阻塞,且返回值为0,从而可以跳出循环。
该程序为单任务,实际服务器为多进程实现,只需将第二个while定义为一个函数,在第一个while中启动一个进程执行该函数即可。
#coding=utf-8
from socket import *
# 创建socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# 绑定本地信息
address = ('', 7788)
tcpSerSocket.bind(address)
# 使⽤socket创建的套接字默认的属性是主动的, 使⽤listen将其变为被动的, 这样就可以接收。
# 5表示服务器同一时刻最多允许5个客户端发数据
tcpSerSocket.listen(5)
while True:
# 如果有新的客户端来连接服务器, 那么就产⽣⼀个新的套接字专⻔为这个客户端服务器
# newSocket⽤来为这个客户端服务
# tcpSerSocket就可以省下来专⻔等待其他新客户端的链接
newSocket, clientAddr = tcpSerSocket.accept()
# 该循环为新的客户端服务。注意:当客户端下线时,newSocket.recv(1024)这句可以解阻塞,且返回
# 值为0,从而可以跳出循环
while True:
# 接收对⽅发送过来的数据, 最⼤接收1024个字节
recvData = newSocket.recv(1024)
# 如果接收的数据的⻓度为0, 则意味着客户端关闭了链接
if len(recvData)>0:
print 'recv:',recvData
else:
break
# 发送⼀些数据到客户端
sendData = raw_input("send:")
newSocket.send(sendData)
# 关闭为这个客户端服务的套接字, 只要关闭了, 就意味着为不能再为这个客户端服务了
newSocket.close()
# 关闭监听套接字, 只要这个套接字关闭了, 就意味着整个程序不能再接收任何新的客户端的连接
tcpSerSocket.close()
tcp客户器端实现如下:
from socket import *
#创建TCP套接字
clientSocket = socket(AF_INET, SOCK_STREAM)
#链接服务器
clientSocket.connect(("192.168.119.153", 8989))
#注意:
# 1. tcp客户端已经连接好了服务器,所以在以后的数据发送中,不需要填写对方的iph和port----->打电话
# 2. udp在发送数据的时候,因为没有之前的链接,所以需要 在每次的发送中 都要填写接收方的ip和port----->写信
#发送数据
clientSocket.send("haha".encode("gb2312"))
#接收数据
recvData = clientSocket.recv(1024)
#打印接收到的数据
print("recvData:%s"%recvData)
#关闭客户端socket
clientSocket.close()