网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。
```python
import socket
socket.socket(socket_family, socket_type,protocal=0)
# socket_family 可以是 AF_UNIX 或 AF_INET。
# socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM
# protocol一般不填,默认值为 0。
# 获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 由于 socket 模块中有太多的属性。我们在这里破例使用了'from module import *'语句。
# 使用'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。
# 例如:
from socket import *
tcpSock = socket(AF_INET, SOCK_STREAM)
```
server端
import socket
# 1.create server socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.Bind ip address and port to symbol the only server
# 127.*.*.* symbol local address
# in cmd, use:
# netstat /an
# to check the port in local machine
server.bind(('127.0.0.1', 20000))
# 3.set the limit of connect to server
server.listen(5)
# 4.start the server to begin listening the request from client
# when accept the request, the request contain information of client socket and client ip address
print('Server is listening request...')
client_connect, client_address = server.accept()
print('The information of client:\n%s\nThe client ip address:\n%s' % (client_connect, client_address))
# 5.stop the connect to client
client_connect.close()
# 6.stop the server
server.close()
client端
import socket
# 1.create client socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.connect to server with server's ip and port
connect = client.connect(('127.0.0.1', 20000))
# 3.stop the client
client.close()
server端
import socket
# 1.create server socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.Bind ip address and port to symbol the only server
server.bind(('127.0.0.1', 20000))
# 3.set the limit of connect to server
server.listen(5)
# Link loop: server can tun long time
while True:
# 4.start the server to begin listening the request from client
print('Server is listening request...')
client_connect, client_address = server.accept()
print('The information of client:\n%s\nThe client ip address:\n%s' % (client_connect, client_address))
# Message loop: server and client can communicate long time
while True:
try:
# 5.set the limit of information received from client
info = client_connect.recv(1024).decode('utf-8') # bytes
# when info is quit, just disconnect
if info == 'quit':
break
# 6.send the data after operating
client_connect.send(info[::-1].encode('utf-8'))
# when any exception occur, just disconnect
except Exception:
break
# 7.stop the connect to client
client_connect.close()
# 8.stop the server
server.close()
client端
import socket
# 1.create client socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.connect to server with server's ip and port
client.connect(('127.0.0.1', 20000))
# Message loop: server and client can communicate long time
while True:
info = input('>>>')
# when info is empty, stop sending
if not info:
continue
# 3.must send bytes
client.send(info.encode('utf-8'))
# when info is quit, just disconnect
if info == 'quit':
break
# 4.receive the data from server
data = client.recv(1024)
print(data.decode('utf-8'))
# 5.stop the client
client.close()
做测试的时候有可能会出现如下错误信息:
OSError: [WinError 10048] 通常每个套接字地址(协议/网络地址/端口)只允许使用一次。
这个是由于你的服务端仍然存在四次挥手的time_wait状态在占用地址的状态
其中原理有可能是如下几种,请自行了解:
- tcp三次握手,四次挥手
- syn洪水攻击
- 服务器高并发情况下会有大量的time_wait状态的优化方法
解决办法:
加入一条socket配置,在任务结束后重用ip和端口,需要在可以正常运行服务端的时候就使用
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决
vi /etc/sysctl.conf
# 编辑文件,加入以下内容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
# 然后执行 /sbin/sysctl -p 让参数生效。
net.ipv4.tcp_syncookies = 1
# 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
# 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_recycle = 1
# 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。
net.ipv4.tcp_fin_timeout
# 修改系統默认的 TIMEOUT 时间
服务端
from socket import *
import subprocess
ip_port=('127.0.0.1',8080)
server=socket(AF_INET,SOCK_STREAM)
server.bind(ip_port)
server.listen(5)
while True:
conn,addr=tcp_socket_server.accept()
print('客户端',addr)
while True:
cmd=conn.recv(1024)
if not cmd:break
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE,
stderr=subprocess.PIPE)
stderr=act_res.stderr.read()
stdout=act_res.stdout.read()
conn.send(stderr)
conn.send(stdout)
客户端
import socket
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
while True:
msg=input('>>: ').strip()
if not msg:continue
if msg == 'quit':break
s.send(msg.encode('utf-8'))
data=s.recv(1024)
print(data.decode('utf-8'))
上面简单实现了一个远程cmd命令行,运行时如果使用返回较多信息的命令(如ipconfig -all)时会发生粘包:
TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。 即面向流的通信是无消息保护边界的。
UDP(user datagram protocol,用户数据报协议)是无连接的,面向消息的,提供高效率服务。不会使用块的合并优化算法,, 由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。 即面向消息的通信是有消息保护边界的。
tcp是基于数据流的,于是收发的消息不能为空,这就需要在客户端和服务端都添加空消息的处理机制,防止程序卡住,而udp是基于数据报的,即便是你输入的是空内容(直接回车),那也不是空消息,udp协议会帮你封装上消息头。
服务端
from socket import *
import subprocess
import hashlib
import json
import struct
servers = socket(AF_INET, SOCK_STREAM)
servers.bind(('127.0.0.1', 20000))
servers.listen(5)
print('The server is started...')
# Link loop
while True:
print('Waiting for client links...')
connection, client_address = servers.accept()
print('Has been linked %s' % client_address[0])
# Communication cycle
while True:
try:
cmd = connection.recv(1024)
if not cmd:
break
# Execute the client's command
result = subprocess.Popen(cmd.decode('utf-8'), shell=True,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = result.stdout.read()
stderr = result.stderr.read()
md5_obj = hashlib.md5()
md5_obj.update(stdout + stderr)
# Make the header
header_dic = {
'total_size': len(stdout) + len(stderr),
'md5': md5_obj.hexdigest()}
header_bytes = json.dumps(header_dic).encode('utf-8')
# Make the fixed length of header
header_length = struct.pack('i', len(header_bytes))
# Send the fixed length of header
connection.send(header_length)
# Send the header
connection.send(header_bytes)
# Send the result
connection.send(stdout)
connection.send(stderr)
except Exception as error_info:
print(error_info)
break
connection.close()
# servers.close()
客户端
from socket import *
import struct
import json
import hashlib
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 20000))
while True:
cmd = input('[+_+] ').strip()
if not cmd:
continue
client.send(cmd.encode('utf-8'))
# Receive the fixed length of header
header_length = struct.unpack('i', client.recv(4))
# Receive the header
header_bytes = client.recv(header_length[0])
header_dic = json.loads(header_bytes.decode('utf-8'))
# Receive the message
total_size = header_dic['total_size']
receive_size = 0
total_data = b''
while receive_size < total_size:
receive_data = client.recv(1024)
receive_size += len(receive_data)
total_data += receive_data
md5_obj = hashlib.md5()
md5_obj.update(total_data)
if md5_obj.hexdigest() == header_dic['md5']:
print(total_data.decode('gbk'))
else:
print('Data is not complete.')
服务端
import socketserver
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print(self.request)
while True:
try:
cmd = self.request.recv(1024)
if not cmd:
break
self.request.send(cmd.upper())
except Exception as error_info:
print(error_info)
break
self.request.close()
if __name__ == '__main__':
server = socketserver.ThreadingTCPServer(('192.168.20.76', 8000), MyServer)
server.allow_reuse_address = True
server.serve_forever()
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('192.168.20.76', 8000))
while True:
msg = input('[+_+] ').strip()
if not msg:
continue
client.send(msg.encode('utf-8'))
result = client.recv(1024)
print(result.decode('utf-8'))