网络编程

门面模式

把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。

基于TCP协议的socket

sever端

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')             #向客户端发送信息 必须传bytes类型
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()                     # 关闭客户套接字

出现address already in use 问题怎么解决

#加入一条socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #避免服务器重启的时候报address already in use
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()                   #关闭服务器套接字(可选)

基于UDP协议的socket

sever端

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)

黏包问题

不知道客户端发送数据的长度
优化算法 多个send连续的小数据包会被并合 连续使用send 引起
tcp会黏包:内部做了优化算法,让整个程序发送数据和接收数据没有边界了
udp不会黏包

解决方法:
1. 确定接收多大数据
要在文件上配置一个配置项,就是每一次recv的大小 buffer=4096
当发送大数据的时候要明确告诉接收方要发送多大的数据,以便接收方能够准确接收到所有数据
多用在文件传输过程中:
大文件的传输一定是按照字节读,每一次读固定的字节
传输的过程中,一边读一边传 接收端一边收一边写
send这个大文件之前 35672字节 send(4096) 35672-4096-4096 -->0
recv这个大文件,recv 35672字节 recv(2048) 35672-2048-->0

缺点:多了一次交互
2. struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
如果发送数据的时候,先发送长度,先接收长度
>>>client
import socket
import struct
import subprocess
sk=socket.socket()
sk.connect(('127.0.0.1',8888))
while True:
cmd=sk.recv(1024).decode('gbk')
if cmd=='q':
break
res=subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
std_out=res.stdout.read()
std_err=res.stderr.read()
# sk.send(str(len(std_err)+len(std_out)).encode('utf-8'))
len_num=len(std_err)+len(std_out)
num_by=struct.pack('i',len_num)
sk.send(num_by)
sk.send(std_out)
sk.send(std_err)
sk.close()
>>>sever
import socket
import struct
sk=socket.socket()
sk.bind(('127.0.0.1',8888))
sk.listen(5)
conn,addr=sk.accept()
while True:
cmd=input('>>>')
if cmd=='q':
conn.send(b'q')
break
conn.send(cmd.encode('gbk'))
num=conn.recv(4)
num=struct.unpack('i',num)[0]
res=conn.recv(int(num)).decode('gbk')
print(res)
conn.close()
sk.close()
数据包里所有的数据都是报文,报文里不止有数据,ip地址,mac地址,端口号
所有报文都有报头:接收多少个字节
定制报文:
复杂的应用上就会用到
阐述文件的时候就够复杂了
文件的名字
文件的大小
文件的类型
存储的路径
head={'filename':'test','filesize':409600,'filetype':'txt','filepath':r'userin'}
报头的长度 #先接收4个字节
send(head) #根据4个字节获取报头
send(file) #从报文中获取filesize,然后filesize接收文件
其实在网络传输的过程中处处有协议
协议就是一堆报文和报头---字节
协议的解析过程不需要关心
如果跳出所了解的端口、ip、协议,我们写的程序也需要多次发送数据或者发送多个数据
协议可以自定制,本质上就是一种约定

原文地址:https://www.cnblogs.com/yangyuqing/p/10059801.html