socket

socket

  • 一.前情概要
    • 1 C/S架构
    • 2 Socket
    • 3 套接字分类
  • 二.基于TCP的套接字(Socket)
    • 1 简单示例
    • 2 模拟手机打电话
    • 3 TCP粘包
      • 粘包问题解决方案
      • 基于TCP协议实现ssh功能
  • 三.基于UDP的套接字(Socket)
    • 1 简单示例

一.前情概要

1 C/S架构

C/S架构就是客户端/服务器架构,
其中server端具有以下特点:

  1. 一直提供服务
  2. 有明确的唯一的地址(ip+port),可以让客户端找到

五层网络通信协议由上至下包括:
应用层(数据)
传输层(TCP/IP)
网络层(IP)
数据链路层(以太网)
物理层(二进制)

C/S架构软件是基于网络进行通信的
Client------internet------Server

2 Socket

Socket就是ip+port,
网络通信的本质就是两个应用程序间的通信,通过ip+port就可以在网络中唯一找到1个应用程序。

Socket是应用层与传输层协议通信的中间软件抽象层,
Socket将复杂的TCP/IP协议隐藏在Socket接口中。

socket与TCP协议的关系:socket是对TCP协议、UDP协议...

3 套接字分类

套接字 :源IP地址和目的IP地址以及源端口号和目的端口号的组合称为套接字。其用于标识客户端请求的服务器和服务。
(ps:看样子socket就是套接字)

套接字分为两类:

  1. 基于文件类型的套接字:AF_UNIX 基于文件通信
  2. 基于网络类型的套接字:AF_INET 基于网络通信
    我们要介绍的是基于网络的套接字

二.基于TCP的套接字(Socket)

1 简单示例

模拟服务端与客户端通信:

socket server:

import socket

ip_port = ('127.0.0.1', 8989)

sk = socket.socket()  # 默认TCP协议
sk.bind(ip_port)
sk.listen(5)

while True:
    print('server waiting ...')
    conn, addr = sk.accept()

    client_data = conn.recv(1024)
    print(client_data)
    conn.sendall('whats up,man?')
    
    conn.close()

socket client:

import socket

ip_port = ('127.0.0.1', 8989)

sk = socket.socket()
sk.connect(ip_port)

sk.sendall(b'hello, anybody here?')

server_reply = sk.recv(1024)
print(server_reply)

sk.close()

2 模拟手机打电话

客户端发送数据,服务器异常处理

Socket服务端:

import socket

ip_port = ('127.0.0.1', 8191)

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)   # 参数1:地址簇:IPv4(默认) 参数2:流式socket , for TCP (默认)
phone.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重用ip和端口,解决:服务重启时,会出现Address already in use错误
phone.bind(ip_port)                                         
phone.listen(5)                                             

while True:                                                 # 循环接收连接
    print('starting...')
    conn, addr = phone.accept()

    while True:                                             # 循环接收消息
        try:                                                # 异常捕获方式(windows作为服务器)
            data = conn.recv(1024)
            if not data:                                    # 异常捕获方式(linux作为服务器)
                break
            print('客户端发来的消息:', data)
            conn.send("what's going on?".encode())          # 编码为byte发送消息
        except Exception:
            break
    conn.close()
phone.close()

客户端:

import socket

ip_port = ('127.0.0.1', 8191)

phone = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
phone.connect(ip_port)

while True:
    msg = input('>>:').strip()
    if msg == '':
        continue
    phone.send(msg.encode('utf-8'))

    data = phone.recv(1024)                                 
    print(data)

phone.close()

3 TCP粘包

问题:
tcp是流式协议,会导致粘包问题!

解决:
根据自己定义协议方式解决粘包问题,自定义协议就是将传输数据增加报头

报头要求:
1.固定长度
2.包含对将要发送数据的描述信息

粘包问题解决方案

server要做的事情:

1.报头制作:

# data_s = 数据
# 1.计算数据长度 
data_len = len(data_s)
# 2.形成报头字典 
head_dict = {'data_size':data_len}
# 3.转为json字符串
head_json = json.dumps(head_dict)  # import json
# 4.转为字节byts
head_byts = head_json.encode('utf-8')

2.报头长度:

# 1.报头长度:
head_len = len(head_byts)
# 2.处理二进制数据,按照给定的格式(fmt),把数据封装成固定长度字节
h_l = struct.pack('i',head_len)  #import struct,这里i表示将二进制转为4字节

3.数据发送:

# 1.发送包头长度
conn.send(h_l)
# 2.发送包头
conn.send(head_byts)
# 3.发送数据
conn.send(data)

client要做的事情:

# 1.接收报头长度
h_l = phone.recv(4)
head_len = struct.unpack('i',h_l)[0]  #取元组索引为0的值

# 2.接收报头
head_byts = phone.recv(head_len)
head_json = head_byts.decode('utf-8') #解码
head_dict = json.loads(head_json)
data_len = head_dicr['data_size']

# 3.接收数据
recv_size = 0
data_s = b''
while recv_size <data_len:
    data = phone.recv(1024)
    recv_size += len(data)
    data_s += data 
    
# 最后得到data_s
基于TCP协议实现ssh功能

server:

import socket
import subprocess
import json
import struct

host_ip = '127.0.0.1'
host_port = 8393
ss = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
ss.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
ss.bind((host_ip,host_port))
ss.listen(5)
while True:
    print('start listening...')

    conn, addr = ss.accept()
    print('客户端信息:', conn, '客户端地址:', addr)

    while True:
        try:
            #接收命令
            cmd = conn.recv(1024)
            if not cmd:break
            print('client的命令:',cmd)
            res = subprocess.Popen(cmd,
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)
            out_res = res.stdout.read()
            err_res = res.stderr.read()

            data_len = len(out_res)+len(err_res)
            head_dict = {'data_size':data_len}
            head_json = json.dumps(head_dict)
            head_byts = head_json.encode('utf-8')

            head_len = len(head_byts)
            h_l = struct.pack('i',head_len)

            conn.send(h_l)
            conn.send(head_byts)
            conn.send(out_res)
            conn.send(err_res)
        except Exception:
            break

    conn.close()
    print('-'*60)
ss.close()

client:

import socket
import struct
import json

host_ip = '127.0.0.1'
host_port = 8393
cs = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
cs.connect((host_ip,host_port))

while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    cs.send(cmd.encode('utf8'))

    h_l = cs.recv(4)
    head_len = struct.unpack('i',h_l)[0]

    head_byts = cs.recv(head_len)
    head_json = head_byts.decode('utf-8')
    head_dict = json.loads(head_json)
    data_len = head_dict['data_size']

    recv_size = 0
    data_s = b''
    while recv_size < data_len:
        msg = cs.recv(1024)
        recv_size += len(msg)
        data_s += msg

    print(data_s.decode('utf8'))

cs.close()

三.基于UDP的套接字(Socket)

UDP特点:
用户数据报协议,无连接,面向消息的,自带报头(ps:发空没事,不会粘包问题)

TCP和UDP的不同:
tcp是可靠传输
udp是不可靠传输

1 简单示例

客户端输入,服务端返回大写

server:

import socket

ip_port = ('127.0.0.1', 8080)
udp_server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 数据报式socket , for UDP
udp_server.bind(ip_port)

while True:
    conn, addr = udp_server.recvfrom(1024)
    print(conn, addr)
    udp_server.sendto(conn.upper(), addr)

client:

import socket

ip_port = ('127.0.0.1', 8080)
udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 数据报式socket , for UDP

while True:
    msg = input('>>:').strip()
    udp_client.sendto(msg.encode('utf-8'), (ip_port))
    conn, addr = udp_client.recvfrom(1024)
    print(conn.decode('utf-8'))

原文地址:https://www.cnblogs.com/sunqim16/p/6802443.html