計算機網路 socket方式傳輸檔案
socket通訊實現檔案的傳輸,TCP傳輸方式,python版與C/C++版。
python版
伺服器端程式碼
TCPserver。py:
# -*- coding:utf-8 -*-
import
socket
import
os
import
threading
# 獲取本機ip
def
get_host_ip
():
try
:
s
=
socket
。
socket
(
socket
。
AF_INET
,
socket
。
SOCK_DGRAM
)
s
。
connect
((
‘8。8。8。8’
,
80
))
ip
=
s
。
getsockname
()[
0
]
finally
:
s
。
close
()
return
ip
# 處理客戶端請求下載檔案的操作(從主執行緒提出來的程式碼)
def
deal_client_request
(
ip_port
,
service_client_socket
):
# 連線成功後,輸出“客戶端連線成功”和客戶端的ip和埠
(
“客戶端連線成功”
,
ip_port
)
# 接收客戶端的請求資訊【recv】
file_name
=
service_client_socket
。
recv
(
1024
)
# 解碼
file_name_data
=
file_name
。
decode
(
“utf-8”
)
# 判斷檔案是否存在
if
os
。
path
。
exists
(
file_name_data
):
#輸出檔案位元組數
fsize
=
os
。
path
。
getsize
(
file_name_data
)
#轉化為兆單位
fmb
=
fsize
/
float
(
1024
*
1024
)
#要傳輸的檔案資訊
senddata
=
“檔名:
%s
檔案大小:
%。2f
MB”
%
(
file_name_data
,
fmb
)
#傳送和列印檔案資訊【send】
service_client_socket
。
send
(
senddata
。
encode
(
“utf-8”
))
(
“請求檔名:
%s
檔案大小:
%。2f
MB”
%
(
file_name_data
,
fmb
))
#接受客戶是否需要下載【recv】
options
=
service_client_socket
。
recv
(
1024
)
if
options
。
decode
(
“utf-8”
)
==
“y”
:
# 開啟檔案
with
open
(
file_name_data
,
“rb”
)
as
f
:
#計算總資料包數目
nums
=
fsize
/
1024
#當前傳輸的資料包數目
cnum
=
0
while
True
:
file_data
=
f
。
read
(
1024
)
cnum
=
cnum
+
1
#progress = cnum/nums*100
#print(“當前已下載:%。2f%%”%progress,end = “\r”)
if
file_data
:
# 只要讀取到資料,就向客戶端進行傳送【send】
service_client_socket
。
send
(
file_data
)
# 資料讀完,退出迴圈
else
:
(
“請求的檔案資料傳送完成”
)
break
else
:
(
“下載取消!”
)
else
:
(
“下載的檔案不存在!”
)
# 關閉服務當前客戶端的套接字【close】
service_client_socket
。
close
()
if
__name__
==
‘__main__’
:
# 獲取本機ip
(
“TCP檔案傳輸伺服器,本機IP:”
+
get_host_ip
())
# 把工作目錄切換到data目錄下
os
。
chdir
(
“。/data”
)
# 建立套接字【socket】
tcp_server_socket
=
socket
。
socket
(
socket
。
AF_INET
,
socket
。
SOCK_STREAM
)
# 繫結埠號【bind】
tcp_server_socket
。
bind
((
“”
,
3356
))
# 設定監聽,將主動套接字變為被動套接字【listen】
tcp_server_socket
。
listen
(
128
)
# 迴圈呼叫【accept】,可以支援多個客戶端同時連線,和多個客戶端同時下載檔案
while
True
:
service_client_socket
,
ip_port
=
tcp_server_socket
。
accept
()
# 連線成功後列印套接字號
#print(id(service_client_socket))
# 建立子執行緒
sub_thread
=
threading
。
Thread
(
target
=
deal_client_request
,
args
=
(
ip_port
,
service_client_socket
))
# 啟動子執行緒
sub_thread
。
start
()
客戶端程式碼
TCPclient。py:
# -*- coding:utf-8 -*-
# 多工檔案下載器客戶端
import
socket
import
os
if
__name__
==
‘__main__’
:
# 建立套接字【socket】
tcp_client_socket
=
socket
。
socket
(
socket
。
AF_INET
,
socket
。
SOCK_STREAM
)
# 和服務端連線【connect】
server_ip
=
input
(
“輸入伺服器IP:”
)
tcp_client_socket
。
connect
((
server_ip
,
3356
))
# 傳送下載檔案的請求
file_name
=
input
(
“請輸入要下載的檔名:”
)
# 編碼
file_name_data
=
file_name
。
encode
(
“utf-8”
)
# 傳送檔案下載請求資料【send】
tcp_client_socket
。
send
(
file_name_data
)
# 接收要下載的檔案資訊【recv】
file_info
=
tcp_client_socket
。
recv
(
1024
)
# 檔案資訊解碼
info_decode
=
file_info
。
decode
(
“utf-8”
)
(
info_decode
)
#獲取檔案大小
fileszie
=
float
(
info_decode
。
split
(
‘:’
)[
2
]
。
split
(
‘MB’
)[
0
])
fileszie2
=
fileszie
*
1024
# 是否下載?輸入y確認輸入q 取消
opts
=
input
(
“是否下載?(y 確認q 取消)”
)
if
opts
==
‘q’
:
(
“下載取消!程式退出”
)
else
:
(
“正在下載>>>>>>”
)
#向伺服器確認正在下載【send】
tcp_client_socket
。
send
(
b
‘y’
)
recvpath
=
“。/receive/”
if
not
os
。
path
。
exists
(
recvpath
):
os
。
mkdir
(
recvpath
)
# 把資料寫入到檔案裡
with
open
(
recvpath
+
file_name
,
“wb”
)
as
file
:
#目前接收到的資料包數目
cnum
=
0
while
True
:
# 迴圈接收檔案資料【recv】
file_data
=
tcp_client_socket
。
recv
(
1024
)
# 接收到資料
if
file_data
:
# 寫入資料
file
。
write
(
file_data
)
cnum
=
cnum
+
1
#progress =cnum/fileszie2*100
#print(“當前已下載:%。2f%%”%progress,end = “\r”)
# 接收完成
else
:
(
“下載結束!”
)
break
# 關閉套接字【close】
tcp_client_socket
。
close
()
上述程式修改搬運自:Python3使用TCP編寫一個簡易的檔案下載器——Linux公社 ,伺服器端添加了一段列印本機IP的程式碼,客戶端添加了一段新建receive資料夾儲存接收檔案的程式碼。
程式在Windows和Linux系統上均可執行,測試時需要在伺服器程式所在路徑新建一個data資料夾並放入用於測試的檔案,如圖片、影片檔案等。
另外,此程式在傳輸較小的檔案(如幾KB)時,程式中計算進度的語句會出現除數為0的錯誤,需要遮蔽傳輸進度相關語句或作某些修改。另一方面,進度的顯示也比較耗時,去掉進度顯示可以減小檔案傳輸時間。
測試結果
伺服器端(Ubuntu18。04):
D。。。@deeplearning:~/。。。/TCPsocketTest$ python3 TCPserver。py
TCP檔案傳輸伺服器,本機IP:192。168。1。143
客戶端連線成功 (‘192。168。1。110’, 53114)
請求檔名:1。jpg 檔案大小:0。04 MB
請求的檔案資料傳送完成
客戶端(Win10):
============= RESTART: G:\。。。\TCPsocketTest\TCPclient。py =============
輸入伺服器IP:192。168。1。143
請輸入要下載的檔名:1。jpg
檔名:1。jpg 檔案大小:0。04MB
是否下載?(y 確認q 取消)y
正在下載>>>>>>
下載結束!
>>>
G:\TCPsocketTest>
伺服器端執行在Ubuntu18。04系統,客戶端執行在Win10系統,當然也可以互換執行。另外,實測win10的伺服器端程式與Ubuntu10的客戶端通訊這種情況,win10的伺服器端必須在IDLE環境中執行,在cmd命令列中執行無法連線,原因未知。
C/C++版
將python程式改寫為C/C++語言,實現類似的檔案傳輸功能,以下程式用到了winsock以及dll庫,只能在Windows系統下執行。
伺服器端
server。cpp
// 客戶端傳送字串,伺服器接收字串,以相同內容返回 (迴圈服務)
#include
#include
#include
#include
#pragma comment (lib, “ws2_32。lib”)
//載入 ws2_32。dll
#define BUF_SIZE 1024
int
main
()
{
WSADATA
wsaData
;
WSAStartup
(
MAKEWORD
(
2
,
2
),
&
wsaData
);
// 載入套接字型檔
//建立套接字
SOCKET
servSock
=
socket
(
AF_INET
,
SOCK_STREAM
,
0
);
//【socket】
//繫結套接字
sockaddr_in
sockAddr
;
memset
(
&
sockAddr
,
0
,
sizeof
(
sockAddr
));
//每個位元組都用0填充
sockAddr
。
sin_family
=
PF_INET
;
//使用IPv4地址
//sockAddr。sin_addr。s_addr = inet_addr(“127。0。0。1”); //具體的IP地址
sockAddr
。
sin_addr
。
s_addr
=
htonl
(
INADDR_ANY
);
//不要這一句好像也行
sockAddr
。
sin_port
=
htons
(
3356
);
//埠
bind
(
servSock
,
(
SOCKADDR
*
)
&
sockAddr
,
sizeof
(
SOCKADDR
));
//【bind】
//進入監聽狀態
listen
(
servSock
,
20
);
//【listen】
//接收客戶端請求
SOCKADDR
clntAddr
;
int
nSize
=
sizeof
(
SOCKADDR
);
char
buffer
[
BUF_SIZE
]
=
{
0
};
//接收緩衝區
char
sbuffer
[
BUF_SIZE
]
=
{
0
};
// 本機IP
char
ip
[
20
]
=
{
0
};
struct
hostent
*
phostinfo
=
gethostbyname
(
“”
);
char
*
p
=
inet_ntoa
(
*
((
struct
in_addr
*
)(
*
phostinfo
->
h_addr_list
)));
strncpy
(
ip
,
p
,
sizeof
(
ip
)
-
1
);
ip
[
sizeof
(
ip
)
-
1
]
=
‘\0’
;
printf
(
“This is TCP file server(IP:%s)
\n
”
,
ip
);
printf
(
“waiting connect。。。
\n
”
);
while
(
1
)
{
SOCKET
clntSock
=
accept
(
servSock
,
(
SOCKADDR
*
)
&
clntAddr
,
&
nSize
);
//【accept】
// 獲取客戶端的IP的埠號
struct
sockaddr_in
*
sock
=
(
struct
sockaddr_in
*
)
&
clntAddr
;
printf
(
“client(IP:%s, PORT:%d)connect ok
\n
”
,
inet_ntoa
(
sock
->
sin_addr
),
ntohs
(
sock
->
sin_port
));
int
strLen
=
recv
(
clntSock
,
buffer
,
BUF_SIZE
,
0
);
//接收客戶端發來的資料 【recv】
//printf(“%s\n”, buffer);
// 判斷檔案是否存在
FILE
*
pFile
;
pFile
=
fopen
(
buffer
,
“rb”
);
//獲取已開啟檔案的指標
if
(
pFile
)
{
fseek
(
pFile
,
0
,
SEEK_END
);
//先用fseek將檔案指標移到檔案末尾
int
n
=
ftell
(
pFile
);
//再用ftell獲取檔案內指標當前的檔案位置。
//printf(“file:%s, size:%dKB\n”, buffer, n/1024);
sprintf
(
sbuffer
,
“file:%s, size:%dKB
\n
”
,
buffer
,
n
/
1024
);
send
(
clntSock
,
sbuffer
,
BUF_SIZE
,
0
);
//【send】檔案大小資訊
recv
(
clntSock
,
buffer
,
BUF_SIZE
,
0
);
//【recv】
if
(
*
buffer
==
‘y’
)
{
fseek
(
pFile
,
0
,
SEEK_SET
);
//開頭
int
len
;
while
(
1
)
{
if
((
len
=
fread
(
&
sbuffer
,
1
,
BUF_SIZE
,
pFile
))
&&
len
>
0
)
{
send
(
clntSock
,
sbuffer
,
len
,
0
);
//【send】
//printf(“FILE p offset:%d\n”, (int)ftell(pFile));
//printf(“%d\n”, len);
}
else
{
printf
(
“Download down!
\n
”
);
break
;
}
}
}
else
printf
(
“Download Canceled!
\n
”
);
}
else
{
sprintf
(
sbuffer
,
“file:%s not exist!!!
\n
”
,
buffer
);
send
(
clntSock
,
sbuffer
,
BUF_SIZE
,
0
);
//【send】檔案大小資訊
}
closesocket
(
clntSock
);
//關閉套接字
memset
(
buffer
,
0
,
BUF_SIZE
);
//重置緩衝區
memset
(
sbuffer
,
0
,
BUF_SIZE
);
}
//關閉套接字
closesocket
(
servSock
);
//終止 DLL 的使用
WSACleanup
();
return
0
;
}
客戶端
client。cpp:
// 客戶端傳送字串,伺服器接收字串,以相同內容返回 (迴圈服務)
#include
#include
#include
#pragma comment(lib, “ws2_32。lib”)
//載入 ws2_32。dll
#define BUF_SIZE 1024
int
main
()
{
//初始化DLL
WSADATA
wsaData
;
WSAStartup
(
MAKEWORD
(
2
,
2
),
&
wsaData
);
//向伺服器發起請求
sockaddr_in
sockAddr
;
memset
(
&
sockAddr
,
0
,
sizeof
(
sockAddr
));
//每個位元組都用0填充
sockAddr
。
sin_family
=
PF_INET
;
//sockAddr。sin_addr。s_addr = inet_addr(“127。0。0。1”);//伺服器IP
sockAddr
。
sin_port
=
htons
(
3356
);
char
bufSend
[
BUF_SIZE
]
=
{
0
};
char
bufRecv
[
BUF_SIZE
]
=
{
0
};
//輸入伺服器IP
printf
(
“Input server IP: ”
);
gets_s
(
bufSend
,
BUF_SIZE
);
//此處先借用bufSend接收輸入的IP地址
sockAddr
。
sin_addr
。
s_addr
=
inet_addr
(
bufSend
);
while
(
1
)
{
//建立套接字
SOCKET
sock
=
socket
(
PF_INET
,
SOCK_STREAM
,
IPPROTO_TCP
);
//【socket】
connect
(
sock
,
(
SOCKADDR
*
)
&
sockAddr
,
sizeof
(
SOCKADDR
));
//【connect】
//獲取使用者輸入的字串併發送給伺服器
printf
(
“Input a file name for download: ”
);
gets_s
(
bufSend
,
BUF_SIZE
);
send
(
sock
,
bufSend
,
BUF_SIZE
,
0
);
//【send】
//接收伺服器傳回的資料
recv
(
sock
,
bufRecv
,
BUF_SIZE
,
0
);
//【recv】
//輸出接收到的資料
printf
(
“%s
\n
”
,
bufRecv
);
if
(
!
strstr
(
bufRecv
,
“not”
))
{
printf
(
“Download now?(y/n)”
);
if
(
getchar
()
==
‘y’
)
{
printf
(
“Download 。。。
\n
”
);
send
(
sock
,
“y”
,
sizeof
(
char
),
0
);
//【send】
FILE
*
pFile
;
pFile
=
fopen
(
bufSend
,
“wb”
);
//獲取已開啟檔案的指標
int
len
;
while
((
len
=
recv
(
sock
,
bufRecv
,
BUF_SIZE
,
0
))
&&
len
>
0
)
//【recv】
{
fwrite
(
&
bufRecv
,
1
,
len
,
pFile
);
//printf(“receive。。。%d\n”, (int)ftell(pFile));
//printf(“%d\n”, len);
}
printf
(
“Download down!
\n
”
);
fclose
(
pFile
);
}
else
printf
(
“Cancel download
\n
”
);
}
getchar
();
//此句是為了消除上面gets_s()或的問題
memset
(
bufSend
,
0
,
BUF_SIZE
);
//重置緩衝區
memset
(
bufRecv
,
0
,
BUF_SIZE
);
//重置緩衝區
closesocket
(
sock
);
//關閉套接字【close】
}
WSACleanup
();
//終止使用 DLL
return
0
;
}
執行效果與python版的類似。