Python爬蟲(chóng)(三)-Socket網(wǎng)絡(luò)編程


python的網(wǎng)絡(luò)變成比c語(yǔ)言簡(jiǎn)單許多, 封裝許多底層的實(shí)現(xiàn)細(xì)節(jié), 方便程序員使用的同時(shí), 也使程序員比較難了解一些底層的東西, 我覺(jué)得學(xué)網(wǎng)絡(luò)編程還是用c語(yǔ)言更好一點(diǎn).

寫這篇博文, 也希望回顧并整理一下以前學(xué)過(guò)的c語(yǔ)言和linux下一些東西, 會(huì)將一些Linux網(wǎng)絡(luò)編程的函數(shù)和Python網(wǎng)絡(luò)變成函數(shù)做一個(gè)簡(jiǎn)單的對(duì)照, 方便記憶

1. Socket套接字的概念


Socket(翻譯為套接字, 我覺(jué)得很挫),是操作系統(tǒng)內(nèi)核中的一個(gè)數(shù)據(jù)結(jié)構(gòu),它是網(wǎng)絡(luò)中的節(jié)點(diǎn)進(jìn)行相互通信的門戶。它是網(wǎng)絡(luò)進(jìn)程的ID。網(wǎng)絡(luò)通信,歸根到底還是進(jìn)程間的通信(不同計(jì)算機(jī)上的進(jìn)程間通信, 又稱進(jìn)程間通信, IP協(xié)議進(jìn)行的主要是端到端通信)。在網(wǎng)絡(luò)中,每一個(gè)節(jié)點(diǎn)(計(jì)算機(jī)或路由)都有一個(gè)網(wǎng)絡(luò)地址,也就是IP地址。兩個(gè)進(jìn)程通信時(shí),首先要確定各自所在的網(wǎng)絡(luò)節(jié)點(diǎn)的網(wǎng)絡(luò)地址。但是,網(wǎng)絡(luò)地址只能確定進(jìn)程所在的計(jì)算機(jī),而一臺(tái)計(jì)算機(jī)上很可能同時(shí)運(yùn)行著多個(gè)進(jìn)程,所以僅憑網(wǎng)絡(luò)地址還不能確定到底是和網(wǎng)絡(luò)中的哪一個(gè)進(jìn)程進(jìn)行通信,因此套接口中還需要包括其他的信息,也就是端口號(hào)(PORT)。在一臺(tái)計(jì)算機(jī)中,一個(gè)端口號(hào)一次只能分配給一個(gè)進(jìn)程,也就是說(shuō),在一臺(tái)計(jì)算機(jī)中,端口號(hào)和進(jìn)程之間是一一對(duì)應(yīng)關(guān)系。
所以,使用端口號(hào)和網(wǎng)絡(luò)地址的組合可以唯一的確定整個(gè)網(wǎng)絡(luò)中的一個(gè)網(wǎng)絡(luò)進(jìn)程.

端口號(hào)的范圍從065535,一類是由互聯(lián)網(wǎng)指派名字和號(hào)碼公司ICANN負(fù)責(zé)分配給一些常用的應(yīng)用程序固定使用的“周知的端口”,其值一般為01023, 用戶自定義端口號(hào)一般大于等于1024, 我比較喜歡用8888

每一個(gè)socket都用一個(gè)半相關(guān)描述{協(xié)議、本地地址、本地端口}來(lái)表示;一個(gè)完整的套接字則用一個(gè)相關(guān)描述{協(xié)議、本地地址、本地端口、遠(yuǎn)程地址、遠(yuǎn)程端口}來(lái)表示。socket也有一個(gè)類似于打開(kāi)文件的函數(shù)調(diào)用,該函數(shù)返回一個(gè)整型的socket描述符,隨后的連接建立、數(shù)據(jù)傳輸?shù)炔僮鞫际峭ㄟ^(guò)socket來(lái)實(shí)現(xiàn)的

1.1. Socket類型

socket類型在Liunx和Python是一樣的, 只是Python中的類型都定義在socket模塊中, 調(diào)用方式socket.SOCK_XXXX

  • 流式socket(SOCK_STREAM) 用于TCP通信

流式套接字提供可靠的、面向連接的通信流;它使用TCP協(xié)議,從而保證了數(shù)據(jù)傳輸?shù)恼_性和順序性

  • 數(shù)據(jù)報(bào)socket(SOCK_DGRAM) 用于UDP通信

數(shù)據(jù)報(bào)套接字定義了一種無(wú)連接的服務(wù),數(shù)據(jù)通過(guò)相互獨(dú)立的報(bào)文進(jìn)行傳輸,是無(wú)序的,并且不保證是可靠、無(wú)差錯(cuò)的。它使用數(shù)據(jù)報(bào)協(xié)議UDP

  • 原始socket(SOCK_RAW) 用于新的網(wǎng)絡(luò)協(xié)議實(shí)現(xiàn)的測(cè)試等

原始套接字,普通的套接字無(wú)法處理ICMP、IGMP等網(wǎng)絡(luò)報(bào)文,而SOCK_RAW可以, 其次,SOCK_RAW也可以處理特殊的IPv4報(bào)文;此外,利用原始套接字,可以通過(guò)IP_HDRINCL套接字選項(xiàng)由用戶構(gòu)造IP頭。

2. Socket編程


2.1. TCP通信

TCP通信的基本步驟如下:
服務(wù)端:socket---bind---listen---while(True){---accept---recv---send----}---close
客戶端:socket----------------------------------connect---send---recv-------close

TCP
TCP

socket函數(shù)
使用給定的地址族、套接字類型、協(xié)議編號(hào)(默認(rèn)為0)來(lái)創(chuàng)建套接字

#Linux
int socket(int domain, int type, int protocol);
domain:AF_INET:Ipv4網(wǎng)絡(luò)協(xié)議 AF_INET6:IPv6網(wǎng)絡(luò)協(xié)議
type : tcp:SOCK_STREAM   udp:SOCK_DGRAM
protocol : 指定socket所使用的傳輸協(xié)議編號(hào)。通常為0.
返回值:成功則返回套接口描述符,失敗返回-1。

#python
socket.socket([family[, type[, proto]]])
family : AF_INET (默認(rèn)ipv4), AF_INET6(ipv6) or AF_UNIX(Unix系統(tǒng)進(jìn)程間通信). 
type : SOCK_STREAM (TCP), SOCK_DGRAM(UDP) . 
protocol : 一般為0或者默認(rèn)

如果socket創(chuàng)建失敗會(huì)拋出一個(gè)socket.error異常

2.1.1. 服務(wù)器端函數(shù)

bind函數(shù)
將套接字綁定到地址, python下,以元組(host,port)的形式表示地址, Linux下使用sockaddr_in結(jié)構(gòu)體指針

#Linux
int bind(int sockfd, struct sockaddr * my_addr, int addrlen);
sockfd : 前面socket()的返回值
my_addr : 結(jié)構(gòu)體指針變量
#####
struct sockaddr_in  //常用的結(jié)構(gòu)體
{
unsigned short int sin_family;  //即為sa_family AF_INET
uint16_t sin_port;  //為使用的port編號(hào)
struct in_addr sin_addr;  //為IP地址
unsigned char sin_zero[8];  //未使用
};
struct in_addr
{
uint32_t s_addr;
};
####
addrlen : sockaddr的結(jié)構(gòu)體長(zhǎng)度。通常是計(jì)算sizeof(struct sockaddr);
返回值:成功則返回0,失敗返回-1



#python
s.bind(address)
s為socket.socket()返回的套接字對(duì)象
address為元組(host,port)
host: ip地址, 為一個(gè)字符串
post: 自定義主機(jī)號(hào), 為整型

listen函數(shù)
使服務(wù)器的這個(gè)端口和IP處于監(jiān)聽(tīng)狀態(tài),等待網(wǎng)絡(luò)中某一客戶機(jī)的連接請(qǐng)求。如果客戶端有連接請(qǐng)求,端口就會(huì)接受這個(gè)連接

#Linux
int listen(int sockfd,int backlog);
sockfd : 為前面socket的返回值.
backlog : 指定同時(shí)能處理的最大連接要求,通常為10或者5。最大值可設(shè)至128
返回值:成功則返回0,失敗返回-1

#python
s.listen(backlog)
s為socket.socket()返回的套接字對(duì)象
backlog : 操作系統(tǒng)可以掛起的最大連接數(shù)量。該值至少為1,大部分應(yīng)用程序設(shè)為5就可以了

accept函數(shù)
接受遠(yuǎn)程計(jì)算機(jī)的連接請(qǐng)求,建立起與客戶機(jī)之間的通信連接。服務(wù)器處于監(jiān)聽(tīng)狀態(tài)時(shí),如果某時(shí)刻獲得客戶機(jī)的連接請(qǐng)求,此時(shí)并不是立即處理這個(gè)請(qǐng)求,而是將這個(gè)請(qǐng)求放在等待隊(duì)列中,當(dāng)系統(tǒng)空閑時(shí)再處理客戶機(jī)的連接請(qǐng)求。

#Linux
int accept(int s,struct sockaddr * addr,int * addrlen);
sockfd : 為前面socket的返回值.
addr : 為結(jié)構(gòu)體指針變量,和bind的結(jié)構(gòu)體是同種類型的,系統(tǒng)會(huì)把遠(yuǎn)程主機(jī)的信息(遠(yuǎn)程主機(jī)的地址和端口號(hào)信息)保存到這個(gè)指針?biāo)傅慕Y(jié)構(gòu)體中。
addrlen : 表示結(jié)構(gòu)體的長(zhǎng)度,為整型指針 
返回值:成功則返回新的socket處理代碼new_fd,失敗返回-1

#python
s.accept()
s為socket.socket()返回的套接字對(duì)象
返回(conn,address),其中conn是新的套接字對(duì)象,可以用來(lái)接收和發(fā)送數(shù)據(jù)。address是連接客戶端的地址

2.1.2. 客戶端函數(shù)

connect函數(shù)
用來(lái)請(qǐng)求連接遠(yuǎn)程服務(wù)器

#Linux
int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);
sockfd : 為前面socket的返回值.
serv_addr : 為結(jié)構(gòu)體指針變量,存儲(chǔ)著遠(yuǎn)程服務(wù)器的IP與端口號(hào)信息
addrlen : 表示結(jié)構(gòu)體變量的長(zhǎng)度
返回值:成功則返回0,失敗返回-1

#python
s.connect(address)
s為socket.socket()返回的套接字對(duì)象
address : 格式為元組(hostname,port),如果連接出錯(cuò),返回socket.error錯(cuò)誤

2.1.3. 通用函數(shù)

接收遠(yuǎn)端主機(jī)傳來(lái)的數(shù)據(jù)

recv函數(shù)

#Linux
int recv(int sockfd,void *buf,int len,unsigned int flags);
sockfd : 為前面accept的返回值.也就是新的套接字。
buf : 表示緩沖區(qū)
len : 表示緩沖區(qū)的長(zhǎng)度
flags : 通常為0
返回值:成功則返回實(shí)際接收到的字符數(shù),可能會(huì)少于你所指定的接收長(zhǎng)度。失敗返回-1


#python
s.recv(bufsize[,flag])
s為socket.socket()返回的套接字對(duì)象
bufsize : 指定要接收的數(shù)據(jù)大小
flag : 提供有關(guān)消息的其他信息,通??梢院雎?返回值為數(shù)據(jù)以字符串形式

send函數(shù)
發(fā)送數(shù)據(jù)給指定的遠(yuǎn)端主機(jī)

#Linux
int send(int s,const void * msg,int len,unsigned int flags);
sockfd : 為前面socket的返回值.
msg : 一般為常量字符串
len : 表示長(zhǎng)度
flags : 通常為0
返回值:成功則返回實(shí)際傳送出去的字符數(shù),可能會(huì)少于你所指定的發(fā)送長(zhǎng)度。失敗返回-1

#python
s.send(string[,flag])
s為socket.socket()返回的套接字對(duì)象
string : 要發(fā)送的字符串?dāng)?shù)據(jù) 
flag : 提供有關(guān)消息的其他信息,通??梢院雎?返回值是要發(fā)送的字節(jié)數(shù)量,該數(shù)量可能小于string的字節(jié)大小。
s.sendall(string[,flag])
#完整發(fā)送TCP數(shù)據(jù)。將string中的數(shù)據(jù)發(fā)送到連接的套接字,但在返回之前會(huì)嘗試發(fā)送所有數(shù)據(jù)。
返回值 : 成功返回None,失敗則拋出異常。

close函數(shù)
關(guān)閉套接字

#Linux
int close(int fd);
fd : 為前面的sockfd
返回值:若文件順利關(guān)閉則返回0,發(fā)生錯(cuò)誤時(shí)返回-1

#python
s.close()
s為socket.socket()返回的套接字對(duì)象

2.2. 簡(jiǎn)單的客戶端服務(wù)器TCP連接

一個(gè)簡(jiǎn)單的回顯服務(wù)器和客戶端模型, 客戶端發(fā)出的數(shù)據(jù), 服務(wù)器會(huì)回顯到客戶端的終端上(只是一個(gè)簡(jiǎn)單的模型, 沒(méi)考慮錯(cuò)誤處理等問(wèn)題)

#服務(wù)器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket   #socket模塊
import commands   #執(zhí)行系統(tǒng)命令模塊


BUF_SIZE = 1024  #設(shè)置緩沖區(qū)大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構(gòu)成表示地址
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #生成一個(gè)新的socket對(duì)象
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  #設(shè)置地址復(fù)用
server.bind(server_addr)  #綁定地址
server.listen(5)  #監(jiān)聽(tīng), 最大監(jiān)聽(tīng)數(shù)為5
while True:
    client, client_addr = server.accept()  #接收TCP連接, 并返回新的套接字和地址
    print 'Connected by', client_addr
    while True :
        data = client.recv(BUF_SIZE)  #從客戶端接收數(shù)據(jù)
        print data
        client.sendall(data)  #發(fā)送數(shù)據(jù)到客戶端
server.close()
#客戶端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

BUF_SIZE = 1024  #設(shè)置緩沖區(qū)的大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構(gòu)成表示地址
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #返回新的socket對(duì)象
client.connect(server_addr)  #要連接的服務(wù)器地址
while True:
    data = raw_input("Please input some string > ")  
    client.sendall(data)  #發(fā)送數(shù)據(jù)到服務(wù)器
    data = client.recv(BUF_SIZE)  #從服務(wù)器端接收數(shù)據(jù)
    print data
client.close()

2.2.1. 帶錯(cuò)誤處理的客戶端服務(wù)器TCP連接

在進(jìn)行網(wǎng)絡(luò)編程時(shí), 最好使用大量的錯(cuò)誤處理, 能夠盡量的發(fā)現(xiàn)錯(cuò)誤, 也能夠使代碼顯得更加嚴(yán)謹(jǐn)

#服務(wù)器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket   #socket模塊

BUF_SIZE = 1024  #設(shè)置緩沖區(qū)大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構(gòu)成表示地址
try :
  server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #生成一個(gè)新的socket對(duì)象
except socket.error, msg :
    print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1]
    sys.exit()
print "Socket Created!"
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)  #設(shè)置地址復(fù)用
try : 
    server.bind(server_addr)  #綁定地址
except socket.error, msg :
  print "Binding Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1]
  sys.exit()
print "Socket Bind!"
server.listen(5)  #監(jiān)聽(tīng), 最大監(jiān)聽(tīng)數(shù)為5
print "Socket listening"
while True:
    client, client_addr = server.accept()  #接收TCP連接, 并返回新的套接字和地址, 阻塞函數(shù)
    print 'Connected by', client_addr
    while True :
        data = client.recv(BUF_SIZE)  #從客戶端接收數(shù)據(jù)
        print data
        client.sendall(data)  #發(fā)送數(shù)據(jù)到客戶端
server.close()
#客戶端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import sys
import socket


BUF_SIZE = 1024  #設(shè)置緩沖區(qū)的大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構(gòu)成表示地址
try : 
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  #返回新的socket對(duì)象
except socket.error, msg :
    print "Creating Socket Failure. Error Code : " + str(msg[0]) + " Message : " + msg[1]
    sys.exit()
client.connect(server_addr)  #要連接的服務(wù)器地址
while True:
    data = raw_input("Please input some string > ")  
    if not data :
        print "input can't empty, Please input again.."
        continue
    client.sendall(data)  #發(fā)送數(shù)據(jù)到服務(wù)器
    data = client.recv(BUF_SIZE)  #從服務(wù)器端接收數(shù)據(jù)
    print data
client.close()

2.3. UDP通信

UDP通信流程圖如下:
服務(wù)端:socket---bind---recvfrom---sendto---close
客戶端:socket----------sendto---recvfrom---close

UDP
UDP

sendto()函數(shù)
發(fā)送UDP數(shù)據(jù), 將數(shù)據(jù)發(fā)送到套接字

#Linux
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
sockfd : 為前面socket的返回值.
msg : 一般為常量字符串
len : 表示長(zhǎng)度
flags : 通常為0
to : 表示目地機(jī)的IP地址和端口號(hào)信息, 表示地址的結(jié)構(gòu)體
tolen : 常常被賦值為sizeof (struct sockaddr)
返回值 : 返回實(shí)際發(fā)送的數(shù)據(jù)字節(jié)長(zhǎng)度或在出現(xiàn)發(fā)送錯(cuò)誤時(shí)返回-1。


#Python
s.sendto(string[,flag],address)
s為socket.socket()返回的套接字對(duì)象
address : 指定遠(yuǎn)程地址, 形式為(ipaddr,port)的元組
flag : 提供有關(guān)消息的其他信息,通??梢院雎?返回值 : 發(fā)送的字節(jié)數(shù)。

recvfrom()函數(shù)
接受UDP套接字的數(shù)據(jù), 與recv()類似

#Linux
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
sockfd : 為前面socket的返回值.
msg : 一般為常量字符串
len : 表示長(zhǎng)度
flags : 通常為0
from :是一個(gè)struct sockaddr類型的變量,該變量保存連接機(jī)的IP地址及端口號(hào)
fromlen : 常置為sizeof (struct sockaddr)。
返回值 : 返回接收到的字節(jié)數(shù)或當(dāng)出現(xiàn)錯(cuò)誤時(shí)返回-1,并置相應(yīng)的errno。

#Python
s.recvfrom(bufsize[.flag])
返回值 : (data,address)元組, 其中data是包含接收數(shù)據(jù)的字符串,address是發(fā)送數(shù)據(jù)的套接字地址
bufsize : 指定要接收的數(shù)據(jù)大小
flag : 提供有關(guān)消息的其他信息,通常可以忽略

2.4. 簡(jiǎn)單的客戶端服務(wù)器UDP連接

#服務(wù)器端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket

BUF_SIZE = 1024  #設(shè)置緩沖區(qū)大小
server_addr = ('127.0.0.1', 8888)  #IP和端口構(gòu)成表示地址
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  #生成新的套接字對(duì)象
server.bind(server_addr)  #套接字綁定IP和端口
while True :
    print "waitting for data"
    data, client_addr = server.recvfrom(BUF_SIZE)  #從客戶端接收數(shù)據(jù)
    print 'Connected by', client_addr, ' Receive Data : ', data
    server.sendto(data, client_addr)  #發(fā)送數(shù)據(jù)給客戶端
server.close()
#客戶端
#!/usr/bin/env python
# -*- coding:utf-8 -*-

import socket
import struct

BUF_SIZE = 1024  #設(shè)置緩沖區(qū)
server_addr = ('127.0.0.1', 8888)  #IP和端口構(gòu)成表示地址
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  #生成新的套接字對(duì)象

while True :
    data = raw_input('Please Input data > ')
    client.sendto(data, server_addr)  #向服務(wù)器發(fā)送數(shù)據(jù)
    data, addr = client.recvfrom(BUF_SIZE)  #從服務(wù)器接收數(shù)據(jù)
    print "Data : ", data
client.close()

2.5. 其他

s.getpeername()
#返回連接套接字的遠(yuǎn)程地址。返回值通常是元組(ipaddr,port)。

s.getsockname()
#返回套接字自己的地址。通常是一個(gè)元組(ipaddr,port)

s.setsockopt(level,optname,value)
#設(shè)置給定套接字選項(xiàng)的值。

s.getsockopt(level,optname[.buflen])
#返回套接字選項(xiàng)的值。

s.settimeout(timeout)
#設(shè)置套接字操作的超時(shí)期,timeout是一個(gè)浮點(diǎn)數(shù),單位是秒。值為None表示沒(méi)有超時(shí)期。一般,超時(shí)期應(yīng)該在剛創(chuàng)建套接字時(shí)設(shè)置,因?yàn)樗鼈兛赡苡糜谶B接的操作(如connect())

s.gettimeout()
#返回當(dāng)前超時(shí)期的值,單位是秒,如果沒(méi)有設(shè)置超時(shí)期,則返回None。

s.fileno()
#返回套接字的文件描述符。

s.setblocking(flag)
#如果flag為0,則將套接字設(shè)為非阻塞模式,否則將套接字設(shè)為阻塞模式(默認(rèn)值)。非阻塞模式下,如果調(diào)用recv()沒(méi)有發(fā)現(xiàn)任何數(shù)據(jù),或send()調(diào)用無(wú)法立即發(fā)送數(shù)據(jù),那么將引起socket.error異常。

s.makefile()
#創(chuàng)建一個(gè)與該套接字相關(guān)連的文件

3. 參考鏈接


python-socket官方文檔

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、網(wǎng)絡(luò)各個(gè)協(xié)議:TCP/IP、SOCKET、HTTP等 網(wǎng)絡(luò)七層由下往上分別為物理層、數(shù)據(jù)鏈路層、網(wǎng)絡(luò)層、傳輸層...
    杯水救車薪閱讀 2,358評(píng)論 0 17
  • 最近在看《UNIX網(wǎng)絡(luò)編程 卷1》和《FREEBSD操作系統(tǒng)設(shè)計(jì)與實(shí)現(xiàn)》這兩本書(shū),我重點(diǎn)關(guān)注了TCP協(xié)議相關(guān)的內(nèi)容...
    腩啵兔子閱讀 1,278評(píng)論 0 7
  • 有一次,閨蜜問(wèn)我,所以以前那幾個(gè)你最愛(ài)的是誰(shuí)。 我說(shuō)我一直愛(ài)的就是他啊。其他人只是我年輕不懂事干出的混蛋事而已。 ...
    米飯醬吱吱閱讀 158評(píng)論 0 0
  • 韓信為劉邦的開(kāi)國(guó)功臣,劉氏的江山基本都是韓信的打下的,大家熟知的背水一戰(zhàn)和十面埋伏。就連項(xiàng)羽,最終也是被韓信逼迫...
    59b0515841d6閱讀 393評(píng)論 0 1
  • 這里首先觀察到的是鳥(niǎo)。 藍(lán)蒼鷺 白鷺 水道,兩邊是紅木林! 魚(yú)鷹 紅松林 林中魚(yú)鷹 水中橡樹(shù)林
    白鹿鯨閱讀 643評(píng)論 1 5

友情鏈接更多精彩內(nèi)容