网络通信概述

网络编程:让在不同的电脑上的软件能进行数据传递,即进程之间的通信。

TCP/IP协议(族)

互联网协议族,把互联网的协议简称为TCP/IP协议。作用:规范网络之间的数据通信。

常用协议见下图

网络1-风君雪科技博客

4层:链路层、网络层、传输层和应用层

7层:链路层拆分为数据链路层和物理层,应用层拆分为会话、表示和应用层。

端口

一个机器上的QQ发给另一个机器上的QQ的过程:

通过IP地址,可以两台机器进行通信,但是要指定发送到QQ,这就需要端口来指定

这里为什么不能用进程的PID呢,这是因为跨电脑,获取进程很难,同一个电脑可以容易获取进程。

正常一些进程端口固定的,端口就是标记唯一进程的

端口好比房子的门,出入这间房子的必经之路。一个进程需要收发网络数据,就需要这样的端口。

Linux系统,端口有2^16个之多。对端口进行编号叫端口号,只有整数,范围0-65535。

端口的分配:知名端口和动态端口

知名端口(well known ports):众所周知的端口号,范围从0-1023

80端口分配给HTTP服务
21端口分配给FTP服务

动态端口(dynamic ports): 范围从1024-65535,动态分配,不固定分配,进程需要网络通信时,向主机申请一个端口,主机从可用的端口号中分配一个供他使用,进程关闭,释放占用的端口号。

查看端口 netstat -an  查看端口状态

ip地址

在网络上标记一台电脑的一串数字,在本地局域网上是唯一的

每个IP地址包括两部分 网络地址和主机地址

分类:A类、B类和C类,C类地址由3字节的网络地址和1字节的主机地址组成,每个网络能容纳254个主机。

广播地址 xxx.xxx.xx.255  网络号 xxx.xxx.xx.0 ,因此最多能用254个

ifconfig  显示网络地址
ping 127.0.0.1 测试网络通信
127.0.0.1-127.255。255.255用于回路测试

Socket简介

本地间的进程间通信有很多种方式,如队列、同步(互斥锁、条件变量等)

网络中进程间如何通信

什么是socket

socket(套接字)是进程间通信的方式,能实现不同主机间的进程通信,网络上各式服务大多是基于socket完成通信的,如浏览网页、QQ聊天、收发邮件等

创建socket

from socket import *
udpSocket = socket(AF_INET, SOCK_DGRAM)#通信类型AF_INET(和IPV4对应),协议家族(UDP通信的SOCK_DGRAM) #TCP是SOCK_STREAM
#help(socket.socket)是一个类所以
#udpSocket是一个对象用来接收和发送数据 udpSocket.bind(("",7788))#绑定的是元组,远程主机IP和端口号 udpSocket.sendto("haha",("192.168.0.103",8080))#发送的信息和接收方IP和port

UDP传输快不稳定
TCP传输慢但是稳定不丢数据
UDP可以通过写信来理解
TCP可以通过打电话来理解

1.udp是TCP/IP协议族中的一种协议,能够完成不同机器上的程序见的数据通信

2.udp服务器、客户端

请求服务的一方称之为客户端,提供服务的一方称之为服务器

3.udp绑定问题

一般情况下,服务器端,需要绑定端口,目的是为了让其他的客户端能正确发送到此进程

客户端不需要绑定,让操作系统随机分配,就不会出现因为需要绑定的端口被占用而导致程序无法运行

的情况。

udp和tcp的区别:udp基于信息包的通信,TCP和文件一样,基于字节流的通信。

网络调试助手可以调试数据的通信,编码采用gb2312,发汉字的时候需要设置

接收

接收数据
from socket import *
#1.创建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2.绑定本地相关信息(一般接收方才做绑定工作)
udpSocket.bind(('',7788))#其实是bind(bindAddr) bindAddr=('',7788) IP地址和端口一般IP不需要写表示本机的任何一个IP(因为有时候会有虚拟机)
#3.等待接收对方发送的数据
recvData = udpSocket.recvfrom(1024)#本次接收的最大字节数
#4.显示接收到的数据
print(recvData)
#5.关闭套接字
udpSocket.close()
from socket import *

udpSocket = socket(AF_INET, SOCK_DGRAM)
destIP = input("请输入目的IP:")
destPort = int(input("请输入目的port:"))
sendData = input("请输入要发送的数据:")

udpSocket.sendto(sendData.encode("gb2312"),(destIP,destPort))

网络通信过程

网络1-风君雪科技博客

form socket import *
def main():
    udpSocket = socket(AF_INET, SOCK_DGRAM)
    udpSocket.bind("",6678)
    while True:
        recvInfor = udpSocket.recvfrom(1024)
        print("[%s]:%s"%(recvInfor[1]),recvInfor[0].decode("gb2312"))
if __name == "__main__":
    main()
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("对方的port:"))

    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()

TFTP下载项目

wireshark抓包工具的使用

安装:默认配置即可,除了需要勾选Install WinPcao

TFTP协议介绍

是TCP/IP协议族中的一个用来在客户端和服务器之间进行简单文件传输的协议。特点是简单,占用资源少,适合传递小文件,适合在局域网传递,端口号是69,基于UDP实现。

TFTP下载过程

服务器默认监听69号端口,客户端发送“下载”请求(读请求)时,需要向服务器的69号端口发送,服务器批准请求,使用一个新的、临时的端口进行数据传输。

网络1-风君雪科技博客

网络1-风君雪科技博客

当服务器找到需要现在的⽂件后, 会⽴刻打开⽂件, 把⽂件中的数据通过TFTP协议发送给客户端
如果⽂件的总⼤⼩较⼤(⽐如3M) , 那么服务器分多次发送, 每次会从⽂件中读取512个字节的数据发送过来
因为发送的次数有可能会很多, 所以为了让客户端对接收到的数据进⾏排序, 所以在服务器发送那512个字节数据的时候, 会多发2个字节的数据, ⽤
来存放序号, 并且放在512个字节数据的前⾯, 序号是从1开始的
因为需要从服务器上下载⽂件时, ⽂件可能不存在, 那么此时服务器就会发送⼀个错误的信息过来, 为了区分服务发送的是⽂件内容还是错误的提示信
息, 所以⼜⽤了2个字节 来表示这个数据包的功能(称为操作码) , 并且在序号的前⾯
因为udp的数据包不安全, 即发送⽅发送是否成功不能确定, 所以TFTP协议中规定, 为了让服务器知道客户端已经接收到了刚刚发送的那个数据包, 所
以当客户端接收到⼀个数据包的时候需要向服务器进⾏发送确认信息, 即发送收到了, 这样的包成为ACK(应答包)
为了标记数据已经发送完毕, 所以规定, 当客户端接收到的数据⼩于516(2字节操作码+2个字节的序号+512字节数据) 时, 就意味着服务器发送完毕了
网络1-风君雪科技博客

客户端 服务器架构  client server c/s架构

浏览器服务器架构 browser server b/s架构

网络1-风君雪科技博客

# -*- 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()