美文网首页
网络编程-socket

网络编程-socket

作者: Snackk | 来源:发表于2018-09-19 15:26 被阅读0次

网络开发的框架

  • C/S
  • B/S 统一了程序的入口 (浏览器)
osi七层模型

TCP(Transmission Control Protocol)可靠的、面向连接的协议(eg:打电话)、传输效率低全双工通信(发送缓存&接收缓存)、面向字节流。使用TCP的应用:Web浏览器;电子邮件、文件传输程序。

UDP(User Datagram Protocol)不可靠的、无连接的服务,传输效率高(发送前时延小),一对一、一对多、多对一、多对多、面向报文,尽最大努力服务,无拥塞控制。使用UDP的应用:域名系统 (DNS);视频流;IP语音(VoIP)。

tcp协议的socket进行通信
  • 对于TCP协议的socket server来说

server端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
sk.listen() #监听链接
conn,addr = sk.accept() #接受客户端链接
ret = conn.recv(1024) #接收客户端信息
print(ret) #打印客户端信息
conn.send(b'hi') #向客户端发送信息
conn.close() #关闭客户端套接字
sk.close() #关闭服务器套接字(可选)

client端
import socket
sk = socket.socket() # 创建客户套接字
sk.connect(('127.0.0.1',8898)) # 尝试连接服务器
sk.send(b'hello!')
ret = sk.recv(1024) # 对话(发送/接收)
print(ret)
sk.close() # 关闭客户套接字

  • 不能同时接受多个client端的连接
server端                                           client端
import socket                                      import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket() #实例化一个对象(买手机)
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)#在bind前加(复用端口,测试用)
sk.bind(('ip地址',端口号)) #(买卡)
sk.listen(数字(1024)) #(开机)以上为启动一个socket服务

conn,addr = sk.accept()  # 三次握手                 sk.connect((服务端ip,服务端口号))
conn.send(bytes类型的内容) #发送消息                  sk.recv(数字)
msg = conn.recv(数字)    #接受消息                   sk.send(bytes的消息)
...打印 操作
conn.close()     #断开与client端的链接(4次挥手)        sk.close()

sk.close()   #关闭服务器端服务


#  server服务器端

import socket
sk = socket.socket()
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sk.bind(('127.0.0.1',10000))
sk.listen()
while True:
    try:
        conn,addr = sk.accept()
        while True:
            msg = input(':')
            conn.send(msg.encode('utf-8'))
            if msg == 'exit':break
            msg = conn.recv(1024)
            if msg == b'exit':break
            print(msg.decode('utf-8'))
        conn.close()
    except UnicodeDecodeError:
        pass
sk.close()

#  client端

import socket
sk = socket.socket()
sk.connect(('127.0.0.1',10000))
while True:
    mag = sk.recv(1024)
    if mag == b'exit':break
    print(mag.decode('utf-8'))
    mag = input(':')
    sk.send(mag.encode('utf-8'))
    if mag == 'exit':break
sk.close()

文件的一次性传送

#client端
import time
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',10000))
sk.send('文件名'.encode('utf-8'))
time.sleep(0.1)
with open('文件绝对路径','rb') as f:
    sk.send(f.read())
sk.close()

#server服务器端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9999))
sk.listen()
conn,addr = sk.accept()
filename = conn.recv(1024)
with open(filename,'wb') as f:
    while True:
        try:
            f1 = conn.recv(1024)
            f.write(f1)
        except Exception:
            break
    conn.close()
sk.close()

  • 文件的分次传送
# 服务器端
import socket
def sky(ip,port):
    i = 0
    count = 1
    sk = socket.socket()
    sk.bind((ip,port))
    sk.listen()
    conn,addr = sk.accept()
    filename = conn.recv(1024)
    butter = int(conn.recv(1024))
    with open(filename,'wb') as f:
        while count:
            try:
                count = conn.recv(butter)
                f.write(count)
                i += 1
                print('\r%s' % i,end = '')
            except Exception:
                break
        conn.close()
    sk.close()

sky('127.0.0.1',9999)

# client 端
import os
import time
import socket
def use(filename,ip,port,butter = 1024):
    sk = socket.socket()
    sk.connect((ip,port))
    sk.send(os.path.basename(filename).encode('utf-8'))
    time.sleep(0.01)
    sk.send(str(butter).encode('utf-8'))
    time.sleep(0.01)
    file_size = os.path.getsize(filename)
    with open(filename,'rb') as f:
        while file_size:
            count = f.read(butter)
            sk.send(count)
            time.sleep(0.0000001)
            file_size -= len(count)
    sk.close()
use(r'D:\BaiduNetdiskDownload\day17\项目架构讲解.mp4','127.0.0.1',9999,102400)
udp协议的socket进行通信
  • 基本结构

server端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) #创建一个服务器的套接字
udp_sk.bind(('127.0.0.1',9000)) #绑定服务器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr) # 对话(接收与发送)
udp_sk.close() # 关闭服务器套接字

client端
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)

import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('ip地址',端口号))
对方发给你的消息,对方的地址 = udp_sk.recvfrom(接受的字节数)
sk.sendto(b'要发给对方的消息',对方的地址)
sk.close()
  • low版
# 服务端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9999))
mag,addr = sk.recvfrom(1024)
print(mag.decode('utf-8'),end = '')
print(addr)
sk.sendto('你好'.encode('utf-8'),addr)
sk.close()

# 客户端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.sendto('你好'.encode('utf-8'),('127.0.0.1',9999))
mag,addr = sk.recvfrom(1024)
print(mag.decode('utf-8'),end='')
print(addr)
sk.close()
  • 还是low版
# 服务端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
sk.bind(('127.0.0.1',9999))

while True:
    mag,addr = sk.recvfrom(1024)
    print(mag.decode('utf-8'))
    mag = input('>>:')
    sk.sendto(mag.encode('utf-8'),addr)
sk.close()

# 客户端
import socket
sk = socket.socket(type = socket.SOCK_DGRAM)
while True:
    mag = input('>>:')
    sk.sendto(mag.encode('utf-8'),('127.0.0.1',9999))
    mag,addr = sk.recvfrom(1024)
    print(mag.decode('utf-8'))
sk.close()
  • 粘包现象 -->优化方法(Nagle算法)
  • 只有TCP有粘包现象,UDP永远不会粘包

由于流式传输的特点 产生了数据连续发送的粘包现象
在一个conn建立起来的连接上传输的多条数据是没有边界的
数据的发送和接收实际上不是在执行send/recv的时候就立刻被发送或者接收
而是需要经过操作系统内核

tcp: 面向流的通信是无消息保护边界的,对于空消息:tcp是基于数据流的,于是收发的消息不能为空

Nagle算法 能够将发送间隔时间得很近的短数据合成一个包发送到接收端

拆包机制 ,当要发送的数据超过了网络上能传输的最大长度,就会被tcp协议强制拆包

udp:是无连接的,面向消息,基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y)

如果是短数据 :只需要告诉对方边界就可以了
如果是长数据 :不仅要告诉对方边界,还要保证对面完整的接受了

  • udp协议中是不会发生粘包现象的
    适合短数据的发送
    不建议你发送过长的数据
    会增大你数据丢失的几率

  • 会发生黏包的两种情况

    • 1,发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
# 服务端
import socket
ip_port=('127.0.0.1',8081)
sk = socket.socket()
sk.bind(ip_port)
sk.listen()
conn,addr = sk.accept()
data1=conn.recv(10)
data2=conn.recv(10)
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
conn.close()
# 客户端
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8081)
sk = socket.socket()
sk.connect(ip_port)
sk.send('hello'.encode('utf-8'))
sk.send('egg'.encode('utf-8'))
  • 2 接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
# 客户端
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 egg'.encode('utf-8'))
# 服务端
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)  #最多接受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()
  • 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
  • 使用struct解决黏包
  • 该模块可以把一个类型,如数字,转成固定长度的bytes
  • 借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。因此可以利用这个特点来预先发送数据长度。

struct.pack('i',1111111111111)
struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围
i int 型
l long 型
f folat型

  • struct实现结构
import struct
ret = struct.pack('i',1023800976)
print(ret,len(ret))
num = struct.unpack('i',ret)
print(num[0])# num是一个元祖,应该取缔一个元素
  • 实现基本结构
# 客户端
import socket
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',9999))
mag = '借助struct模块,我们知道长度数字可以被转换成一个标准大小的4字节数字。'.encode('utf-8')
num = struct.pack('i',len(mag))
sk.send(num)
sk.send(mag)
sk.close()

# 服务端
import socket
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',9999))
sk.listen(3)
conn,addr = sk.accept()
num = conn.recv(4)
num = struct.unpack('i',num)[0]
mag = conn.recv(num).decode('utf-8')
print(mag)
conn.close()
sk.close()

服务端套接字函数
s.bind() 绑定(主机,端口号)到套接字
s.listen() 开始TCP监听
s.accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

客户端套接字函数
s.connect() 主动初始化TCP服务器连接
s.connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

公共用途的套接字函数
s.recv() 接收TCP数据
s.send() 发送TCP数据
s.sendall() 发送TCP数据
s.recvfrom() 接收UDP数据
s.sendto() 发送UDP数据
s.getpeername() 连接到当前套接字的远端的地址
s.getsockname() 当前套接字的地址
s.getsockopt() 返回指定套接字的参数
s.setsockopt() 设置指定套接字的参数
s.close() 关闭套接字

面向锁的套接字方法
s.setblocking() 设置套接字的阻塞与非阻塞模式 ,默认True(阻塞) 设置成False就不阻塞了,没人连就会报错(用try-excpet解决)
s.settimeout() 设置阻塞套接字操作的超时时间
s.gettimeout() 得到阻塞套接字操作的超时时间

面向文件的套接字的函数
s.fileno() 套接字的文件描述符
s.makefile() 创建一个与该套接字相关的文件

验证客户端链接的合法性
# 服务端
import os
import hmac
import socket
def auth(conn):
    msg = os.urandom(32)  # 生成一个随机的32位字节字符串
    conn.send(msg)  # # 发送到client端
    result = hmac.new(secret_key, msg)  # 处理这个随机字符串,得到一个结果
    client_digest = conn.recv(1024)  # 接收client端处理的结果
    if result.hexdigest() == client_digest.decode('utf-8'):
        print('是合法的连接')  # 对比成功可以继续通信
        return True
    else:
        print('不合法的连接')  # 不成功 close
        return False

secret_key = b'alex_sb'
sk = socket.socket()
sk.bind(('127.0.0.1',9000))
sk.listen()
conn,addr = sk.accept()
if auth(conn):
    print(conn.recv(1024))
    # 正常的和client端进行沟通了
    conn.close()
else:
    conn.close()
sk.close()

# 客户端
import hmac
import socket
def auth(sk):
    msg = sk.recv(32)
    result = hmac.new(key, msg)
    res = result.hexdigest()
    sk.send(res.encode('utf-8'))

key = b'alex_s'
sk = socket.socket()
sk.connect(('127.0.0.1',9000))
auth(sk)
sk.send(b'upload')
# 进行其他正常的和server端的沟通
sk.close()

  • socketserver模块
import socketserver
# tcp协议的server端就不需要导入socket,socketserver中导入了socket模块
class Myserver(socketserver.BaseRequestHandler):
    def handle(self):
        conn = self.request   #self.data = self.request.recv(1024).strip()
        while True:
            conn.send(b'hello')
            print(conn.recv(1024))

server = socketserver.ThreadingTCPServer(('127.0.0.1',9000),Myserver)
server.serve_forever()

# 这个类不用实例化也不用调用类中的handle方法

相关文章

  • 许世伟的Go语言基础 第五章总结

    第5章 网络编程 5.1 socket编程 以往socket编程: 建立socket:使用socket()函数。 ...

  • 网络编程

    python学习笔记-网络编程 socket编程: socket()函数:socket.socket([famil...

  • 网络编程

    网络 Socket 基于TCP协议的Socket编程 基于UDP协议的Socket编程

  • 2018-09-12 day18-网络编程和http请求

    网络编程 socket 网络编程就是socket编程,socket就是套接字,就是进行数据通信的两端(服务器和客户...

  • 网络基础介绍

    网络编程的两种 TCP socket编程,是网络编程的主流。之所以叫Tcp socket编程,是因为底层是基于Tc...

  • Go语言的Socket编程

    我们在日常开发当中,几乎所有网络编程都是Socket编程,因为大部分底层网络的编程都离不开Socket编程。 什么...

  • 动脑学院架构篇-Java Socket编程基础及深入讲解

    【Socket】Java Socket编程基础及深入讲解 Socket是Java网络编程的基础,了解还是有好处的,...

  • Python网络编程

    Python网络编程 1、socket编程, 类:socket 1.server端 # socket第一个参数:地...

  • TCP通信网络编程

    1. Socket网络编程 Socket是网络编程的一个抽象概念。通常我们用一个Socket表示“打开了一个网络链...

  • Netty

    一、网络编程基础原理 1 网络编程(Socket)概念 首先注意,Socket不是Java中独有的概念,而是一个语...

网友评论

      本文标题:网络编程-socket

      本文链接:https://www.haomeiwen.com/subject/lpyinftx.html