TCP : 可靠传输,不安全,UDP: 安全传输,不可靠
一台机器上有2^16-1=65535个端口(1-1024)保留自己开就1024往上
socket (套接字):也可以理解为它是一个管道,用于描述IP地址和端口
socket是一种特殊的文件:针对服务器端和客户端来 打开(建立链接),读(发送数据),写(接收数据)关闭的模式来实现信息的交换
一、socket函数
socket.socket(family,type[, protocol]) 默认协议为TCP/IP
# socket.slcketpair([family,type[,proto]]]):
# socket.create_connection(address[,timeout[, source_address]])
参数说明:
1.family指定应用程序使用的通信协议的协议族
family参数 | |
socket.AF_UNIX | 只能单一的在Unix系统进行进程通信,一种本地的管道(socket) |
socket.AF_INET | IPv4 为TCP/IP协议的默认值,服务器之间的网络通信 |
socket.AF_INET6 | IPv6 |
2.type创建套接字的类型
type参数 | |
socket.SOCK_STREAM | 流式socket,用于TCP时选用 |
socket.SOCK_DGRAM | 数据报式socket,用于UDP时选用 |
socket.SOCK_RAM |
原始套接字,普通的套接字无法处理ICMP , IGMP 等网络报文,而SOCK_RAM可以;其次,SOCK_RAM也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCT套接字选项由用户构造IP头。 |
socket.SOCK_RDM |
是一种可靠的UDP形式,即可保证交付数据但不保证顺序 ,SOCK_RAM用来提供对原始协议的低级访问,在需要某些特殊操作时使用,如发生ICMP报文,SOCK_RAM通常仅限于高级用户或管理员运行的程序使用. |
3.protocol指明所要接收的协议类型,通常设置为0,系统就会根据地址格式和套接类别自动选择一个合适的协议
protocol参数 | |
socket.IPPROTO_TAW |
相当于protocol=255,此时socket只能用来发送IP包,而不能接收任何的数据,发送的数据需要自己填充IP包头,并且自己计算校验和 |
socket.IPPROTO_IP |
相当于protocol=0,此时用于接收任何的IP数据包,其中校验和和协议分析由系统完成 |
二、socket内建方法
服务器端套接字 | |
sk.bind() |
将套接字绑定地址,address地址的格式取决于地址簇,在AF_INET下,以(host.port )的形式表示地址 |
sk.listen(backlog) |
开始监听传入连接,backlog指定在拒绝连接之前,可以挂起的最大连接数量,等待排队的最多排几个,如:backlog等于5,表示内核已经接到了连接要求,但服务器还没有调用accept进行处理的连接个数,这个值不能无限大,因为要在内核中维护连接队列。 |
sk.accept() |
接受TCP客户连接并返回(conn,address),阻塞式等待连接其到来,其中conn是新的套接字对象,可以用来接受和发送数据,address 是客户端的地址。 |
客户端套接字 | |
s.connect() | 主动初始化TCP服务器连接,一般address的格式为元组(hostname,port),如果连接出错,则返回socket.error错误 |
s.connet_ex() | 同上,不过,连接成功是返回0,连接失败是返回错误码,不会抛出异常 |
公共用途套接字 | |
sk.recv(bufsize[,flag]) |
接受套接字的数据。数据以字符串的形式返回,bufsize指定最多可接收的数据(一般为8192),flag提供有关消息的其他信息,通常可以忽略 |
sk.recvform() | 与recv()类似,多用于接受UDP数据,返回值为(data,address),其中data是包含接受数据的字符串,address是发送数据的套接字地址 |
sk.send() | 发送套接字数据,将string中的数据发送到连接的套接字,返回值是要发送的字节数量,该数据可能小于string的字节大小 |
sk.sendall() | 完整发送TCP数据,将string中的数据发送到连接的套接字,但返回之前会尝试一次性发送,就是不断的调用send,发送成功返回None,失败则抛出异常 |
sk.sendto() | 发送UDP数据,将数据发送到套接字,address的形式是(ipaddr,port)的元组,指定远程地址,返回值是发送的字节数 |
sk.close() | 关闭套接字 |
sk.getpeername() | 返回连接套接字的远程地址,返回值通常是元组(ipaddr,port) |
sk.getsockname() | 返回套接字自己的地址,通常是一个元组(ipaddr,port) |
sk.setsockopt() | 设置给定套接字选项的值 |
sk.getsockopt() | 返回条件字选项的值 |
sk.settimeout(timeout) | 设置套接字操作的超时期,timeout是一个浮点数,单位为秒,值为None表示没有超时期,一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
sk.gettimeout() | 返回当前超时期的值,如果没有设置超时期,则返回为None |
sk.fileno() | 返回套接字的文件描述符 |
sk.setblocking(flag) | 是否阻塞(默认为True),如果设置False或0(即非堵塞模式),那么accept和recv时一旦无数据,则报错 |
sk.makefile() | 创建一个与该套接字相关联的文件 |
实例一:你说一句,我说一句
import socket
ip_port = ("127.0.0.1", 1313) sk = socket.socket()
sk.bind(ip_port) # 创建端口
sk.listen(5) # 监听多少个 while True:
print("等待消息。。")
conn,addr = sk.accept() # 生成地址和一个实例线程
while True:
try: # 如果客户端端口,没有消息了,抓取异常,跳出循环
client_data = conn.recv(1024) # 接收消息
print(str(client_data, "utf8"))
except Exception:
print("over")
break server_input = input(">>").strip() # 发送消息
conn.sendall(bytes(server_input,"utf8"))
conn.close()
服务器端
import socket ip_port = ("127.0.0.1",1313) sk = socket.socket() # 实例化
sk.connect(ip_port) # 连接端口
while True:
client_data = input(">>").strip()
sk.sendall(bytes(client_data,"utf8")) # 发送消息
server_data = sk.recv(1024) # 接收消息
print(str(server_data,"utf8"))
sk.close()
客户端
实例二:在Linux上Python3实现客户端发命令,服务器端把客户端命令实现,并发回客户端
#!/usr/bin/python
# -*- coding:utf-8 -*-
import socket
import subprocess
ip_port = ('127.0.0.1',9999)
sk = socket.socket()
sk.bind(ip_port)
sk.listen(5)
while True:
print('server waiting...')
conn,addr = sk.accept()
while True:
client_data = conn.recv(1024)
if not client_data:break # 如果没有客户端发来的消息则退出
print('recv cmd:',str(client_data,'utf8')) # 打印客户端发来了的消息
cmd = str(client_data, 'utf8').strip() cmd_call = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE) # 把客户发来的消息执行结果放在管道里 cmd_result = cmd_call.stdout.read() # 把 cmd 的执行结果读出来
if len(cmd_result) == 0: # 如果结果为 0
cmd_result = b'cmd execution has no output...'
ack_msg = bytes('CMD_RESULT_SIZE|%s' % len(cmd_result), 'utf8') # 文件名和长度发回客户端
conn.send(ack_msg)
client_ack = conn.recv(50) # 向客户端确认消息 (收),用来解决粘包
if client_ack.decode() == "yes":
conn.send(cmd_result) # 确认后,才向客户端发数据
conn.close()
服务器端
#!usr/bin/python3
# -*- coding:utf-8 -*-
import socket ip_port = ('127.0.0.1',9999)
sk = socket.socket()
sk.connect(ip_port) while True:
user_input = input('cmd:').strip()
if len(user_input) == 0:continue
if user_input =='q':break # 按q可以退出 sk.send(bytes(user_input,'utf8')) # 发消息 server_ack_msg = sk.recv(100) # 每次接收服务器端发来的消息最大长度
cmd_res_msg = str(server_ack_msg.decode()).split("|") # 去掉 | ,并转码
print('server response:',cmd_res_msg) # 打印 文件名,总长度
if cmd_res_msg[0] =="CMD_RESULT_SIZE": # 确定 文件名
cmd_res_size = int(cmd_res_msg[1]) # 确定文件长度
sk.send(b"yes") # 发回消息确认,解决粘包 res = '' # 保存 文件内容
received_size = 0
# (在服务器端和客户端中各有一个缓冲区,在一定的时间内客户端接收数据不够500,也会先进行收数据,其他的就下次再收)
while received_size < cmd_res_size: # 如果 文件内容,小于发来的长度
data = sk.recv(500) # 每次接收最大长度 为500 received_size += len(data) # 累加
res += str(data.decode())
else:
print(str(res))
print('---------------------')
sk.close()
客户端
客户端: [root@localhost python_day2018_10_28]# python3 client_1.py
cmd:df
server response: ['CMD_RESULT_SIZE', '']
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/mapper/centos-root 18307072 2056032 16251040 12% /
devtmpfs 490096 0 490096 0% /dev
tmpfs 500664 0 500664 0% /dev/shm
tmpfs 500664 6956 493708 2% /run
tmpfs 500664 0 500664 0% /sys/fs/cgroup
/dev/sda1 508588 111880 396708 22% /boot
tmpfs 100136 0 100136 0% /run/user/0 ---------------------
cmd: 服务器端: [root@localhost python_day2018_10_28]# python3 server.py
server waiting...
recv cmd: df
result