1、简单的套接字通信

1.1、服务端

import socket

# 买手机
phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 插卡
phone.bind(('127.0.0.1', 8080))
# 开机
phone.listen(5)
# 等电话链接
print('server start...')
conn, client_addr = phone.accept() # (tcp链接,client_addr)
print('链接:', conn)
print(client_addr) # 基于建立的链接,收发消息
client_data = conn.recv(1024)
print('客户端的消息', client_data)
conn.send(client_data.upper()) # 挂电话链接
conn.close() # 关机
phone.close()

1.2、客户端 

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080)) phone.send('hello'.encode('utf-8'))
server_data = phone.recv(1024)
print('服务端回应的消息', server_data) phone.close()

2、加上通信循环的套接字

2.1、服务端

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
phone.listen(5)
print('server start...')
conn,client_addr=phone.accept() while True: #通讯循环
client_data=conn.recv(1024)
# print('has rev')
conn.send(client_data.upper()) conn.close() phone.close()

2.2、客户端  

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
# print('====>has send')
server_data=phone.recv(1024)
# print('====>has recv')
print(server_data.decode('utf-8')) phone.close()

3、加上链接的通信循环

  问题:同一时间内,只能建立一条TCP链接,其它链接会被挂起

3.1、服务端

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 就是它,在bind前加
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('server start...')
while True: # 链接循环
conn, client_addr = phone.accept()
print(conn, client_addr) while True: # 通讯循环
try:
client_data = conn.recv(1024)
if not client_data: break # 针对linux系统
# print('has rev')
conn.send(client_data.upper())
except Exception: # 针对windwos
break
conn.close()

3.2、客户端

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
# print('====>has send')
server_data=phone.recv(1024)
# print('====>has recv')
print(server_data.decode('utf-8')) phone.close()

客户端1

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
# print('====>has send')
server_data=phone.recv(1024)
# print('====>has recv')
print(server_data.decode('utf-8')) phone.close()

客户端2

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
# print('====>has send')
server_data=phone.recv(1024)
# print('====>has recv')
print(server_data.decode('utf-8')) phone.close()

客户端3

4、模拟SSH远程执行命令 

注意:由于接受发送只能是1024,会出现粘包的问题

4.1、服务端

import socket
import subprocess phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 就是它,在bind前加
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('server start...')
while True: # 链接循环
conn, client_addr = phone.accept()
print(conn, client_addr) while True: # 通讯循环
try:
cmd = conn.recv(1024)
if not cmd: break # 执行命令,拿到结果
res = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stdout = res.stdout.read() #命令执行正确结果
stderr = res.stderr.read() #命令执行错误结果 conn.send(stdout + stderr) #回给客户端的执行结果内容,保存在管道里的内容是bytes格式的,直接可以发送给客户端
except Exception: # 针对windwos
break
conn.close() phone.close()

4.2、客户端  

import socket

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080)) while True:
cmd = input('>>: ').strip()
if not cmd: continue
# 发命令
phone.send(cmd.encode('utf-8')) # 收命令的执行结果
cmd_res = phone.recv(1024) # 打印结果
print(cmd_res.decode('gbk')) phone.close()

5、什么是粘包  

须知:只有TCP有粘包现象,UDP永远不会粘包,为何?

首先需要掌握一个socket收发消息的原理

发送端可以是一K一K地发送数据,而接收端的应用程序可以两K两K地提走数据,当然也有可能一次提走3K或6K数据,或者一次只提走几个字节的数据,也就是说,应用程序所看到的数据是一个整体,或说是一个流(stream),一条消息有多少字节对应用程序是不可见的,因此TCP协议是面向流的协议,这也是容易出现粘包问题的原因。而UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据,这一点和TCP是很不同的。怎样定义消息呢?可以认为对方一次性write/send的数据为一个消息,需要明白的是当对方send一条信息的时候,无论底层怎样分段分片,TCP协议层会把构成整条消息的数据段排序完成后才呈现在内核缓冲区。

例如基于tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束

所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

此外,发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一个TCP段。若连续几次需要send的数据都很少,通常TCP会根据优化算法把这些数据合成一个TCP段后一次发送出去,这样接收方就收到了粘包数据。

  1. TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
  2. UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
  3. tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头,实验略

udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠

tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

两种情况下会发生粘包。

发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(10)
data2=conn.recv(10) print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8')) conn.close() 服务端

服务端

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port) s.send('hello'.encode('utf-8'))
s.send('feng'.encode('utf-8')) 客户端

客户端

接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
ip_port=('127.0.0.1',8080) tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5) conn,addr=tcp_socket_server.accept() data1=conn.recv(2) #一次没有收完整
data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的 print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8')) conn.close() 服务端

服务端

#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080) s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port) s.send('hello feng'.encode('utf-8')) 客户端

客户端

6、解决粘包问题(模拟ssh)+包头定制

6.1、服务端

import socket
import struct #struct模块用来把数字打成固定的长度,struct.pack('i',1231),i表示int类型四个字节
import subprocess
import json '''
struct的用法
>>> import struct
>>> struct.pack('i',231231)
b'?\x87\x03\x00'
>>> res
KeyboardInterrupt
>>> res=struct.pack('i',231231)
>>> len(res)
4
>>> struct.unpack('i',res)
(231231,)
>>> struct.unpack('i',res)[0]
231231
>>> ''' phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 就是它,在bind前加
phone.bind(('127.0.0.1', 8080))
phone.listen(5)
print('server start...')
while True: # 链接循环
conn, client_addr = phone.accept()
print(conn, client_addr) while True: # 通讯循环
try:
cmd = conn.recv(1024)
if not cmd: break # 执行命令,拿到结果
res = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stdout = res.stdout.read()
stderr = res.stderr.read() # 制作报头
header_dic = {'total_size': len(stdout) + len(stderr), 'md5': None}
header_json = json.dumps(header_dic)
header_bytes = header_json.encode('utf-8') # 1 先发报头的长度(固定4个bytes)
conn.send(struct.pack('i', len(header_bytes))) # 2 先发报头
conn.send(header_bytes) # 3 再发真实的数据
conn.send(stdout)
conn.send(stderr) except Exception: # 针对windwos
break
conn.close() phone.close() 

6.2、客户端  

import socket
import struct
import json phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(('127.0.0.1', 8080)) while True:
cmd = input('>>: ').strip()
if not cmd: continue
# 发命令
phone.send(cmd.encode('utf-8')) # 先收报头的长度
struct_res = phone.recv(4)
header_size = struct.unpack('i', struct_res)[0] # 再收报头
header_bytes = phone.recv(header_size)
head_json = header_bytes.decode('utf-8')
head_dic = json.loads(head_json) total_size = head_dic['total_size']
# 再收命令的执行结果
recv_size = 0
data = b''
while recv_size < total_size:
recv_data = phone.recv(1024)
recv_size += len(recv_data)
data += recv_data # 打印结果
print(data.decode('gbk')) phone.close()

6.3、关于struct的详细用法

import json,struct
#假设通过客户端上传1T:1073741824000的文件a.txt #为避免粘包,必须自定制报头
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值 #为了该报头能传送,需要序列化并且转为bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输 #为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节
head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度 #客户端开始发送
conn.send(head_len_bytes) #先发报头的长度,4个bytes
conn.send(head_bytes) #再发报头的字节格式
conn.sendall(文件内容) #然后发真实内容的字节格式 #服务端开始接收
head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式
x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度 head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式
header=json.loads(json.dumps(header)) #提取报头 #最后根据报头的内容提取真实的数据,比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)
 #_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
__author__ = 'Linhaifeng'
import struct
import binascii
import ctypes values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI') print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t) s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2) print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size)) s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0)) 关于struct的详细用法

关于struct的详细用法

7、socketserver实现并发

7.1、服务端 

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
server_data=phone.recv(1024)
print(server_data.decode('utf-8')) phone.close()

7.2、客户端  

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
server_data=phone.recv(1024)
print(server_data.decode('utf-8')) phone.close()

客户端1

 import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
server_data=phone.recv(1024)
print(server_data.decode('utf-8')) phone.close()

客户端2

import socket
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8080)) while True:
msg=input('>>: ').strip()
if not msg:continue
phone.send(msg.encode('utf-8'))
server_data=phone.recv(1024)
print(server_data.decode('utf-8')) phone.close()

客户端3

 

  

day8_python网络编程(实验版本)的更多相关文章

  1. 20145208 实验五 Java网络编程

    20145208 实验五 Java网络编程 实验内容 1.用书上的TCP代码,实现服务器与客户端. 2.客户端与服务器连接 3.客户端中输入明文,利用DES算法加密,DES的秘钥用RSA公钥密码中服务 ...

  2. 20145325张梓靖 实验五 "JAVA的网络编程"

    20145325张梓靖 实验五 "JAVA的网络编程" 实验内容 使用 JVAV语言 进行网络编程 对明文进行加密 设计过程 我完成的是客户端,服务端同伴 20145308刘昊阳 ...

  3. 20145220 实验五 Java网络编程

    20145220 实验五 Java网络编程 实验内容 1.用书上的TCP代码,实现服务器与客户端. 2.客户端与服务器连接 3.客户端中输入明文,利用DES算法加密,DES的秘钥用RSA公钥密码中服务 ...

  4. 20145122《 Java网络编程》实验五实验报告

    实验名称 Java网络编程 实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 结对小伙伴 20145120黄玄曦 博客地址:http://www.cnblo ...

  5. 实验五 Java网络编程

    实验五 Java网络编程 实验五 Java网络编程 实验五所涉及的密码学算法及编程思路 ## Java对称加密-DES算法 (1) 获取密钥生成器 KeyGenerator kg=KeyGenerat ...

  6. Java课程实验报告 实验四 Java网络编程及安全

    北京电子科技学院(BESTI) 实     验    报     告 课程:Java程序设计     班级:1352     姓名:吕松鸿  学号:20135229 成绩:               ...

  7. 20145301实验五 Java网络编程及安全

    北京电子科技学院(BESTI)实验报告 课程:Java程序设计 班级:1453 指导教师:娄嘉鹏 实验日期:2016.05.06 18:30-21:30 实验名称:实验五 Java网络编程 实验内容 ...

  8. 20145327实验五 Java网络编程

    实验五 Java网络编程 实验内容 1.掌握Socket程序的编写: 2.掌握密码技术的使用: 3.设计安全传输系统. 实验步骤 基于Java Socket实现安全传输 基于TCP实现客户端和服务器, ...

  9. 20145331实验五 Java网络编程及安全

    20145331魏澍琛 实验五 Java网络编程 实验内容 1.用书上的TCP代码,实现服务器与客户端. 2.客户端与服务器连接 3.客户端中输入明文,利用DES算法加密,DES的秘钥用RSA公钥密码 ...

随机推荐

  1. 沙雕去死吧! 劳资终于弄好了toimcat9 apt和ssl配置

    搜一下吧,要不就是不用apr模式,也不用nio模式,直接用bio模式跑的,麻烦问一下,您们都是傻屌吗? APR模式必须要用!!!!! 然后全他妈的用jks证书,然后再提取公钥*.cer,然后再转换为* ...

  2. 大半夜吃饱了撑的帮人调IE玩

      那高手的也是IE6,我也是IE6,但是他的IE6就总是进recv,我的IE6就进WSARecv,一点都不科学...擦..不调了.

  3. Android——app基础

    Android Application基础 系统启动过程 APK文件介绍 APK是Android Package的缩写,即android安装包.APK 文件其实是zip 格式,但后缀名被修改为apk  ...

  4. Bundler和Minifier Visual Studio扩展

    原文地址:https://marketplace.visualstudio.com/items?itemName=MadsKristensen.BundlerMinifier 特征 将CSS,Java ...

  5. WEB性能测试用例设计

    性能测试用例主要分为预期目标用户测试,用户并发测试,疲劳强度与大数据量测试,网络性能测试,服务器性能测试五大部分,具体编写测试用例时要根据实际情况进行裁减,在项目应用中遵守低成本,策略为中心,裁减,完 ...

  6. FastAdmin 使用 phpmail 出现 spl_autoload_register 错误

    FastAdmin 使用 phpmail 出现 spl_autoload_register 错误 现象 意思是 __autoload() 已经废弃 问题来源于:https://ask.fastadmi ...

  7. 关于PHP学习--摘自知乎

    主要是学框架(其实也没啥可学的).数据库.服务器.linux. 所以我推荐apache/nginx文档,框架的文档,mysql的文档,linux使用说明,等等等等. PHP: PHP 手册MySQL ...

  8. PHP 从 MongoDb 中查询数据怎么样实现

    一.软件环境(版本非必须) php v5.6 扩展:MongoDB nginx v1.11 mongodb v3.2 note: 必须安装MongoDB扩展 二.连接 $client = new Mo ...

  9. 深入浅出Cocoa之消息(二)-详解动态方法决议(Dynamic Method Resolution) 【转】

    序言 如果我们在 Objective C 中向一个对象发送它无法处理的消息,会出现什么情况呢?根据前文<深入浅出Cocoa之消息>的介绍,我们知道发送消息是通过 objc_send(id, ...

  10. C#创建日志方法

    1.通过流的方式 public void WriteLog(string log) { StreamWriter stream; string path = "D:/WriteLog/&qu ...