一.網(wǎng)絡(luò)編程理論基礎(chǔ)
1.網(wǎng)絡(luò)
??????網(wǎng)絡(luò),顧名思義就是通過點(diǎn)、線、面組成的一張連通全球的虛擬的大網(wǎng),實(shí)現(xiàn)信息資源上的共享。
??????最早期的網(wǎng)絡(luò),是用物理線路將多臺計(jì)算機(jī)連接起來實(shí)現(xiàn)計(jì)算機(jī)之間特定的數(shù)據(jù)交互。這種方式一定程度上緩解了數(shù)據(jù)共享的需求。然而這種方式極為簡陋,極大的限制了數(shù)據(jù)共享的范圍和計(jì)算機(jī)共享的普遍適用性,越來越不能滿足日益發(fā)展的計(jì)算機(jī)網(wǎng)絡(luò)技術(shù)的需求。
??????在這種迫切需求數(shù)據(jù)通信共享的情況下,計(jì)算機(jī)網(wǎng)絡(luò)計(jì)數(shù)有了飛速的發(fā)展,為了在眾多計(jì)算機(jī)中找到特定的計(jì)算機(jī),Vint cerf在實(shí)驗(yàn)?zāi)M階段使用了32位標(biāo)記的網(wǎng)絡(luò)地址協(xié)議(即后來的IP地址)用來標(biāo)識網(wǎng)絡(luò)上唯一的一臺計(jì)算機(jī),為了能在一臺計(jì)算機(jī)中找到特定的某個程序,將計(jì)算機(jī)中的每個和外界連接的數(shù)據(jù)程序設(shè)定了數(shù)據(jù)通信通道(即后來的端口)
??????同一時間國際標(biāo)準(zhǔn)化組織ISO定義了經(jīng)典的OSI/RM參考模型,極大的方便了人們在網(wǎng)絡(luò)上進(jìn)行數(shù)據(jù)通信傳輸,同時人們可以通過不同的協(xié)議進(jìn)行數(shù)據(jù)的有效傳播和共享
2.網(wǎng)絡(luò)編程的模型
??????每一個網(wǎng)絡(luò)應(yīng)用基本都由服務(wù)端和客戶端組成,網(wǎng)絡(luò)編程的模型也就是"客戶端-服務(wù)器端"模型。那么什么是服務(wù)端什么又是客戶端呢?見名知意,服務(wù)端負(fù)責(zé)管理某些資源,而客戶端就是這些資源的需求者。就如同售貨員跟顧客一樣,售貨員提供商品,客戶從售貨員這里獲得他們需要的物品。

3.網(wǎng)絡(luò)協(xié)議
??????什么是協(xié)議?協(xié)議呢,通俗的來講,網(wǎng)絡(luò)協(xié)議就是一種雙方都知道的必須遵守的約定。就比如兩個民族的人交流,但是你用苗語,他用瑤語,雖然各自都知道自己要表達(dá)的意思,但是對方聽不懂啊,為了更加友好的交流,就有了這么一個約定,都用漢語中的普通話交流,這樣就能消除各民族語言之間的差異導(dǎo)致的無效交流了,畢竟CCTV這個各民族都會看的頻道都說的是普通話嘛,誰都會誰都能聽得懂,何樂而不為呢?同樣的,計(jì)算機(jī)之間的通信也是遵循不同層次的協(xié)議,來實(shí)現(xiàn)計(jì)算機(jī)的通信。為了把全世界的所有不同類型的計(jì)算機(jī)都連接起來,就必須規(guī)定一套全球通用的協(xié)議,為了實(shí)現(xiàn)這個目標(biāo),互聯(lián)網(wǎng)協(xié)議簇就成為了通用協(xié)議標(biāo)準(zhǔn)。互聯(lián)網(wǎng)協(xié)議包含了上百種協(xié)議,但是最重要的兩個協(xié)議是TCP和IP協(xié)議,而我們通常把基于TCP和IP協(xié)議的所有協(xié)議統(tǒng)稱為”TCP/IP協(xié)議(蔟)”。
??????網(wǎng)絡(luò)協(xié)議又是用來干什么的呢?你用兩個不同主機(jī)之間需要進(jìn)行通信,需要利用各種硬件(光纖,路由器,交換機(jī)等)實(shí)現(xiàn)物理連接組成局域網(wǎng)和廣域網(wǎng).這些網(wǎng)絡(luò)中的主機(jī)如果需要進(jìn)行通信,就需要遵守相同的約定,即協(xié)議.網(wǎng)絡(luò)中的應(yīng)用抽象成7層,各層都有對應(yīng)的協(xié)議,這些協(xié)議有兩種基本的功能即命名方法(確定通信的雙方的身份)和傳送機(jī)制(如何把信息傳送過去).

4.網(wǎng)絡(luò)中的通信方式
??????在網(wǎng)絡(luò)中,如何將數(shù)據(jù)包發(fā)送給對方呢?socket套接字的出現(xiàn)解決了這一問題,通過套接字 socket 對象的連接,完成多種協(xié)議的網(wǎng)絡(luò)程序服務(wù)端/客戶端的開發(fā)和數(shù)據(jù)通信。它不是一種協(xié)議,而是對TCP/UDP等協(xié)議進(jìn)行封裝以后供程序調(diào)用的接口,代碼中調(diào)用相應(yīng)的接口,即可將數(shù)據(jù)發(fā)送給對方。這種編程方式叫做socket編程.客戶端建立一個socket,服務(wù)端建立一個socket,二者建立連接后便可以進(jìn)行通信,以TCP協(xié)議的socket為例,步驟如下:
1)服務(wù)端創(chuàng)建一個socket,并且需要指定相應(yīng)的端口(端口為2字節(jié)),此時這個服務(wù)進(jìn)程便于該端口綁定,監(jiān)聽端口的連接請求;
2)客戶端創(chuàng)建一個socket,客戶端的端口號一般由內(nèi)核分配,
3)客戶端的socket向某IP地址:端口的socket發(fā)送連接請求;
4)服務(wù)端的socket監(jiān)聽到連接請求后建立連接,此時兩個socket便建立了可靠地TCP連接,可以進(jìn)行通信;
5)通信結(jié)束后客戶端關(guān)閉socket并向服務(wù)端發(fā)送關(guān)閉請求;
6)服務(wù)端受到關(guān)閉請求后關(guān)閉socket
socket是長連接,連接建立后可以多次通信,最后主動關(guān)閉連接.實(shí)際中可能長時間不通信socket也會斷掉,此時需要自己實(shí)現(xiàn)心跳機(jī)制確保連接不斷開;
??????基本語法結(jié)構(gòu):
socket.socket(socket_family, socket_type, protocal=0)
socket_family:socket 地址家族,AF_UNIX/AF_LOCAL 或者 AF_INET
socket_type:socket 連接類型
面向連接的(SOCK_STREAM),面向無連接的(SOCK_DGRAME)
protocal:傳輸協(xié)議,一般不用設(shè)置,使用默認(rèn)值進(jìn)行自動匹配就好
??????創(chuàng)建 TCP 協(xié)議的套接字 socket 對象:
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
??????創(chuàng)建 UDP 協(xié)議的套接字 socket 對象:
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
??????socket套接字常用操作:
| 屬性 | 描述 |
|---|---|
| s.bind() | 綁定(主機(jī)名稱、端口)到一個套接字上 |
| s.listen() | 設(shè)置并啟動TCP監(jiān)聽 |
| s.accept() | 等待客戶端連接 |
| s.connect() | 連接到指定服務(wù)器 |
| s.connect_ex() | 連接指定服務(wù)器。如果出現(xiàn)錯誤返回信息 |
| s.recv() | 接收TCP消息 |
| s.recv_into() | 接收TCP消息到緩沖區(qū) |
| s.send() | 發(fā)送TCP消息 |
| s.sendall() | 完整發(fā)送TCP消息 |
| s.recvfrom() | 接收UDP消息 |
| s.recvfrom_info() | 接收UDP消息到緩沖區(qū) |
| s.sendto() | 發(fā)送UDP消息 |
| s.shutdown() | 關(guān)閉連接對象 |
| s.close() | 關(guān)閉套接字對象 |
二.TCP編程理論基礎(chǔ)
1.三次握手與四次揮手
??????TCP是一種面向連接的,可靠的、基于字節(jié)流的傳輸層通信協(xié)議,通過三次握手與四次揮手完成連接的可靠性。
三次握手:
第一次握手:客戶端向服務(wù)器端發(fā)送連接請求包SYN(syn=j),等待服務(wù)器回應(yīng);
第二次握手:服務(wù)器端收到客戶端連接請求包SYN(syn=j)后,將客戶端的請求包SYN(syn=j)放入到自己的未連接隊(duì)列,此時服務(wù)器需要發(fā)送兩個包給客戶端;
?。?)向客戶端發(fā)送確認(rèn)自己收到其連接請求的確認(rèn)包ACK(ack=j+1),向客戶端表明已知道了其連接請求
?。?)向客戶端發(fā)送連接詢問請求包SYN(syn=k),詢問客戶端是否已經(jīng)準(zhǔn)備好建立連接,進(jìn)行數(shù)據(jù)通信;
即在第二次握手時服務(wù)器向客戶端發(fā)送ACK(ack=j+1)和SYN(syn=k)包,此時服務(wù)器進(jìn)入SYN_RECV狀態(tài)。
第三次握手:客戶端收到服務(wù)器的ACK(ack=j+1)和SYN(syn=k)包后,知道了服務(wù)器同意建立連接,此時需要發(fā)送連接已建立的消息給服務(wù)器;
向服務(wù)器發(fā)送連接建立的確認(rèn)包ACK(ack=k+1),回應(yīng)服務(wù)器的SYN(syn=k)告訴服務(wù)器,我們之間已經(jīng)建立了連接,可以進(jìn)行數(shù)據(jù)通信。
ACK(ack=k+1)包發(fā)送完畢,服務(wù)器收到后,此時服務(wù)器與客戶端進(jìn)入ESTABLISHED狀態(tài),開始進(jìn)行數(shù)據(jù)傳送。

四次揮手:
第一次揮手:主機(jī)A向主機(jī)B發(fā)送FIN包;A告訴B,我(A)發(fā)送給你(B)的數(shù)據(jù)大小是N,我發(fā)送完畢,請求斷開A->B的連接。
第二次揮手:主機(jī)B收到了A發(fā)送的FIN包,并向主機(jī)A發(fā)送ACK包;B回答A,是的,我總共收到了你發(fā)給我N大小的數(shù)據(jù),A->B的連接關(guān)閉。
第三次揮手:主機(jī)B向主機(jī)A發(fā)送FIN包;B告訴A,我(B)發(fā)送給你(A)的數(shù)據(jù)大小是M,我發(fā)送完畢,請求斷開B->A的連接。
第四次揮手:主機(jī)A收到了B發(fā)送的FIN包,并向主機(jī)B發(fā)送ACK包;A回答B(yǎng),是的,我收到了你發(fā)送給我的M大小的數(shù)據(jù),B->A的連接關(guān)閉。

2.服務(wù)端的開發(fā)
服務(wù)端的編程操作步驟如下:
1)定義需要監(jiān)聽的主機(jī) IP 和端口號
2)綁定 IP 地址和端口號到套接字對象
3)開始監(jiān)聽
4)等待連接
5)連接成功-開始數(shù)據(jù)通信
6) 斷開連接
# -*- coding: utf-8 -*-
import socket,time
HOST = ''
PORT = 8081
ADDRESS = (HOST,PORT)
BUFFER = 1024
#創(chuàng)建服務(wù)端套接字
tcp_server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_server.bind(ADDRESS)
tcp_server.listen(10086)
while True:
#等待客戶端建立連接
print('waitting')
client_socket,client_address = tcp_server.accept()
print("歡迎{}的光臨".format(client_address))
while True:
#服務(wù)端發(fā)送消息
info = input("server:")
client_socket.send(info.encode('utf-8'))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
#接收客戶端消息
msg = client_socket.recv(BUFFER)
print("client{}:{}".format(client_address,msg.decode('utf-8')))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
print("☆" * 20)
#客戶端退出服務(wù)端等待狀態(tài)
if msg == b'bye':
client_socket.close()
break
tcp_server.close()
3.客戶端的開發(fā)
客戶端開發(fā)操作步驟如下:
1)創(chuàng)建 TCP 連接套接字對象
2)向指定的 IP 和 PORT 發(fā)起請求,請求連接
3)連接成功,進(jìn)行數(shù)據(jù)收發(fā)操作
4)關(guān)閉連接,釋放資源
# -*- coding: utf-8 -*-
import socket,time
TARGET_HOST = '192.168.209.1'
TARGRT_PORT = 8081
TARGET_ADDRESS = (TARGET_HOST,TARGRT_PORT)
BUFFER = 1024
#創(chuàng)建套接字對象,連接主機(jī)
tcp_client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
tcp_client.connect(TARGET_ADDRESS)
while True:
#接收服務(wù)端消息
msg = tcp_client.recv(BUFFER)
print("server:{}".format(msg.decode('utf-8')))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
#客戶端發(fā)送消息
info = input("client:")
tcp_client.send(info.encode('utf-8'))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
print("☆" * 20)
#客戶端關(guān)閉套接字退出
if info == 'bye':
tcp_client.close()
break
print("客戶端退出")