reference
1.0 黑客成長日記
2.0入門網(wǎng)絡(luò)工具
3.0 工具合集
4.0知乎
5.0客戶端服務(wù)端
其實不黑客
Socket編程
Socket編程所牽涉的東西非常寬泛枢劝,調(diào)用各種編程語言對socket的TCP(TCP可靠通信的實現(xiàn)方式)和UDP封裝進行網(wǎng)絡(luò)通信袍祖,可以是監(jiān)聽外部鏈接,也可以是主動發(fā)起鏈接請求伶选,發(fā)送特定協(xié)議并進行通信冤荆,如何制定協(xié)議規(guī)范玷过,如何進行協(xié)議的編碼和解碼人乓,如何將協(xié)議數(shù)據(jù)轉(zhuǎn)換為二進制數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上和從網(wǎng)絡(luò)接收辨別且處理成功(牽涉到TCP粘包等問題),如何針對建立的鏈接進行管理等乳附。内地。。赋除。
前置知識:
1. 什么是socket:
IP+端口阱缓,socket編程就是承載通訊的系統(tǒng)資源標(biāo)識
白話Socket 就是插座
端口就是插座上的孔 端口不能被其他進程占用 抽象理解 Socket 類似于操作某個IP地址上的某個端口達到點對點通信的目的, 需要綁定到某個具體的進程中和端口中。
2.TCP/IP四層協(xié)議
3.半相關(guān):網(wǎng)絡(luò)中用一個三元組可以在全局唯一標(biāo)志一個進程:
(協(xié)議举农,本地地址荆针,本地端口號)
4.全相關(guān)五元組:一個完整的網(wǎng)間進程通信需要兩個進程,同一種協(xié)議
(協(xié)議颁糟,本地地址航背,本地端口號,遠地地址棱貌,遠地端口號)
開始有點硬核起來:客戶端編程
socket類提供標(biāo)準(zhǔn)的BSD Socket API玖媚。
為了方便網(wǎng)絡(luò)服務(wù)器的開發(fā),**socketserver **為服務(wù)器端編程提供了進一步封裝
調(diào)用socket.socket可以創(chuàng)建一個Socket實例婚脱,socket類構(gòu)造函數(shù)聲明如下:
socket(family, type[,protocal])
我們看到socket構(gòu)造函數(shù)接收三個參數(shù)今魔,第一個為family勺像。family表示套接字對象使用的地址族,可選值:AF_INET——IPv4地址族错森,AF_INET6——IPv6地址族吟宦,AF_UNIX——針對類UNIX系統(tǒng)的套接字。第二個為type涩维,可使用的類型如下:socket.SOCK_STREAM基于TCP的流式socket通信
socket.SOCK_DGRAM基于UDP的數(shù)據(jù)報式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)#根據(jù)域名獲取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) #發(fā)送數(shù)據(jù)
print('發(fā)送數(shù)據(jù)成功')
reply = s.recv(4096) #接收數(shù)據(jù)
# while True:
# reply = s.recv(4096)
# if reply:
# print(reply)
# else:
# s.close()
# break
print(reply)
s.close() #關(guān)閉連接
except socket.error:
print("socket error")
print('發(fā)送數(shù)據(jù)失敗')
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實例辰狡,構(gòu)造一個半相關(guān)(這個是我自己想的不一定對)锋叨,這里你把網(wǎng)給停了垄分,這一步依舊是能走通的。
第二個try娃磺,通過域名獲取ip薄湿,如果是一個沒用的域名,就獲取不到ip
第三個try偷卧,選擇要接通的端口豺瘤,這里是80,通過s.connect方法鏈接遠程主機听诸。連接建立后坐求,通過sendall發(fā)送數(shù)據(jù),recv接收數(shù)據(jù)晌梨。注意數(shù)據(jù)是二進制的(如果數(shù)據(jù)很多的時候需要使用send方法循環(huán)發(fā)送桥嗤,接收也一樣,數(shù)據(jù)很大或者不知道具體多大就需要循環(huán)接收仔蝌。)
ps: b"GET / HTTP/1.1\r\n\r\n"就是標(biāo)準(zhǔn)的HTTP 1.0的請求報文
while True:
reply = s.recv(4096)
if reply:
print(reply)
else:
s.close()
break
開始有點硬核起來:服務(wù)端編程
# -*- 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)#創(chuàng)建socket
try:
s.bind((self.ip, self.port)) # 綁定
# 把socket綁定到傳入的IP和端口上泛领,調(diào)用bind方法,傳入ip和端口號敛惊。
s.listen(10)#監(jiān)聽
print('socket正在監(jiān)聽')
# 進入監(jiān)聽狀態(tài)渊鞋,listen方法接收一個參數(shù),用來指定可以同時掛起的連接數(shù)瞧挤。
print("s.family", s.family)
print('等待客戶端連接')
conn, addr = s.accept() # 接收連接
# accept方法會返回一個代表當(dāng)前鏈接的connection對象和客戶端的ip地址
print('客戶端連接 ' + addr[0] + ':' + str(addr[1]))
data = conn.recv(1024) # 接收數(shù)據(jù)
print("客戶端數(shù)據(jù):%s" % data)
conn.sendall(bytes("你好客戶端\n\r", encoding = "utf8")) # 發(fā)送數(shù)據(jù)
# conn.sendall(bytes("你好客戶端\n\r", encoding = "utf8")) # 發(fā)送數(shù)據(jù)
print("服務(wù)端消息發(fā)往客戶端成功")
conn.close()#關(guān)閉連接
except socket.error as e:
print(e)
sys.exit()
# finally:
# s.close() #關(guān)閉服務(wù)端
print("正常關(guān)閉服務(wù)器")
s.close() #關(guān)閉服務(wù)端
if __name__ == '__main__':
s = server('', 8008)
s.start()
server類的start方法創(chuàng)建了一個簡單的服務(wù)端锡宋。和客戶端編程類似,首先創(chuàng)建一個socket對象特恬。隨后执俩,我們要把socket綁定到傳入的IP和端口上,調(diào)用bind方法鸵鸥,傳入ip和端口號奠滑。服務(wù)端不會主動連接其他主機丹皱,而是等待客戶端連接,這需要進入監(jiān)聽狀態(tài)宋税,listen方法接收一個參數(shù)摊崭,用來指定可以同時掛起的連接數(shù)。監(jiān)聽模式之后杰赛,如果有客戶端連接進來呢簸,如何接收連接呢?需要使用accept方法乏屯。accept方法會返回一個代表當(dāng)前鏈接的connection對象和客戶端的ip地址根时。接下來就可以使用conn對象來接收和發(fā)送數(shù)據(jù)了,最后調(diào)用conn.close()關(guān)閉和客戶端的連接辰晕。下面我們啟動服務(wù)端蛤迎,然后再命令行啟動nc,來連接服務(wù)端含友。
一個有趣的現(xiàn)象:在客戶端s.recv(4096)替裆,在服務(wù)器端s.recv(1024),答:顯然是自己設(shè)定的窘问,sb
ps:把基于TCP改成基于UDP的時候本地不支持辆童,不知道為啥,坑惠赫,待填
pps:cli能收到server的回復(fù)把鉴,可是馬上就報錯了, 不知道為啥,后面不知道為啥又不報錯了儿咱。庭砍。。
socket正在監(jiān)聽
s.family AddressFamily.AF_INET
等待客戶端連接
客戶端連接 127.0.0.1:13819
客戶端數(shù)據(jù):b'GET / HTTP/1.1\r\n\r\n'
服務(wù)端消息發(fā)往客戶端成功
[WinError 10022] 提供了一個無效的參數(shù)概疆。