1.什么是socket

了解socket之前首先要了解osi七层模型,或者说五层模型:应用层,传输层,网络层,数据链路层,物理层

Python之socket网络编程

  其中,数据一定是转化成物理信号通过物理层的物理设备才能传输;但是我们不谈物理设备,我们今天的重点是 :网络上的两个程序怎么通过一个连接实现数据交换。怎么唯一标示一个程序呢,是通过ip+端口的方式。socket就是用来描述ip和端口的,可以理解socket为把tcp,udp协议封装成一组接口,我们不需要知道复杂的tcp/udp协议,只需要按照socket的规定编程,写出的程序自然就是遵循tcp/udp协议的。

  socket又被称为套接字,我们研究套接字只需要记住两件事:建立连接,收发数据,就行了在写代码之前我们还需要知道一个c/s模型,就是client/sever(客户端与服务端),交换数据至少得两个人吧,在网络编程中就是客户端和服务端。客户端向服务端发送请求,服务端响应客户端的请求。

我们先写一个简单的基于tcp的socket程序:

#服务端
import socket

phone_sever=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone_sever.bind(('127.0.0.1',8080))#绑定ip和端口,我们用的本地回环网卡演示
phone_sever.listen(5)#监听,等待连接

conn,addr=phone_sever.accept() #客户端连入
ret=conn.recv(1024)#接收客户端发来的消息
conn.close()#断开连接
phone_sever.close()#服务端关闭
#客户端
import socket

phone_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone_client.connect(('127.0.0.1',8080))#连接服务端的ip和端口
phone_client.send('hello'.encode('utf-8'))#向服务端发送消息
phone_client.close()#客户端关闭

写好后,先运行服务端,在运行客户端,这样就成功写了一个socket。这是最简单的socket例子了,只是介绍了基本语法,我们下面优化一下代码,

注意:传输的数据都应该是bytes类型

2.基于TCP的套接字

我们基于上面的例子,优化一下代码

  1.服务端应该是循环接收数据,并能循环发送数据

  2.客户端应该也能接收服务端的消息,并且也是循环收发

  3.服务端可以与多个客户端连接

  4.某一个客户端断开连接或者发生故障不能导致服务端崩溃

  5.顺便模拟一下客户端输入命令,服务端把命令的执行结果返回给客户端

  6.解决OSError:[Errno 48] Address already in use的问题

#服务端
import subprocess

import socket
phone_sever=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机
phone_sever.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)#解决问题6,加上这句话就行
phone_sever.bind(('127.0.0.1',8080))#这里是元组形式
phone_sever.listen(5)
print('sever run ')
while True:#这个循环解决问题3
    conn,client_addr=phone_sever.accept()   
    print('客户端',client_addr)
    while True:#这个while循环解决问题1
        try:#捕捉异常,解决问题4
            cmd=conn.recv(1024)#收消息
            res=subprocess.Popen(cmd.decode('utf-8'),
                                shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)#利用subprocess这个模块,解决问题5
            stdout=res.stdout.read()
            stderr=res.stderr.read()
            sever_res=stdout+stderr
            if not sever_res:
                sever_res=b'is vaild'
            conn.sendall(sever_res)#发消息
        except Exception:
            break
    conn.close()#挂电话

phone_sever.close()#关机
#客户端
import
socket phone_client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机 phone_client.connect(('127.0.0.1',8080))#拨号 # phone_client.connect(('192.168.16.253',8080))#拨号 while True:#这个循环解决问题2 msg=input('>>>>:') if not msg:continue#解决客户端输入空,服务端会死掉的问题 phone_client.send(msg.encode('utf-8')) server_res=phone_client.recv(1024) print('server_res:',server_res.decode('gbk')) phone_client.close()

当我们在实验的时候,可能会遇到这种情况:客户端输入为空,服务端就卡住了。解释这个问题就要说到网络传输的原理了

我们前面说了,两台机器之间传输数据一定是通过底层网卡等物理设备的,而应用程序是不能操作底层硬件的,这时就需要调用操作系统了,当用send发数据时,其实是由应用程序把数据发给操作系统,然后由操作系统把数据放到一块缓存上,然后由这块缓存再将数据发送给服务端的缓存,如果我们send的是一个空数据,那肯定就不会发送成功了,但是我们的客户端以为按了回车就发送了,就开始等待服务端的回应了,所以就卡住了。

3.基于UDP的套接字

#服务端
import
socket udp_sever=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_sever.bind(('127.0.0.1',8080)) while True: msg,addr=udp_sever.recvfrom(1024) print(msg,addr) udp_sever.sendto(msg.upper(),addr)
#客户端
import socket

ip_port=('127.0.0.1',8080)
udp_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
    msg=input('>>>>>>:').strip()
    if not msg:continue
    udp_client.sendto(msg.encode('utf-8'),ip_port)
    back_msg,addr=udp_client.recvfrom(1024)
    print(back_msg.decode('utf-8'),addr)

udp不需要建立连接,所以可以实现与多个客户端同时建立连接。但是相比于tcp,udp是不可靠传输,但是速度快。可以把tcp协议理解为打电话,必须双方建立连接,然后说一句回一句。udp就像是发短信,客户端只关心消息有没有发出去就行了,不用关心对方有没有收到。具体udp的不可靠传输,下面会说到

在代码方面的区别,tcp的recv就相当于udp的recvfrom,tcp的send就相当于udp的sendto,另外因为udp不建立连接,所以发送消息的时候需要指定ip和端口号

4.粘包现象

先来看我们前面写的tcp的代码,recv()括号里的1024,这个1024限制了接收消息的最大字节数,想象一下,如果我们接收的数据长度超过1024字节,为了便于观察,我们把这个接受消息的最大字节数改成10,代码如下,看看如果发送的消息超过10,接收端会发生什么:

 1 import subprocess
 2 
 3 import socket
 4 phone_sever=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#买手机
 5 phone_sever.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
 6 phone_sever.bind(('127.0.0.1',8080))
 7 
 8 phone_sever.listen(5)
 9 
10 print('sever run ')
11 while True:
12 
13     conn,client_addr=phone_sever.accept()   
14     print('客户端',client_addr)
15     while True:
16         try:
17             cmd=conn.recv(10)#收消息
18             print(cmd.decode('utf-8'))
19             msg=input('>>>>>').strip()
20             conn.send(msg.encode('utf-8'))
21         except Exception:
22             break
23     conn.close()#挂电话
24 
25 phone_sever.close()#关机
服务端

相关文章: