网络编程
软件开发的架构:
1. C/S架构:客户端与服务器端的架构
2. B/S架构:浏览器端与服务器端架构
网络基础
地址解析协议,即ARP(Address Resolution Protocol),是根据IP地址获取物理地址的一个TCP/IP协议
socket概念
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
其实站在你的角度上看,socket就是一个模块。我们通过调用模块中已经实现的方法建立两个进程之间的连接和通信。
也有人将socket说成ip+port,因为ip是用来标识互联网中的一台主机的位置,而port是用来标识这台机器上的一个应用程序。
所以我们只要确立了ip和port就能找到一个应用程序,并且使用socket模块来与之通信。
套接字(socket)的初使用
基于TCP协议的socket
tcp是基于链接的,必须先启动服务器,然后再启动客户端去链接服务器
server端:
1 import socket
2 sk = socket.socket()
3 sk.bind(('127.0.0.1',8898)) #把地址绑定到套接字
4 sk.listen() #监听链接
5 conn,addr = sk.accept() #接受客户端链接
6 ret = conn.recv(1024) #接收客户端信息
7 print(ret) #打印客户端信息
8 conn.send(b'hi') #向客户端发送信息
9 conn.close() #关闭客户端套接字
10 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() # 关闭客户套接字
基于UDP协议的socket
udp是无连接的,启动服务器之后可以直接接收消息,不需要提前建立连接
server端:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
sk.bind(('127.0.0.1', 8021))
while True:
msg, addr = sk.recvfrom(1024)
ret = msg.decode('utf-8')
print(ret)
info = input('>>>')
sk.sendto(bytes(info.encode('utf-8')), addr)
sk.close()
client1端:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = (('127.0.0.1', 8021))
while True:
info = input('client1: ')
sk.sendto(bytes(info.encode('utf-8')), ip_port)
ret, addr = sk.recvfrom(1024)
print(ret.decode('utf-8'))
sk.close()
client2端:
import socket
sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = (('127.0.0.1'), 8021)
while True:
info = input('client2: ')
info = '\033[34m:%s\033[0m' % info # 给消息上颜色
sk.sendto(bytes(info.encode('utf-8')), ip_port)
msg, addr = sk.recvfrom(1024)
print(msg.decode('utf-8'))
sk.close()
黏包问题:
1 # 基于TCP实现远程执行命令
2 # 在server端发命令
3 import socket
4 sk = socket.socket()
5 sk.bind(('127.0.0.1', 8011))
6 sk.listen()
7
8 conn, addr = sk.accept()
9 while True:
10 cmd = input('>>>')
11 conn.send(cmd.encode('utf-8'))
12 ret = conn.recv(1024).decode('utf-8')
13 print(ret)
14
15 conn.close()
16 sk.close()
1 # 在client接收命令 并执行
2 import subprocess
3 import socket
4 sk = socket.socket()
5 sk.connect(('127.0.0.1', 8011))
6 while True:
7 cmd = sk.recv(1024).decode('utf-8')
8 ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9
10 std_out = 'stdout:'+(ret.stdout.read().decode('gbk'))
11 std_err = 'std_err'+(ret.stderr.read().decode('gbk'))
12 sk.send(std_out.encode('utf-8'))
13 sk.send(std_err.encode('utf-8'))
14 sk.close()
1 import socket
2 sk = socket.socket(type=socket.SOCK_DGRAM)
3 sk.bind(('127.0.0.1', 8033))
4 msg, addr = sk.recvfrom(10240)
5 while True:
6 cmd = input('>>>')
7 sk.sendto(cmd.encode('utf-8'), addr)
8 msg, addr = sk.recvfrom(10240)
9 print(msg.decode('utf-8'))
10
11 sk.close()
1 import socket
2 import subprocess
3 sk = socket.socket(type=socket.SOCK_DGRAM)
4 addr = ('127.0.0.1', 8033)
5 sk.sendto('在'.encode('utf-8'), addr)
6 while True:
7 cmd, addr = sk.recvfrom(10240)
8 ret = subprocess.Popen(cmd.decode('utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
9
10 std_out = 'stdout:' + (ret.stdout.read().decode('gbk'))
11 std_err = 'std_err' + (ret.stderr.read().decode('gbk'))
12 sk.sendto(std_out.encode('utf-8'), addr)
13 sk.sendto(std_err.encode('utf-8'), addr)
14 sk.close()
15
16 # UDP不会黏包
黏包成因:
tcp的套接字客户端往服务端上传文件,发送时文件内容是按照一段一段的字节流发送的,在接收方看了,根本不知道该文件的字节流从何处开始,在何处结束
udp不会发生黏包:
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。
不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。
即面向消息的通信是有消息保护边界的。
对于空消息:tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),也可以被发送,udp协议会帮你封装上消息头发送过去。
不可靠不黏包的udp协议:udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y;x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠。
会发生黏包的两种情况:
情况1:发送方的缓存机制
情况2:接收方的缓存机制
总结:
黏包现象只发生在tcp协议中:
1. 从表面上看,黏包现象主要是因为发生方和接收方的缓存机制,tcp协议面向流通信的特点
2. 实际上,主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的
解决黏包:
1 import socket
2 sk = socket.socket()
3 sk.bind(('127.0.0.1', 8200))
4 sk.listen()
5
6 conn, addr = sk.accept()
7 while True:
8 cmd = input('>>>')
9 if cmd == 'q':
10 conn.send(b'q')
11 break
12 conn.send(bytes(cmd.encode('gbk')))
13 num = conn.recv(1024).decode('utf-8')
14 conn.send(b'ok')
15 res = conn.recv(int(num)).decode('gbk')
16 print(res)
17
18
19 conn.close()
20 sk.close()
1 import socket
2 import subprocess
3 sk = socket.socket()
4 sk.connect(('127.0.0.1', 8200))
5
6 while True:
7 cmd = sk.recv(1024).decode('gbk')
8 if cmd == 'q':
9 break
10 res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
11
12 std_out = res.stdout.read()
13 std_err = res.stderr.read()
14 sk.send(str(len(std_out) + len(std_err)).encode('utf-8'))
15 sk.recv(1024)
16 sk.send(std_out)
17 sk.send(std_err)
18 # 好处:确定了到底要接受多大的数据
19 # 要在文件中配置一个配置项:就是每一次recv的大小
20 #当我们要发送大数据的时候,要明确的告诉接收方要发送多大的数据,以便接收方能够准确的接收到所有的数据
21 # 多用在文件传输的过程中
22 # 大文件的传输一定是按照字节读 每一次读固定的字节
23 # 传输的过程中 一边读 一边传 接收端一边收 一边写
24
25 # 不好的地方:多了一次交互
26
27 sk.close()
1 import socket
2 import struct
3
4 sk = socket.socket()
5 sk.bind(('127.0.0.1', 8200))
6 sk.listen()
7
8 conn, addr = sk.accept()
9 while True:
10 cmd = input('>>>')
11 if cmd == 'q':
12 conn.send(b'q')
13 break
14 conn.send(bytes(cmd.encode('gbk')))
15 num = conn.recv(4)
16 num = struct.unpack('i', num)[0]
17 res = conn.recv(int(num)).decode('gbk')
18 print(res)
19
20 conn.close()
21 sk.close()
1 import struct
2 import socket
3 import subprocess
4
5 sk = socket.socket()
6 sk.connect(('127.0.0.1', 8200))
7
8 while True:
9 cmd = sk.recv(1024).decode('gbk')
10 if cmd == 'q':
11 break
12 res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
13
14 std_out = res.stdout.read()
15 std_err = res.stderr.read()
16 len_num = len(std_out) + len(std_err)
17 num_by = struct.pack('i', len_num)
18 sk.send(num_by)
19 sk.send(std_out)
20 sk.send(std_err)
21
22 sk.close()
socketserver
1 import socketserver
2 class MyServer(socketserver.BaseRequestHandler):
3 def handle(self): # self.request 相当于conn
4 while True:
5 msg = self.request.recv(1024).decode('utf-8')
6 if msg == 'q':
7 self.request.close()
8 break
9 print(msg)
10 info = input('>>>')
11 self.request.send(info.encode('utf-8'))
12
13
14 if __name__ == '__main__':
15 server = socketserver.ThreadingTCPServer(('127.0.0.1', 8082), MyServer)
16 # thread 线程
17 server.serve_forever()
1 import socket
2 sk = socket.socket()
3 sk.connect(('127.0.0.1', 8082))
4 while True:
5 msg = input('>>>')
6 if msg == 'q':
7 break
8 sk.send(('Wechat:'+msg).encode('utf-8'))
9 ret = sk.recv(1024).decode('utf-8')
10 print(ret)
11 sk.close()
1 import socket
2 sk = socket.socket()
3 sk.connect(('127.0.0.1', 8082))
4 while True:
5 msg = input('>>>')
6 if msg == 'q':
7 break
8 sk.send(('QQ:'+msg).encode('utf-8'))
9 ret = sk.recv(1024).decode('utf-8')
10 print(ret)
11 sk.close()


浙公网安备 33010602011771号