网络编程——协议基础

程序员yt2020/07/09 14:46:03

本篇包括网络编程概述、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得通。

Linux服务器开发学习视频资料,包括Linux,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等等。

需要知识技术学习视频文档资料的朋友可以后台私信【架构】获取

网络编程——协议基础

网络编程——协议基础

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

内容来自于 今日头条

若本内容侵犯了您的合法权益,请作者持权属证明与此邮箱联系,邮箱地址:feedcoopjubao@toutiao.com。