python网络编程入门

一、什么是socket?

socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。
在设计模式中,socket其实就是一个门面模式,他把复杂的TCP/IP协议族隐藏
在socket接口后面。对用户来说,一组简单的接口就是全部,让socket
取组织数据,以符合指定的协议

所以,我们无需深入理解tcp/udp协议,socket已经为我们封装好了,
我们只需要遵循socket的规定取编程,写出的程序自然就是遵循
tcp/udp标准的。

也有人将socket说成是ip+port,
ip用来标识互联网中的一台主机的位置,
而port是用来标识这台机器上的一个应用程序,
ip与port的绑定就标识了互联网中独一无二的一个应用程序

##二、套接字的分类
起初,套接字被设计用在同一台主机上多个应用程序之间
多个程序之间的通讯。这也被称进程间的通讯,或IPC。
套接字有两种(或者称为有两个种族),分别是基于文件型
的和基于网络型的。

1.基于文件类型的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆文件,基于文件的套机字调用的就是底层的文件
系统来取数据,两个套接字进程运行在同一机器,可以通过
访问同一个文件系统间接完成通信

2.基于网络类型的套接字家族

套接字家族的名字:AF_INEF

(还有AF_INET6被用于ipv6,还有一些其他的地址家族,不过
他们要么是只用于某个平台,要么就是已经被废弃,或者是很少
被使用,或者是根本没有实现,所有地址家族中,AF_INET是
使用最广泛的一个,python支持很多种地址家族,但是由于我们
只关心网络编程,所以大部分时候我们只使用AF_INET)

三、基于tcp协议的套接字编程

	服务端:
import socket

# 第一个参数:基于网络的套接字  第二个参数:流的方式
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.bind(('127.0.0.1',8000))

phone.listen(5)#允许连接数
print('===============')
conn,addr = phone.accept()# (本质是在进行三次握手) 

msg = conn.recv(1024)#接收信息的字节大小
print('客户端发来的消息是',msg)

#发消息
conn.send(msg)

conn.close()#
phone.close()

客户端:

import socket

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

phone.connect(('127.0.0.1',8000))#(本质是在进行三次握手)  

phone.send('hello'.encode('utf-8'))#发消息

data = phone.recv(1024)
print('收到服务端发来的消息:',data)

上面就是一个简单的基于tcp协议的套接字编程,服务端只能接收一个客户端的连接,服务端和客户端通信完成后,直接断开连接,服务端和客户端都关闭

2.客户端和服务端循环收发消息:

服务端:

# import socket
from socket import *

ip_port = ('127.0.0.1',8081)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)

print('服务端开始运行了')
conn,addr = tcp_server.accept()#
print('双向连接是',conn)
print('客户端地址',addr) #ip地址加这个进程的端口号

while True:
    data = conn.recv(buffer_size)
    print('客户端发来的消息是',data.decode('utf-8'))
    conn.send(data.upper())

conn.close()
tcp_server.close()

客户端:

# import socket
from socket import *

ip_port = ('127.0.0.1',8081)
back_log = 5
buffer_size = 1024

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

while True:
    msg = input('>>:').strip()
    tcp_client.send(msg.encode('utf-8'))
    print('客户端已经发送消息')
    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))

tcp_client.close()

注意:这里的服务端虽然可以和客户端循环发送消息,但还是只能连接一个客户端,且两端都不会断掉。

3.socket收发消息原理剖析:

服务端:

# import socket
from socket import *

ip_port = ('127.0.0.1',8084)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)

print('服务端开始运行了')
conn,addr = tcp_server.accept()#
print('双向连接是',conn)
print('客户端地址',addr)

while True:
    data = conn.recv(buffer_size)
    print('客户端发来的消息是',data.decode('utf-8'))
    # 服务端   当发送的消息,即msg为空的时候,recv这里会卡住(卡在了自己的内核态内存)

    #socket服务端和客户端的程序都是在应用层的,而且他们都是存在各自的用户态内存的,
    # 而他们发送send和接收recv的消息(想发送消息必须要操作网卡,而只有操作系统才能操作网卡)
    # 都是在各自的内核态内存的(socket发送消息,先把数据包发
    # 到自己的内核态内存,由操作系统调用网卡和数据包发送给对方,接收消息的时候也是在
    # 自己的内核态内存中取数据包)。

    #当客户端发送的消息为空时,先把这个空发送到自己的
    #内核态内存,然后操作系统检测到你发送的消息是空,
    #所以就不会把消息发送给服务端,故而就造成了卡住了

    #
    conn.send(data.upper())

conn.close()
tcp_server.close()

客户端:

# import socket
from socket import *

ip_port = ('127.0.0.1',8084)
back_log = 5
buffer_size = 1024

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

while True:
    msg = input('>>:').strip()
    #因为输入空  会在recv卡住,所以要进行空判断
    # if not msg:
    #     continue
    tcp_client.send(msg.encode('utf-8'))
    print('客户端已经发送消息')
    # 客户端   当发送的消息,即msg为空的时候,recv这里会卡住(卡在了自己的内核态内存)
    data = tcp_client.recv(buffer_size)
    print('收到服务端发来的消息',data.decode('utf-8'))

tcp_client.close()

4.基于UDP的套接字

服务端:

from socket import *

ip_port = ('127.0.0.1',8082)
buffer_size = 1024

udp_server = socket(AF_INET,SOCK_DGRAM)#第二个参数 代表数据报式的套接字
udp_server.bind(ip_port)

while True:
    data,addr = udp_server.recvfrom(buffer_size)
    print(data.decode('utf-8'))

    udp_server.sendto(data.upper(),addr)

客户端:

'''
recv在自己这段的缓冲区为空时,阻塞
recvfrom在自己这段的缓冲区为空时,就收一个空

tcp是基于数据流的,于是收发消息不能为空,这就需要在
客户端和服务端都添加空消息的处理机制,防止程序卡住,
而udp是基于数据报的,即使是你输入的是空内容(直接
回车),那也不是空消息,udp协议会帮你封装上消息头

'''

from socket import *

ip_port = ('127.0.0.1',8082)
buffer_size = 1024

udp_client = socket(AF_INET,SOCK_DGRAM)#第二个参数 代表数据报式的套接字

while True:
    msg = input('>>').strip()
    udp_client.sendto(msg.encode('utf-8'),ip_port)

    data,addr = udp_client.recvfrom(buffer_size)
    print(data.decode('utf-8'))

5.基于tcp的粘包问题:

服务端:

from socket import *
ip_port = ('127.0.0.1',8084)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)

conn,addr = tcp_server.accept()


data1 = conn.recv(5)#指定一次接收的字节,就解决了粘包的问题

#tcp是面向连接的,有优化算法,一次接收不完,会接着继续接收

#但是udp不是面向连接的,没有优化算法,

print('第一次数据',data1)

data2 = conn.recv(5)
print('第二次数据',data2)

data3 = conn.recv(5)
print('第三次数据',data3)

客户端:

'''
数据量小的粘包
'''

from socket import *
ip_port = ('127.0.0.1',8084)
back_log = 5
buffer_size = 1024

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

#套接字不是一个发对应一个收(服务端和客户端有自己的缓冲区)
tcp_client.send('helloworldchuan'.encode('utf-8'))

6.解决粘包的方法:

'''
#

'''

'''
粘包解决方案


由于应用程序自己发送的数据可以进行打包处理,自己制作协议,对数据进行封装添
加报头,然后发送数据部分。而报头必须是固定长度,对方接受时可以先接受报头,对
报头进行解析,然后根据报头内的封装的数据的长度对数据进行读取,这样收取的数据
就是一个完整的数据包

'''


#解决粘包版本
from socket import *
import subprocess
import struct

ip_port = ('127.0.0.1',8086)
back_log = 5
buffer_size = 1024

tcp_server = socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)

while True:
    conn,addr = tcp_server.accept()
    print('新的客户端链接',addr)
    while True:
        # 客户端的conn.recv和服务端的tcp_client.send是三次握手的过程
        cmd = conn.recv(buffer_size)  #
        if not cmd:
            break
        print('收到客户端的命令', cmd)

        res = subprocess.Popen(cmd.decode('utf-8'), shell=True,
                               stderr=subprocess.PIPE,
                               stdout=subprocess.PIPE,
                               stdin=subprocess.PIPE)

        err = res.stderr.read()
        if err:
            cmd_res = err
        else:
            cmd_res = res.stdout.read()

        # 发


        length = len(cmd_res)

        data_length = struct.pack('i',length)
        conn.send(data_length)
        conn.send(cmd_res)


    conn.close()

'''


'''
'''
粘包解决方案


由于应用程序自己发送的数据可以进行打包处理,自己制作协议,对数据进
行封装添加报头,然后发送数据部分。而报头必须是固定长度,对方接受时
可以先接受报头,对报头进行解析,然后根据报头内的封装的数据的长度对
数据进行读取,这样收取的数据就是一个完整的数据包

'''
from socket import *
import struct
ip_port = ('127.0.0.1',8086)
back_log = 5
buffer_size = 1024

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

while True:
    cmd = input('>>:').strip()
    if not cmd:continue
    if cmd == 'quit':break

    tcp_client.send(cmd.encode('utf-8'))


    #解决粘包的位置

    #接收报头
    length_data = tcp_client.recv(4)
    #对报头解压
    length = struct.unpack('i',length_data)[0]


    recv_size = 0
    recv_msg = b''
    while recv_size < length:
        #写法一
        # r_m = tcp_client.recv(buffer_size)
        # recv_msg+=r_m
        # recv_size+=len(r_m)
        #写法二
        recv_msg += tcp_client.recv(buffer_size)
        recv_size = len(recv_msg)

    print('命令的执行结果是',recv_msg.decode('gbk'))

tcp_client.close()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值