reference
1.0 黑客成長(zhǎng)日記
2.0入門網(wǎng)絡(luò)工具
3.0 工具合集
4.0知乎
5.0客戶端服務(wù)端
其實(shí)不黑客
Socket編程
Socket編程所牽涉的東西非常寬泛,調(diào)用各種編程語(yǔ)言對(duì)socket的TCP(TCP可靠通信的實(shí)現(xiàn)方式)和UDP封裝進(jìn)行網(wǎng)絡(luò)通信,可以是監(jiān)聽外部鏈接,也可以是主動(dòng)發(fā)起鏈接請(qǐng)求,發(fā)送特定協(xié)議并進(jìn)行通信,如何制定協(xié)議規(guī)范,如何進(jìn)行協(xié)議的編碼和解碼,如何將協(xié)議數(shù)據(jù)轉(zhuǎn)換為二進(jìn)制數(shù)據(jù)發(fā)送到網(wǎng)絡(luò)上和從網(wǎng)絡(luò)接收辨別且處理成功(牽涉到TCP粘包等問題),如何針對(duì)建立的鏈接進(jìn)行管理等。。。。
前置知識(shí):
1. 什么是socket:
IP+端口,socket編程就是承載通訊的系統(tǒng)資源標(biāo)識(shí)
白話Socket 就是插座 端口就是插座上的孔 端口不能被其他進(jìn)程占用 抽象理解 Socket 類似于操作某個(gè)IP地址上的某個(gè)端口達(dá)到點(diǎn)對(duì)點(diǎn)通信的目的, 需要綁定到某個(gè)具體的進(jìn)程中和端口中。
2.TCP/IP四層協(xié)議

3.半相關(guān):網(wǎng)絡(luò)中用一個(gè)三元組可以在全局唯一標(biāo)志一個(gè)進(jìn)程:
(協(xié)議,本地地址,本地端口號(hào))
4.全相關(guān)五元組:一個(gè)完整的網(wǎng)間進(jìn)程通信需要兩個(gè)進(jìn)程,同一種協(xié)議
(協(xié)議,本地地址,本地端口號(hào),遠(yuǎn)地地址,遠(yuǎn)地端口號(hào))
開始有點(diǎn)硬核起來(lái):客戶端編程
socket類提供標(biāo)準(zhǔn)的BSD Socket API。
為了方便網(wǎng)絡(luò)服務(wù)器的開發(fā),**socketserver **為服務(wù)器端編程提供了進(jìn)一步封裝
調(diào)用socket.socket可以創(chuàng)建一個(gè)Socket實(shí)例,socket類構(gòu)造函數(shù)聲明如下:
socket(family, type[,protocal])
我們看到socket構(gòu)造函數(shù)接收三個(gè)參數(shù),第一個(gè)為family。family表示套接字對(duì)象使用的地址族,可選值:AF_INET——IPv4地址族,AF_INET6——IPv6地址族,AF_UNIX——針對(duì)類UNIX系統(tǒng)的套接字。第二個(gè)為type,可使用的類型如下:socket.SOCK_STREAM基于TCP的流式socket通信
socket.SOCK_DGRAM基于UDP的數(shù)據(jù)報(bào)式socket通信
# -*- coding: UTF-8 -*-
import socket
import sys
#測(cè)試類
class Client:
def __init__(self, host, ip=None, port=80):
self.host = host #待連接的遠(yuǎn)程主機(jī)的域名
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() #退出進(jìn)程
#
#
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() #退出進(jìn)程
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('主機(jī)無(wú)法被解析:', e)
sys.exit() #退出進(jìn)程
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() #退出進(jìn)程
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'))
上面的代碼里,總共有三個(gè)try,第一個(gè)try,新建socket.socket實(shí)例,構(gòu)造一個(gè)半相關(guān)(這個(gè)是我自己想的不一定對(duì)),這里你把網(wǎng)給停了,這一步依舊是能走通的。
第二個(gè)try,通過(guò)域名獲取ip,如果是一個(gè)沒用的域名,就獲取不到ip
第三個(gè)try,選擇要接通的端口,這里是80,通過(guò)s.connect方法鏈接遠(yuǎn)程主機(jī)。連接建立后,通過(guò)sendall發(fā)送數(shù)據(jù),recv接收數(shù)據(jù)。注意數(shù)據(jù)是二進(jìn)制的(如果數(shù)據(jù)很多的時(shí)候需要使用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的請(qǐng)求報(bào)文
while True:
reply = s.recv(4096)
if reply:
print(reply)
else:
s.close()
break
開始有點(diǎn)硬核起來(lái):服務(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和端口號(hào)。
s.listen(10)#監(jiān)聽
print('socket正在監(jiān)聽')
# 進(jìn)入監(jiān)聽狀態(tài),listen方法接收一個(gè)參數(shù),用來(lái)指定可以同時(shí)掛起的連接數(shù)。
print("s.family", s.family)
print('等待客戶端連接')
conn, addr = s.accept() # 接收連接
# accept方法會(huì)返回一個(gè)代表當(dāng)前鏈接的connection對(duì)象和客戶端的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)建了一個(gè)簡(jiǎn)單的服務(wù)端。和客戶端編程類似,首先創(chuàng)建一個(gè)socket對(duì)象。隨后,我們要把socket綁定到傳入的IP和端口上,調(diào)用bind方法,傳入ip和端口號(hào)。服務(wù)端不會(huì)主動(dòng)連接其他主機(jī),而是等待客戶端連接,這需要進(jìn)入監(jiān)聽狀態(tài),listen方法接收一個(gè)參數(shù),用來(lái)指定可以同時(shí)掛起的連接數(shù)。監(jiān)聽模式之后,如果有客戶端連接進(jìn)來(lái),如何接收連接呢?需要使用accept方法。accept方法會(huì)返回一個(gè)代表當(dāng)前鏈接的connection對(duì)象和客戶端的ip地址。接下來(lái)就可以使用conn對(duì)象來(lái)接收和發(fā)送數(shù)據(jù)了,最后調(diào)用conn.close()關(guān)閉和客戶端的連接。下面我們啟動(dòng)服務(wù)端,然后再命令行啟動(dòng)nc,來(lái)連接服務(wù)端。
一個(gè)有趣的現(xiàn)象:在客戶端s.recv(4096),在服務(wù)器端s.recv(1024),答:顯然是自己設(shè)定的,sb
ps:把基于TCP改成基于UDP的時(shí)候本地不支持,不知道為啥,坑,待填
pps:cli能收到server的回復(fù),可是馬上就報(bào)錯(cuò)了, 不知道為啥,后面不知道為啥又不報(bào)錯(cuò)了。。。
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] 提供了一個(gè)無(wú)效的參數(shù)。