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

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

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)連的文件