reference
其实不黑客
Socket编程
Socket编程所牵涉的东西非常宽泛,调用各种编程语言对socket的TCP(TCP可靠通信的实现方式)和UDP封装进行网络通信,可以是监听外部链接,也可以是主动发起链接请求,发送特定协议并进行通信,如何制定协议规范,如何进行协议的编码和解码,如何将协议数据转换为二进制数据发送到网络上和从网络接收辨别且处理成功(牵涉到TCP粘包等问题),如何针对建立的链接进行管理等。。。。
前置知识:
1. 什么是socket:
IP+端口,socket编程就是承载通讯的系统资源标识
白话Socket 就是插座
端口就是插座上的孔 端口不能被其他进程占用 抽象理解 Socket 类似于操作某个IP地址上的某个端口达到点对点通信的目的, 需要绑定到某个具体的进程中和端口中。
2.TCP/IP四层协议
3.半相关:网络中用一个三元组可以在全局唯一标志一个进程:
(协议,本地地址,本地端口号)
4.全相关五元组:一个完整的网间进程通信需要两个进程,同一种协议
(协议,本地地址,本地端口号,远地地址,远地端口号)
开始有点硬核起来:客户端编程
socket类提供标准的BSD Socket API。
为了方便网络服务器的开发,**socketserver **为服务器端编程提供了进一步封装
调用socket.socket可以创建一个Socket实例,socket类构造函数声明如下:
socket(family, type[,protocal])
我们看到socket构造函数接收三个参数,第一个为family。family表示套接字对象使用的地址族,可选值:AF_INET——IPv4地址族,AF_INET6——IPv6地址族,AF_UNIX——针对类UNIX系统的套接字。第二个为type,可使用的类型如下:socket.SOCK_STREAM基于TCP的流式socket通信
socket.SOCK_DGRAM基于UDP的数据报式socket通信
# -*- coding: UTF-8 -*-
import socket
import sys
#测试类
class Client:
def __init__(self, host, ip=None, port=80):
self.host = host #待连接的远程主机的域名
self.ip = ip
self.port = port
#
#
def connet_test(self): #连接方法
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print("TCP connet")
except socket.error as e:
print("Failed to create socket. Error: %s"%e)
sys.exit() #退出进程
#
#
def connet(self): #连接方法
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('socket created')
except socket.error as e:
print("Failed to create socket. Error: %s"%e)
sys.exit() #退出进程
try:
remote_ip = self.ip or socket.gethostbyname(self.host)#根据域名获取ip
print("get domain by IP: ", remote_ip)
except socket.gaierror as e:
print('主机无法被解析:', e)
sys.exit() #退出进程
try:
s.connect((remote_ip, self.port)) #连接
print('socket连接成功')
message = bytes("GET / HTTP/1.1\r\n\r\n", encoding="UTF-8")
# message = b"GET / HTTP/1.1\r\n\r\n"
s.sendall(message) #发送数据
print('发送数据成功')
reply = s.recv(4096) #接收数据
# while True:
# reply = s.recv(4096)
# if reply:
# print(reply)
# else:
# s.close()
# break
print(reply)
s.close() #关闭连接
except socket.error:
print("socket error")
print('发送数据失败')
sys.exit() #退出进程
return reply
if __name__ == '__main__':
cl = Client('www.baidu.com', '127.0.0.1', 8008)
# cl = Client('www.woqunidaye.com')
# cl = Client('www.baidu.com')
reply = cl.connet()
print(reply.decode('UTF-8'))
上面的代码里,总共有三个try,第一个try,新建socket.socket实例,构造一个半相关(这个是我自己想的不一定对),这里你把网给停了,这一步依旧是能走通的。
第二个try,通过域名获取ip,如果是一个没用的域名,就获取不到ip
第三个try,选择要接通的端口,这里是80,通过s.connect方法链接远程主机。连接建立后,通过sendall发送数据,recv接收数据。注意数据是二进制的(如果数据很多的时候需要使用send方法循环发送,接收也一样,数据很大或者不知道具体多大就需要循环接收。)
ps: b"GET / HTTP/1.1\r\n\r\n"就是标准的HTTP 1.0的请求报文
while True:
reply = s.recv(4096)
if reply:
print(reply)
else:
s.close()
break
开始有点硬核起来:服务端编程
# -*- coding: UTF-8 -*-
import socket
import sys
class server:
def __init__(self, ip, port):
self.port=port
self.ip=ip
def start(self):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#创建socket
try:
s.bind((self.ip, self.port)) # 绑定
# 把socket绑定到传入的IP和端口上,调用bind方法,传入ip和端口号。
s.listen(10)#监听
print('socket正在监听')
# 进入监听状态,listen方法接收一个参数,用来指定可以同时挂起的连接数。
print("s.family", s.family)
print('等待客户端连接')
conn, addr = s.accept() # 接收连接
# accept方法会返回一个代表当前链接的connection对象和客户端的ip地址
print('客户端连接 ' + addr[0] + ':' + str(addr[1]))
data = conn.recv(1024) # 接收数据
print("客户端数据:%s" % data)
conn.sendall(bytes("你好客户端\n\r", encoding = "utf8")) # 发送数据
# conn.sendall(bytes("你好客户端\n\r", encoding = "utf8")) # 发送数据
print("服务端消息发往客户端成功")
conn.close()#关闭连接
except socket.error as e:
print(e)
sys.exit()
# finally:
# s.close() #关闭服务端
print("正常关闭服务器")
s.close() #关闭服务端
if __name__ == '__main__':
s = server('', 8008)
s.start()
server类的start方法创建了一个简单的服务端。和客户端编程类似,首先创建一个socket对象。随后,我们要把socket绑定到传入的IP和端口上,调用bind方法,传入ip和端口号。服务端不会主动连接其他主机,而是等待客户端连接,这需要进入监听状态,listen方法接收一个参数,用来指定可以同时挂起的连接数。监听模式之后,如果有客户端连接进来,如何接收连接呢?需要使用accept方法。accept方法会返回一个代表当前链接的connection对象和客户端的ip地址。接下来就可以使用conn对象来接收和发送数据了,最后调用conn.close()关闭和客户端的连接。下面我们启动服务端,然后再命令行启动nc,来连接服务端。
一个有趣的现象:在客户端s.recv(4096),在服务器端s.recv(1024),答:显然是自己设定的,sb
ps:把基于TCP改成基于UDP的时候本地不支持,不知道为啥,坑,待填
pps:cli能收到server的回复,可是马上就报错了, 不知道为啥,后面不知道为啥又不报错了。。。
socket正在监听
s.family AddressFamily.AF_INET
等待客户端连接
客户端连接 127.0.0.1:13819
客户端数据:b'GET / HTTP/1.1\r\n\r\n'
服务端消息发往客户端成功
[WinError 10022] 提供了一个无效的参数。