來源:豐年留客 發(fā)布時(shí)間:2018-11-03 16:18:22 閱讀量:1512
開始添加FTP框架
通過學(xué)習(xí)FTP協(xié)議,我們知道FTP維護(hù)了兩個(gè)TCP連接。一個(gè)是控制連接,一個(gè)是數(shù)據(jù)連接??刂七B接走的是控制信息、命令等。數(shù)據(jù)連接是用來發(fā)送文件和目錄的。 為什么要這么設(shè)計(jì)呢?剛好在學(xué)習(xí)TCP/IP協(xié)議棧,嘗試解釋下。FTP中的命令,都是簡單的幾個(gè)字符組成的,最多加上命令參數(shù)不過100個(gè)字節(jié)以內(nèi)。而文件往往是以MB來計(jì)的。所以在發(fā)送命令時(shí),一個(gè)IP包往往是利用了一小部分,如我的wlan卡,MTU是1500字節(jié)。而文件被發(fā)送時(shí),每個(gè)包都是裝滿數(shù)據(jù)的。兩者遵循的TCP處理方式也不同,小分組比較多時(shí),會(huì)浪費(fèi)掉大部分的帶寬,系統(tǒng)會(huì)執(zhí)行NAGLE算法,對(duì)小數(shù)據(jù)包進(jìn)行合并發(fā)送,以提高網(wǎng)絡(luò)帶寬的利用率。大分組則會(huì)全力發(fā)送,只要窗口允許,帶寬允許。
FTP具體的協(xié)議要求為,客戶端為控制連接的發(fā)起端,服務(wù)器則是數(shù)據(jù)連接的發(fā)起端。當(dāng)客戶端想要傳輸某個(gè)文件時(shí),通過控制連接向服務(wù)器端發(fā)起請(qǐng)求,并告知自己可用的臨時(shí)端口號(hào),客戶端會(huì)去監(jiān)聽這個(gè)端口號(hào)。由服務(wù)器發(fā)起數(shù)據(jù)連接,把文件傳輸過來。注意,服務(wù)器端兩個(gè)TCP連接的端口號(hào)都是20,要求端口復(fù)用。我這里實(shí)現(xiàn)時(shí),只保證服務(wù)器端兩個(gè)連接使用相同的端口號(hào)即可,是不是20,我不關(guān)心。也不重要。
這次要實(shí)現(xiàn)的是在此前的基礎(chǔ)上添加以下特性。
1)添加控制連接,和數(shù)據(jù)連接。
2)實(shí)現(xiàn)通過控制連接傳輸請(qǐng)求,通過數(shù)據(jù)連接傳輸文件。
FTP的基本命令不在此次實(shí)現(xiàn)的范圍內(nèi)。通過初步的嘗試發(fā)現(xiàn),客戶端的單線程已經(jīng)遇到了致命問題。一方面我們要接受用戶的輸入,來向服務(wù)器傳遞命令,同時(shí)又要監(jiān)聽一個(gè)本地窗口。如果先發(fā)送命令,則等到服務(wù)器發(fā)起連接時(shí),客戶端的端口還沒有處于監(jiān)聽狀態(tài),導(dǎo)致連接無法進(jìn)行。而另一方面,如果先監(jiān)聽,則此時(shí)socket處于阻塞狀態(tài),無法接收用戶的輸入。這就是一個(gè)矛盾狀態(tài)了。沒有辦法了,反復(fù)試驗(yàn),只能采用多線程來實(shí)現(xiàn)。
具體做法是:
1)當(dāng)收到用戶要傳送文件的命令(g),就開啟一個(gè)線程,用來啟動(dòng)一個(gè)臨時(shí)的數(shù)據(jù)連接,監(jiān)聽本地的一個(gè)端口。
2)線程啟動(dòng)后,則在主線程中,通過控制連接向服務(wù)器發(fā)送傳輸文件的命令。
3)服務(wù)器接受到傳輸文件的命令后,向客戶端發(fā)發(fā)起數(shù)據(jù)連接。并把文件發(fā)送給客戶端。
4)文件發(fā)送完成后,客戶端線程退出,數(shù)據(jù)連接關(guān)閉。
目前的實(shí)現(xiàn)中。還沒有加入端口的傳遞信息。還是默認(rèn)傳輸tmp文件。
代碼如下,在手機(jī)和電腦之間進(jìn)行了測(cè)試??梢越邮?,但是手機(jī)上運(yùn)行客戶端,沒有寫權(quán)限。
#! /usr/bin/python
# -*- coding: utf-8 -*-
# client side
import socket
from threading import *
import time
def get_file():
data_socket_s = socket.socket()
data_socket_s.bind(('0.0.0.0', 8001))
data_socket_s.listen(1)
data_socket_c, data_socket_c_addr = data_socket_s.accept()
if data_socket_c:
print 'get a server connection'
file_data = data_socket_c.recv(1024)
f = open('tmp', 'w')
f.write(file_data)
f.close()
data_socket_c.close()
data_socket_s.close()
if __name__ == '__main__':
control_socket = socket.socket()
control_socket.connect(('127.0.0.1', 8000)) #根據(jù)需要更改地址
print 'connected'
while True:
print 'sending data'
in_data = raw_input('>>')
if in_data == 'q':
break;
if in_data == 'g': #開啟接受文件線程
file_thread = Thread(target = get_file)
file_thread.start()
time.sleep(0.5)
control_socket.send('g')
file_thread.join()
else:
control_socket.send(in_data)
print 'end'
control_socket.close()
#! /usr/bin/python
# -*- coding: utf-8 -*-
# server side
from socket import *
if __name__ == '__main__':
s_listen_socket = socket()
s_listen_socket.bind(('0.0.0.0', 8000))
print 'b4 listening'
s_listen_socket.listen(2)
control_socket, client_addr = s_listen_socket.accept()
# control_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
print client_addr[0]
print 'incoming connection'
if control_socket:
while True:
recv_data = control_socket.recv(20)
if not recv_data:
break;
print 'control receiving %s' % recv_data
if recv_data == 'g':
print 'sending file tmp'
data_socket = socket()
# data_socket.bind(('127.0.0.2', 8001))
data_socket.connect((client_addr[0], 8001))
out_file = open('tmp', 'r')
send_data = out_file.read()
data_socket.send(send_data)
out_file.close()
data_socket.close()
recv_data = ''
print 'sending file tmp done'
else:
print 'receiving %s' % recv_data
control_socket.send(recv_data)
print 'end'
s_listen_socket.close()
control_socket.close()
---------------------
作者:豐年留客
來源:CSDN
原文:https://blog.csdn.net/u012520854/article/details/53575134
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請(qǐng)附上博文鏈接!
在線
客服
服務(wù)時(shí)間:周一至周日 08:30-18:00
選擇下列產(chǎn)品馬上在線溝通:
客服
熱線
7*24小時(shí)客服服務(wù)熱線
關(guān)注
微信
關(guān)注官方微信