网络编程——1、协议基础

网络编程——1、协议基础,本篇。

网络编程——2、通信基础,点击此处。

网络编程——3、服务器基础,点击此处。

本篇包括网络编程概述、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层,如下图所示:

网络编程——1、协议基础_第1张图片

二、端口

为什么使用端口?只有ip地址时只知道发往哪个电脑而不知道发往哪个程序,端口用来辨识要发往的具体程序。

为什么不用PID辨识进程?因为进程是动态的,远端电脑可能不知道本地的pid号。

知名端口:大家都知道的约定好的端口,如80端口为HTTP服务,21端口为FTP服务,范围为0~1023。

动态端口:用户自己定义的端口,范围为1024~65535.

查看端口命令:netstat  -an

注意:在同一个OS中,端口不允许相同,如果某个端口已经被使用了,那么在这个进程释放这个端口之前,其他进程不能使用这个端口。因为端口用来区分一个进程。

网络编程——1、协议基础_第2张图片

三、IP地址

用来逻辑上表示网络上的唯一一台电脑。

注意:一个电脑可以有多个网卡,即多个IP地址!

IP地址分类

其中网络号固定不变,表示位于同一网络中的电脑,主机号为当前网络中的电脑号。

主机号为0时表示网段号,主机号为255时为网关

D类用于多播(不是广播),例如视频会议,只有一些人可以看到。

E类实验和开发用。

网络编程——1、协议基础_第3张图片

私有ip

用于局域网中,访问公网时不能使用,需要转换为公有ip访问外网。范围如下:

网络编程——1、协议基础_第4张图片

注意

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测试程序如下:

网络编程——1、协议基础_第5张图片网络编程——1、协议基础_第6张图片

端口绑定(只能绑定自己的端口!)

上面程序每次运行时操作系统为它分配的端口不一样,这导致了远端电脑不知道每次运行的端口,不能发送信息到本地。

python程序如下:

注意:bindAddr中第一个参数为空,因为该参数表示本地IP地址,但本地可能有多个IP,空表示任意ip都进行绑定。

网络编程——1、协议基础_第7张图片

六、UDP网络通信过程

应用层填写需要发送的数据;传输层加上端口号等;网络层加上目的ip等;链路层加上目的mac等;如下图:

网络编程——1、协议基础_第8张图片

七、模拟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工具可以抓取当前电脑中所有网络数据,具体如图所示:

网络编程——1、协议基础_第9张图片

九、tftp下载器的使用(tftpd64或tftpd32)

tftp是tcp/ip协议族中用来将客户端和服务器之间进行简单文件传输的协议。

特点如下:

网络编程——1、协议基础_第10张图片

基于UDP实现,可能会丢包,实现过程为收到-回复,下载过程如下:

网络编程——1、协议基础_第11张图片

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。

包格式如图所示:

网络编程——1、协议基础_第12张图片

使用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简单实现:

网络编程——1、协议基础_第13张图片

十一、TCP服务器、客户端简介及实现

11.1 TCP简介

tcp:传输控制协议

特点:1、稳定;2、相对udp而言要慢一些;3、web服务器都是使用的tcp;

udp:用户数据包协议

特点:1、不稳定;2、相对tcp而言要快一些;

11.2 TCP和UDP通信模型

udp通信模型:相当于写信;

网络编程——1、协议基础_第14张图片

tcp通信模型:相当于打电话;

 socket创建出来的套接字,默认为主动套接字,即发送数据给别人。listen()将主动套接字变为被动套接字。

TCP服务器端:

1、买个手机  socket(xxx);

2、插入手机卡  bind(xxx);

3、设置手机为响铃模式  listen();

4、等待别人的电话,准备好接听  accept();

TCP客户端:

1、买个手机  socket(xxx);

2、拨打电话  connect(xxx);

网络编程——1、协议基础_第15张图片

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

 

你可能感兴趣的:(计算机网络)