1. 網(wǎng)絡(luò)編程基礎(chǔ)
- 網(wǎng)絡(luò)編程:讓不同電腦上的軟件進(jìn)行數(shù)據(jù)傳遞,即進(jìn)程間通信饮睬。
- IP地址:用來(lái)標(biāo)記網(wǎng)絡(luò)主機(jī)秸谢。
- 每一個(gè)IP地址包括兩部分:網(wǎng)絡(luò)地址和主機(jī)地址。根據(jù)網(wǎng)絡(luò)地址和主機(jī)地址分為:A B C D E類奢驯。具體內(nèi)容參考:https://www.cnblogs.com/tunian/p/9632893.html
- 端口:端口通過(guò)端口號(hào)標(biāo)記,可通過(guò)IP+端口號(hào)來(lái)區(qū)分不同服務(wù)次绘。常用端口對(duì)照表:https://blog.csdn.net/l_smalltiger/article/details/81951824
2. 套接字
套接字是一種“通信端點(diǎn)”瘪阁,網(wǎng)絡(luò)化的應(yīng)用程序在開始任何通訊之前需要?jiǎng)?chuàng)建套接字。套接字分類:
面向連接的套接字:在通訊之前建立一條連接邮偎,也稱為流套接字管跺。面向連接的通信方式提供了可靠的、順序的禾进、不會(huì)重復(fù)的數(shù)據(jù)傳輸伙菜,也不會(huì)被加上數(shù)據(jù)邊界。這也代表每個(gè)發(fā)送的消息可能會(huì)被拆為多份命迈,沒有順序地正確到達(dá)目的地,然后被重新拼接起來(lái)火的。實(shí)現(xiàn)這種連接的主要協(xié)議是傳輸控制協(xié)議(TCP)壶愤。創(chuàng)建TCP套接字需要指定套接字類型為SOCK_STREAM。
無(wú)連接即數(shù)據(jù)報(bào)型的無(wú)連接套接字馏鹤。即無(wú)需建立連接就可進(jìn)行通訊征椒,但此時(shí)數(shù)據(jù)到達(dá)的順序、完整性及不重復(fù)性無(wú)法保證湃累。數(shù)據(jù)報(bào)會(huì)保留數(shù)據(jù)邊界勃救,即數(shù)據(jù)是整個(gè)發(fā)送的。實(shí)現(xiàn)這種連接的主要協(xié)議是用戶數(shù)據(jù)報(bào)協(xié)議(UDP)治力。創(chuàng)建UDP套接字需要指定套接字類型為SOCK_DGRAM蒙秒。
2.1 套接字使用:
套接字使用流程和文件很相似:創(chuàng)建套接字、使用套接字收發(fā)數(shù)據(jù)宵统、關(guān)閉套接字晕讲。
python使用socket()模塊來(lái)創(chuàng)建套接字,完成基于套接字的網(wǎng)絡(luò)通信。
函數(shù) socket.socket() 創(chuàng)建一個(gè) socket瓢省,該函數(shù)帶有兩個(gè)參數(shù):
- Address Family:可以選擇 AF_INET(用于 Internet 進(jìn)程間通信) 或者 AF_UNIX(用于同一臺(tái)機(jī)器進(jìn)程間通信),實(shí)際工作中常用AF_INET
- Type:套接字類型弄息,可以是 SOCK_STREAM(流式套接字,主要用于 TCP 協(xié)議)或者 SOCK_DGRAM(數(shù)據(jù)報(bào)套接字勤婚,主要用于 UDP 協(xié)議)
套接字中常用的函數(shù)如下:
2.2 udp套接字
udp通信模型
udp服務(wù)器
recvfrom方法:
- data, addr = server_socket.recvfrom()
- 方法返回兩個(gè)值摹量,第一個(gè)值為接收到的二進(jìn)制數(shù)據(jù),第二個(gè)數(shù)據(jù)是一個(gè)元組形式(IP, PORT)馒胆,標(biāo)記客戶端的地址缨称。第一個(gè)元素為客戶端的IP地址字符串,第二個(gè)元素為客戶端端口號(hào)国章。
sendto方法:
- server_socket.sentto(data, addr)
- sendto方法接受兩個(gè)參數(shù)具钥,第一個(gè)為發(fā)送的數(shù)據(jù)(二進(jìn)制形式,需要編碼)液兽,第二個(gè)為客戶端的地址(從recvfrom方法得到)
import socket
# 定義服務(wù)器IP及端口
HOST = "" # 字符串留空或者localhost代表本地骂删,也可自定義Ip
PORT = 7890
ADDR = (HOST, PORT)
def main():
# 定義服務(wù)器套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 綁定地址
server_socket.bind(ADDR)
# 通信循環(huán):收發(fā)消息
while True:
print("waiting for messages......")
# 接受客戶端消息
recv_data, addr = server_socket.recvfrom(1024)
print("receive messages:%s from %s" % (recv_data.decode("gbk"), str(addr)))
if recv_data.decode("gbk") == "exit":
break
# 發(fā)送消息到客戶端
send_data = "我是UDP服務(wù)器,已接收到你發(fā)來(lái)的消息四啰!"
server_socket.sendto(send_data.encode("gbk"), addr)
# 關(guān)閉服務(wù)端套接字
server_socket.close()
if __name__ == '__main__':
main()
udp客戶端
import socket
# 定義要連接的服務(wù)端地址
HOST = ""
PORT = 7890
ADDR = (HOST, PORT)
def main():
# 定義客戶端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 通信循環(huán):收發(fā)消息
while True:
# 客戶端先發(fā)送消息
send_data = input("請(qǐng)輸入要發(fā)送的數(shù)據(jù):")
if not send_data:
break
client_socket.sendto(send_data.encode("gbk"), ADDR)
recv_data, addr = client_socket.recvfrom(1024)
# 打印服務(wù)器發(fā)過(guò)來(lái)的數(shù)據(jù)
print(recv_data.decode("gbk"))
# 關(guān)閉客戶端套接字
client_socket.close()
if __name__ == '__main__':
main()
2.3 tcp套接字
tcp通信模型
TCP服務(wù)器
偽代碼:
server_socket.accept方法:
- tmp_socket, addr = server_socket.accept()
- 該方法返回兩個(gè)數(shù)據(jù)宁玫,第一個(gè)為一個(gè)臨時(shí)的套接字用于標(biāo)記當(dāng)前連接的客戶端及用來(lái)與客戶端進(jìn)行通信;第二個(gè)為當(dāng)前連接客戶端的地址信息(ip,port)
import socket
# 定義服務(wù)器地址:主機(jī)名/ip柑晒、端口號(hào)
Host = ''
PORT = 7890
ADDR = (Host, PORT)
def main():
# 定義TCP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 綁定地址
server_socket.bind(ADDR)
# 開始監(jiān)聽(變?yōu)楸粍?dòng))
server_socket.listen(128)
# 通信循環(huán):用來(lái)循環(huán)接受客戶端連接
while True:
# 接受客戶端連接
tmp_socket, addr= server_socket.accept() # 返回一個(gè)臨時(shí)套接字(用于客戶端通信)及客戶端的地址
print("connect from :", addr)
# 通信循環(huán):使用臨時(shí)套接字收發(fā)數(shù)據(jù)
while True:
recv_data = tmp_socket.recv(1024)
if not recv_data:
break
print(recv_data.decode("gbk"))
send_data = "我是一個(gè)TCP服務(wù)器E繁瘛!"
tmp_socket.send(send_data.encode("gbk"))
tmp_socket.close() # 關(guān)閉臨時(shí)套接字
server_socket.close() # 關(guān)閉監(jiān)聽套接字
if __name__ == '__main__':
main()
TCP客戶端
偽代碼:
import socket
# 定義要連接的服務(wù)器端地址
Host = ""
PORT = 7890
ADDR = (Host, PORT)
def main():
# 定義tcp客戶端套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 連接tcp服務(wù)器
client_socket.connect(ADDR)
# 通信循環(huán):收發(fā)數(shù)據(jù)
while True:
send_data = input("請(qǐng)輸入要發(fā)送的數(shù)據(jù):")
# 發(fā)送數(shù)據(jù)給服務(wù)器
client_socket.send(send_data.encode("gbk"))
# 從服務(wù)器接收數(shù)據(jù)并打印
recv_data = client_socket.recv(1024)
if not recv_data:
break
print(recv_data.decode("gbk"))
client_socket.close()
if __name__ == '__main__':
main()
tcp注意點(diǎn)
- tcp服務(wù)器一般情況下都需要綁定匙赞,否則客戶端找不到這個(gè)服務(wù)器
- tcp客戶端一般不綁定佛掖,因?yàn)槭侵鲃?dòng)鏈接服務(wù)器,所以只要確定好服務(wù)器的ip涌庭、port等信息就好芥被,本地客戶端可以隨機(jī)
- tcp服務(wù)器中通過(guò)listen可以將socket創(chuàng)建出來(lái)的主動(dòng)套接字變?yōu)楸粍?dòng)的,這是做tcp服務(wù)器時(shí)必須要做的
- 當(dāng)客戶端需要鏈接服務(wù)器時(shí)坐榆,就需要使用connect進(jìn)行鏈接拴魄,udp是不需要鏈接的而是直接發(fā)送,但是tcp必須先鏈接席镀,只有鏈接成功才能通信
- 當(dāng)一個(gè)tcp客戶端連接服務(wù)器時(shí)匹中,服務(wù)器端會(huì)有1個(gè)新的套接字,這個(gè)套接字用來(lái)標(biāo)記這個(gè)客戶端豪诲,單獨(dú)為這個(gè)客戶端服務(wù)
- listen后的套接字是被動(dòng)套接字顶捷,用來(lái)接收新的客戶端的鏈接請(qǐng)求的,而accept返回的新套接字是標(biāo)記這個(gè)新客戶端的
- 關(guān)閉listen后的套接字意味著被動(dòng)套接字關(guān)閉了跛溉,會(huì)導(dǎo)致新的客戶端不能夠鏈接服務(wù)器焊切,但是之前已經(jīng)鏈接成功的客戶端正常通信扮授。
- 關(guān)閉accept返回的套接字意味著這個(gè)客戶端已經(jīng)服務(wù)完畢
- 當(dāng)客戶端的套接字調(diào)用close后,服務(wù)器端會(huì)recv解堵塞专肪,并且返回的長(zhǎng)度為0刹勃,因此服務(wù)器可以通過(guò)返回?cái)?shù)據(jù)的長(zhǎng)度來(lái)區(qū)別客戶端是否已經(jīng)下線
tcp的三次握手及四次揮手