#coding=utf-8
# socket
#网络中进程之间通信:网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。利用ip地址,协议,端口就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互
#socket(简称 套接字) 是进程间通信的一种方式,它与其他进程间通信的一个主要不同是:它能实现不同主机间的进程间通信,我们网络上各种各样的服务大多都是基于 Socket 来完成通信的;例如我们每天浏览网页、QQ 聊天、收发 email 等等
#创建socket: Python 中 使用socket 模块的函数 socket 就可以完成:socket.socket(AddressFamily, Type),函数 socket.socket 创建一个 socket,返回该 socket 的描述符,该函数带有两个参数:Address Family:可以选择 AF_INET(用于 Internet 进程间通信) 或者 AF_UNIX(用于同一台机器进程间通信),实际工作中常用AF_INET;Type:套接字类型,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 协议)或者 SOCK_DGRAM(数据报套接字,主要用于 UDP 协议)
#创建一个tcp socket(tcp套接字)
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Socket Created')#Socket Created
#创建一个udp socket(udp套接字)
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
print('Socket Created')#Socket Created
#创建一个udp客户端程序的流程是简单,具体步骤如下:创建客户端套接字;发送/接收数据;关闭套接字
#发送数据
#coding=utf-8
from socket import *
#1. 创建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2. 准备接收方的地址
sendAddr = ('192.168.80.1', 8080)
#3. 从键盘获取数据
sendData = input("请输入要发送的数据:")
#4. 发送数据到指定的电脑上
udpSocket.sendto(sendData.encode('utf-8'), sendAddr)
#5. 关闭套接字
udpSocket.close()
#接收数据
#coding=utf-8
from socket import *
#1. 创建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2. 准备接收方的地址
sendAddr = ('192.168.80.1', 8080)
#3. 从键盘获取数据
sendData = input("请输入要发送的数据:")
#4. 发送数据到指定的电脑上
udpSocket.sendto(sendData.encode('utf-8'), sendAddr)
#5. 等待接收对方发送的数据
recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数
#6. 显示对方发送的数据
print(recvData)#(b'haha1', ('192.168.80.1', 8080))
#7. 关闭套接字
udpSocket.close()
#每重新运行一次网络程序,端口不一样的原因在于,当重新运行时,如果没有确定到底用哪个,系统默认会随机分配。记住一点:这个网络程序在运行的过程中,这个就唯一标识这个程序,所以如果其他电脑上的网络程序如果想要向此程序发送数据,那么就需要向这个数字(即端口)标识的程序发送即可
#udp绑定信息
#coding=utf-8
from socket import *
#1. 创建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2. 绑定本地的相关信息,如果一个网络程序不绑定,则系统会随机分配
bindAddr = ('', 7788) # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
udpSocket.bind(bindAddr)
#3. 等待接收对方发送的数据
recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数
#4. 显示接收到的数据
print(recvData)#(b'thanks', ('192.168.80.1', 8080))
#5. 关闭套接字
udpSocket.close()
#一个udp网络程序,可以不绑定,此时操作系统会随机进行分配一个端口,如果重新运行次程序端口可能会发生变化,一个udp网络程序,也可以绑定信息(ip地址,端口号),如果绑定成功,那么操作系统用这个端口号来进行区别收到的网络数据是否是此进程的
#udp应用:echo服务器,echo服务端循环等待对方发数据,再发送回去
#coding=utf-8
from socket import *
#1. 创建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2. 绑定本地的相关信息
bindAddr = ('', 7788) # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
udpSocket.bind(bindAddr)
num = 1
while True:
#3. 等待接收对方发送的数据
recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数
#4. 将接收到的数据再发送给对方
udpSocket.sendto(recvData[0], recvData[1])
#5. 统计信息
print('已经将接收到的第%d个数据返回给对方,内容为:%s'%(num,recvData[0]))
num+=1
#5. 关闭套接字
udpSocket.close()
#udp应用:聊天室
#coding=utf-8
from socket import *
from time import ctime
#1. 创建套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#2. 绑定本地的相关信息
bindAddr = ('', 7788) # ip地址和端口号,ip一般不用写,表示本机的任何一个ip
udpSocket.bind(bindAddr)
while True:
#3. 等待接收对方发送的数据
recvData = udpSocket.recvfrom(1024) # 1024表示本次接收的最大字节数
#4. 打印信息
print('【%s】%s:%s'%(ctime(),recvData[1][0],recvData[0]))#【Mon Nov 23 11:25:58 2020】192.168.80.1:b'thank2'
#5. 关闭套接字
udpSocket.close()
#1. udp是TCP/IP协议族中的一种协议能够完成不同机器上的程序间的数据通信;2. udp服务器、客户端:udp的服务器和客户端的区分:往往是通过请求服务和提供服务来进行区分;请求服务的一方称为:客户端;提供服务的一方称为:服务器;3. udp绑定问题:一般情况下,服务器端,需要绑定端口,目的是为了让其他的客户端能够正确发送到此进程,客户端,一般不需要绑定,而是让操作系统随机分配,这样就不会因为需要绑定的端口被占用而导致程序无法运行的情况
#TFTP(Trivial File Transfer Protocol,简单文件传输协议):是TCP/IP协议族中的一个用来在客户端与服务器之间进行简单文件传输的协议。适合在局域网进行传递,端口号为69,基于UDP实现
#TFTP服务器默认监听69号端口,当客户端发送“下载”请求(即读请求)时,需要向服务器的69端口发送,服务器若批准此请求,则使用一个新的、临时的 端口进行数据传输。当服务器找到需要现在的文件后,会立刻打开文件,把文件中的数据通过TFTP协议发送给客户端,如果文件的总大小较大(比如3M),那么服务器分多次发送,每次会从文件中读取512个字节的数据发送过来,因为发送的次数有可能会很多,所以为了让客户端对接收到的数据进行排序,所以在服务器发送那512个字节数据的时候,会多发2个字节的数据,用来存放序号,并且放在512个字节数据的前面,序号是从1开始的。因为需要从服务器上下载文件时,文件可能不存在,那么此时服务器就会发送一个错误的信息过来,为了区分服务发送的是文件内容还是错误的提示信息,所以又用了2个字节 来表示这个数据包的功能(称为操作码),并且在序号的前面。
"""
操作码 功能
1 读请求,即下载
2 写请求,即上传
3 表示数据包,即DATA
4 确认码,即ACK
5 错误
"""
#因为udp的数据包不安全,即发送方发送是否成功不能确定,所以TFTP协议中规定,为了让服务器知道客户端已经接收到了刚刚发送的那个数据包,所以当客户端接收到一个数据包的时候需要向服务器进行发送确认信息,即发送收到了,这样的包成为ACK(应答包)。为了标记数据已经发送完毕,所以规定,当客户端接收到的数据小于516(2字节操作码+2个字节的序号+512字节数据)时,就意味着服务器发送完毕了。
#在192.168.80.128上搭建TFTP服务器,1、yum install xinetd tftp tftp-server # root 用户执行
# 2、配置tftp-server
"""
# [root@localhost ~]# cat /etc/xinetd.d/tftp
# default: off
# description: The tftp server serves files using the trivial file transfer \
# protocol. The tftp protocol is often used to boot diskless \
# workstations, download configuration files to network-aware printers, \
# and to start the installation process for some operating systems.
service tftp
{
socket_type = dgram
protocol = udp
wait = yes
user = root
server = /usr/sbin/in.tftpd
server_args = -s /var/lib/tftpboot -c # 注意这行,如果允许上传,一定要加上参数 -c
disable = no # 这行默认为yes,改成no,允许
per_source = 11
cps = 100 2
flags = IPv4
}
"""
#3、启动服务
"""
[root@localhost ~]#systemctl restart xinetd.service
[root@localhost ~]# netstat -a | grep tftp
udp 0 0 0.0.0.0:tftp 0.0.0.0:*
udp6 0 0 [::]:tftp [::]:*
[root@localhost ~]# netstat -tunap | grep :69
udp 0 0 0.0.0.0:69 0.0.0.0:* 30014/xinetd
udp6 0 0 :::69 :::* 1/systemd
"""
#4、如果上传时出现"连接请求失败"的提示,请确保tftp服务的文件存放目录权限设置正确
#解决办法:chmod 0777 /var/lib/tftpboot #其中tftpboot为上传和下载文件存储目录
#coding:utf-8
from socket import *
import struct
import sys
if len(sys.argv) != 2:
print('-'*30)
print("tips:")
print("python network.py 192.168.80.128")
print('-'*30)
exit()
else:
ip = sys.argv[1]
# 创建udp套接字
udpSocket = socket(AF_INET, SOCK_DGRAM)
#构造下载请求数据
cmd_buf = struct.pack("!H8sb5sb".encode('utf-8'),1,"test.jpg".encode('utf-8'),0,"octet".encode('utf-8'),0)
#发送下载文件请求数据到指定服务器
sendAddr = ('192.168.80.128', 69)
udpSocket.sendto(cmd_buf, sendAddr)
p_num = 0
recvFile = ''
while True:
recvData,recvAddr = udpSocket.recvfrom(1024)
recvDataLen = len(recvData)
# print recvAddr # for test
# print len(recvData) # for test
cmdTuple = struct.unpack("!HH", recvData[:4])
# print cmdTuple # for test
cmd = cmdTuple[0]
currentPackNum = cmdTuple[1]
if cmd == 3: #是否为数据包
# 如果是第一次接收到数据,那么就创建文件
if currentPackNum == 1:
recvFile = open("test.jpg", "ab")
# 包编号是否和上次相等
if p_num+1 == currentPackNum:
recvFile.write(recvData[4:])
p_num +=1
print('(%d)次接收到的数据'%(p_num))
ackBuf = struct.pack("!HH",4,p_num)
udpSocket.sendto(ackBuf, recvAddr)
# 如果收到的数据小于516则认为出错
if recvDataLen<516:
recvFile.close()
print('已经成功下载!!!')
break
elif cmd == 5: #是否为错误应答
print("error num:%d"%currentPackNum)
break
udpSocket.close()
"""
PS D:\python> python network.py test.jpg
(1)次接收到的数据
(2)次接收到的数据
已经成功下载!!!
"""
#网络编程中的广播
#coding=utf-8
import socket, sys
dest = ('', 7788)
# 创建udp套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 对这个需要发送广播数据的套接字进行修改设置,否则不能发送广播数据
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,1)
# 以广播的形式发送数据到本网络的所有电脑中
s.sendto("Hi".encode('utf-8'), dest)
print("等待对方回复(按ctrl+c退出)")
while True:
(buf, address) = s.recvfrom(2048)
print("Received from %s: %s" % (address, buf))
"""客户端(UDP协议局域网广播)"""
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
PORT = 7788
s.bind(('', PORT))
print('Listening for broadcast at ', s.getsockname())
while True:
data, address = s.recvfrom(65535)
print('Server received from {}:{}'.format(address, data.decode('utf-8')))
#如果想要完成一个tcp服务器的功能,需要的流程如下:socket创建一个套接字;bind绑定ip和port;listen使套接字变为可以被动链接;accept等待客户端的链接;recv/send接收发送数据
#先运行这段代码作为服务端,打开客户端链接服务端,发送信息“哈哈”,客户端会收到:[2020-11-23 16:01:25.449]# RECV ASCII>thank you !
#coding=utf-8
from socket import *
# 创建socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# 绑定本地信息
address = ('', 7788)
tcpSerSocket.bind(address)
# 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了
tcpSerSocket.listen(5)
# 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务器;newSocket用来为这个客户端服务;tcpSerSocket就可以省下来专门等待其他新客户端的链接
newSocket, clientAddr = tcpSerSocket.accept()
# 接收对方发送过来的数据,最大接收1024个字节
recvData = newSocket.recv(1024)
recvData = recvData.decode('gb2312')#中文编码
print('接收到的数据为:',recvData)
# 发送一些数据到客户端
newSocket.send("thank you !".encode('utf-8'))
# 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
newSocket.close()
# 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接
tcpSerSocket.close()
#tcp客户端构建流程
#coding:utf-8
from socket import *
# 创建socket
tcpClientSocket = socket(AF_INET, SOCK_STREAM)
# 链接服务器
serAddr = ('192.168.80.1', 7788)
tcpClientSocket.connect(serAddr)
# 提示用户输入数据
sendData = input("请输入要发送的数据:")
tcpClientSocket.send(sendData.encode('utf-8'))
# 接收对方发送过来的数据,最大接收1024个字节
recvData = tcpClientSocket.recv(1024).decode('gb2312')
print('接收到的数据为:',recvData)
# 关闭套接字
tcpClientSocket.close()
#模拟QQ聊天#客户端
#coding=utf-8
from socket import *
# 创建socket
tcpClientSocket = socket(AF_INET, SOCK_STREAM)
# 链接服务器
serAddr = ('192.168.80.1', 7788)
tcpClientSocket.connect(serAddr)
while True:
# 提示用户输入数据
sendData = input("send:")
if len(sendData)>0:
tcpClientSocket.send(sendData.encode('utf-8'))
else:
break
# 接收对方发送过来的数据,最大接收1024个字节
recvData = tcpClientSocket.recv(1024)
print('recv:',recvData.decode('gb2312'))
# 关闭套接字
tcpClientSocket.close()
#服务器端
#coding=utf-8
from socket import *
# 创建socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# 绑定本地信息
address = ('', 7788)
tcpSerSocket.bind(address)
# 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了
tcpSerSocket.listen(5)
while True:
# 如果有新的客户端来链接服务器,那么就产生一个信心的套接字专门为这个客户端服务器, # newSocket用来为这个客户端服务, # tcpSerSocket就可以省下来专门等待其他新客户端的链接
newSocket, clientAddr = tcpSerSocket.accept()
while True:
# 接收对方发送过来的数据,最大接收1024个字节
recvData = newSocket.recv(1024).decode('gb2312')
# 如果接收的数据的长度为0,则意味着客户端关闭了链接
if len(recvData)>0:
print('recv:',recvData)
else:
break
# 发送一些数据到客户端
sendData = input("send:")
newSocket.send(sendData.encode('utf-8'))
# 关闭为这个客户端服务的套接字,只要关闭了,就意味着为不能再为这个客户端服务了,如果还需要服务,只能再次重新连接
newSocket.close()
# 关闭监听套接字,只要这个套接字关闭了,就意味着整个程序不能再接收任何新的客户端的连接
tcpSerSocket.close()
#listen的队列长度
#服务器端
#coding=utf-8
from socket import *
from time import sleep
# 创建socket
tcpSerSocket = socket(AF_INET, SOCK_STREAM)
# 绑定本地信息
address = ('', 7788)
tcpSerSocket.bind(address)
connNum = int(input("请输入要最大的链接数:"))
# 使用socket创建的套接字默认的属性是主动的,使用listen将其变为被动的,这样就可以接收别人的链接了。代表可建立socket连接的排队的个数windows,mac 此连接参数有效,Linux 此连接参数无效,默认最大
#bacuklog的定义:在进程正理一个一个连接请求的时候,可能还存在其它的连接请求。因为TCP连接是一个过程,所以可能存在一种半连接的状态,有时由于同时尝试连接的用户过多,使得服务器进程无法快速地完成连接请求。如果这个情况出现了,服务器进程希望内核如何处理呢?内核会在自己的进程空间里维护一个队列以跟踪这些完成的连接但服务器进程还没有接手处理或正在进行的连接,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限。这个backlog告诉内核使用这个数值作为上限。
#当客户端请求到来时,服务器会处理他们的请求,在你的代码中就是创建一个线程来处理。那么请求多了之后就会创建很多个线程,这样服务器资源总会到达上限。backlog为5,实际上不是限制处理的线程为5,而是当服务器忙不过来了,内核会hold住一个长度为5的队列,一旦服务器忙过来了,就会从这个队列中拿出一个来处理,直到队列为空。
tcpSerSocket.listen(connNum)
while True:
# 如果有新的客户端来链接服务器,那么就产生一个新的套接字专门为这个客户端服务器
newSocket, clientAddr = tcpSerSocket.accept()
print(clientAddr)
sleep(1)
#客户端运行
#coding=utf-8
from socket import *
connNum = input("请输入要链接服务器的次数:")
for i in range(int(connNum)):
s = socket(AF_INET, SOCK_STREAM)
s.connect(("192.168.80.1", 7788))
print(i)
#listen中的black表示已经建立链接和半链接的总数,如果当前已建立链接数和半链接数以达到设定值,那么新客户端就不会connect成功,而是等待服务器