Jusene's Blog

python网络编程之socket

字数统计: 3k阅读时长: 15 min
2017/10/15 Share

socket

socket源于unix,而unix/linux基本哲学“一切皆文件”,socket有两种,分别是基于文件的和基于网络的。基于文件可用于两个进程运行同一服务器上,socket基于文件的形式提供,这样的效率比通过基于网络的要快。但是需要与外部服务器进行通信,我们就需要基于网络的socket。

socket通信

socket方法总结

1
socket(socket_family,socket_type,protocol=0) -- create a new socket object  创建一个新的socket对象
2
#TCP/IP socket对象  
3
>>> sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4
#UDP/IP socket对象 
5
>>> sk=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
6
#文件TCP
7
>>> sk=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
8
#文件UDP
9
>>> sk=socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM)
10
11
socketpair(socket_family,socket_type,protocol=0) -- create a pair of new socket objects [*] 创建一对新的socket对象,常用于父子进程通信,默认socket_family是AF_UNIX
12
>>> parent,child=socket.socketpair()
13
14
gethostname() -- return the current hostname  #返回当前的主机名
15
>>> socket.gethostname()
16
'ZGXMacBook-Pro.local'
17
18
gethostbyname() -- map a hostname to its IP number #通过主机名映射得到ip
19
>>> socket.gethostbyname(socket.gethostname())
20
'192.168.1.101'
21
22
gethostbyaddr() -- map an IP number or hostname to DNS info #通过主机名或者ip映射得到dns信息
23
>>> socket.gethostbyaddr('ZGXMacBook-Pro.local')
24
('zgxmacbook-pro.local', ['9.f.9.4.3.d.9.7.6.5.3.6.d.d.c.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.e.f.ip6.arpa'], ['fe80::cdd:6356:79d3:49f9'])
25
26
getservbyname() -- map a service name and a protocol name to a port number #通过一个服务名或者协议名映射到端口号
27
>>> socket.getservbyname('ssh')
28
22
29
30
getprotobyname() -- map a protocol name (e.g. 'tcp') to a number #映射一个协议名到数字
31
>>> socket.getprotobyname('tcp')
32
6
33
>>> socket.getprotobyname('udp')
34
17
35
>>> socket.getprotobyname('icmp')
36
1
37
38
socket.getdefaulttimeout() -- get the default timeout value #得到默认超时时间
39
>>> socket.getdefaulttimeout()
40
>>> 
41
42
socket.setdefaulttimeout() -- set the default timeout value
43
>>> socket.setdefaulttimeout(10)
44
>>> socket.getdefaulttimeout()
45
10.0
46
47
create_connection(address,timeout,source_address=None) -- connects to an address, with an optional timeout and
48
                       optional source address.
49
>>> socket.create_connection(('www.baidu.com',80),10)
50
<socket.socket fd=6, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('192.168.1.101', 52735), raddr=('111.13.12.139', 80)>

socket套接字内建方法

1
# 服务器端
2
sk.bind(address) => sk.bind将套接字绑定到地址。address地址的格式取决于socket_family,在AF_INET中,以元组(addr,port)的形式表示,在AF_UNIX中,则是以文件的方式表示。
3
sk.listen(backlog) => 开始监听传入连接。backlog指定在拒绝连接之前,可以挂起的最大连接数。
4
sk.setblocking(bool) => 是否阻塞,如果设置false,那么accept和recv一旦无数据就报错。
5
sk.accept() => 接受连接并返回对象,阻塞等待客户端连接,返回(conn,addr),这里的conn是新的套接字对象,可以用来发送数据和接受数据,addr是客户端的地址。
6
7
# 客户端
8
sk.connect(address) => 连接address的socket套接字,一般的格式为元组(addr,port)。
9
sk.connetc_ex(address) => connect函数的扩展版本,连接失败不报错而是返回错误码。
10
11
# 公共函数
12
sk.recv(bufsize[,flag]) => 接受套接字数据,数据以bytes返回,bufsize指定最多可以接受的数量。
13
sk.recvfrom(bufsize[,flag]) => 与recv相似,返回的是data,addr info
14
sk.send(string[,flag]) => 将string发送到连接的套接字,返回值是要发送的字节的数量,这里python2和3是有区别的,python2中发送str没有问题,而python3需要发送bytes
15
sk.sendall(string[,flag]) => 将str中的数据发送到连接的套接字,但在返回之前会尝试发送所以数据,成果返回None,失败抛出异常,内部通过递归send实现
16
sk.sendfile(file[, offset[, count]]) => file必须为二进制数据流,offet是偏移位,count是发送的数据量
17
sk.sendto(string[,flag],address) => 将数据发送到套接字,address的形式为(addr,port)的元组,指定远程地址。返回值是发送的字节数,该函数主要使用于udp
18
sk.settimeout(timeout) => 设置阻塞套接字操作的超时期,timeout是一个浮点数,单位秒,值为None表示没有超时。
19
sk.gettimeout(timeout) => 得到阻塞套接字的超时时间。
20
sk.getpeername() => 返回连接套接字的远程地址,返回值(addr,port)。
21
sk.getsockname() => 返回当前套接字自己的地址,返回值(addr,port)。
22
sk.setsockopt() => 设置指定套接字的参数,常用的(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
23
sk.getsockopt() => 得到指定套接字的参数
24
sk.close() => 关闭套接字
25
26
# 面向文件的套接字函数
27
sk.fileno() => 套接字的文件描述符
28
sk.makefile() => 创建一个与该套接字关联的文件对象

TCP服务器

创建一个socket TCP服务端(python2.7)

1
import socket
2
try:
3
	sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #创建TCP/IP套接字
4
	sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
5
	sk.bind(('127.0.0.1',1234))
6
	sk.listen(10)
7
	conn,addr=sk.accept()
8
	print('connect from {}'.format(addr))
9
	while True:
10
		data=conn.recv(1024)
11
		if data:
12
			print(data)
13
			conn.sendall('I receive data "{}"'.format(data))
14
except Exception as e:
15
	print(e)
16
finally:
17
	conn.close()
18
	sk.close()

创建一个socket TCP客户端

1
import socket
2
3
sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4
sk.connect(('127.0.0.1',1234))
5
while True:
6
	data=raw_input('>')
7
	sk.sendall(data)
8
	ret=sk.recv(1024)
9
	print(ret)
10
sk.close()

测试:

1
[root@INIT ~]# python server.py 
2
connect from ('127.0.0.1', 58777)
3
test
4
hello python
1
[root@INIT ~]# python client.py 
2
>test
3
I receive data "test"
4
>hello python
5
I receive data "hello python"
6
>

以上的测试在python2.7可信,而在python3中需要做点修改。

1
import socket
2
try:
3
	sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)   #创建TCP/IP套接字
4
	sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
5
	sk.bind(('127.0.0.1',1234))
6
	sk.listen(10)
7
	conn,addr=sk.accept()
8
	print('connect from {}'.format(addr))
9
	while True:
10
		data=conn.recv(1024)
11
		if data:
12
			print(data.decode())
13
			conn.sendall('I receive data "{}"'.format(data.decode()).encode())
14
except Exception as e:
15
	print(e)
16
finally:
17
	conn.close()
18
	sk.close()
1
import socket
2
try:
3
	sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4
	sk.connect(('127.0.0.1',1234))
5
	while True:
6
		data=input('>')
7
		sk.sendall(data.encode())
8
		ret=sk.recv(1024)
9
		print(ret.decode())
10
except Exception as e:
11
	print(e)
12
finally:
13
	sk.close()

UDP服务器

创建一个socket UDP服务端(python2.7)

1
import socket
2
try:
3
	sk=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
4
	sk.bind(("127.0.0.1",1234))
5
	while True:
6
		data,addr=sk.recvfrom(1024)
7
		print('connect from {}'.format(addr))
8
		if data:
9
			print(data)
10
			sk.sendto('I receive data "{}"'.format(data),addr)
11
except Exception as e:
12
	print(e)
13
finally:
14
	sk.close()

创建一个socket UDP客户端

1
import socket
2
try:
3
	sk=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
4
	while True:
5
		data=raw_input('>')
6
		sk.sendto(data,('127.0.0.1',1234))
7
		data,addr=sk.recvfrom(1024)
8
		print(data)
9
except Exception as e:
10
	print(e)
11
finally:
12
	sk.close()

测试:

1
[root@INIT ~]# python server.py 
2
connect from ('127.0.0.1', 35515)
3
test
1
[root@INIT ~]# python client.py 
2
>test
3
I receive data "test"
4
>

文件socket TCP

python2.7

1
import socket
2
import os
3
sock_file='./tcp.sock'
4
try:
5
	sk=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)   
6
	sk.bind(sock_file)
7
	sk.listen(10)
8
	conn,addr=sk.accept()
9
	while True:
10
		data=conn.recv(1024)
11
		if data:
12
			print(data)
13
			conn.sendall('I receive data "{}"'.format(data))
14
except Exception as e:
15
	print(e)
16
finally:
17
	conn.close()
18
	sk.close()
19
	os.remove(sock_file)
1
import socket
2
sock_file='./tcp.sock'
3
sk=socket.socket(socket.AF_UNIX,socket.SOCK_STREAM)
4
sk.connect(sock_file)
5
while True:
6
	data=raw_input('>')
7
	sk.sendall(data)
8
	ret=sk.recv(1024)
9
	print(ret)
10
sk.close()

测试:

1
[root@INIT ~]# python server.py 
2
test
1
[root@INIT ~]# python client.py 
2
>test
3
I receive data "test"
4
>

文件socket UDP

python 2.7

1
import socket
2
import os
3
4
sock_file='./server.sock'
5
sock_file_1='./client.sock'
6
7
try:
8
	sk=socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM)
9
	sk.bind(sock_file)
10
	while True:
11
		data,addr=sk.recvfrom(1024)
12
		if data:
13
			print(data)
14
			sk.sendto('I receive data "{}"'.format(data),sock_file_1)
15
		else:
16
			sk.sendto('None',sock_file_1)
17
except Exception as e:
18
	print(e)
19
finally:
20
	sk.close()
21
	os.remove(sock_file)
1
import socket
2
sock_file='./server.sock'
3
sock_file_1='./client.sock'
4
5
try:
6
	sk=socket.socket(socket.AF_UNIX,socket.SOCK_DGRAM)
7
	sk.bind(sock_file_1)
8
	while True:
9
		data=raw_input('>')
10
		sk.sendto(data,sock_file)
11
		data.addr=sk.recvfrom(1024)
12
		if data:
13
			print(data)
14
except Exception as e:
15
	print(e)
16
finally:
17
	sk.close()

socket服务器的其他应用

web服务器

python 3.0

1
import socket
2
try:
3
    sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
5
    sk.bind(("0.0.0.0",9900))
6
    sk.listen(10)
7
    response_header='HTTP/1.1 200 ok \nAccept-Ranges:bytes \nContent-Type:text/html;charset=utf-8 \nContent-Length:'
8
    while True:
9
        conn,addr=sk.accept()
10
        data=conn.recv(1024)
11
        print(data.decode())
12
        ret='Hello World!'
13
        response_data=response_header.encode()+str(len(ret)).encode()+'\n\n'.encode()+ret.encode()
14
        print(response_data)
15
        conn.sendall(response_data)
16
except Exception as e:
17
    print(e)
18
finally:
19
    conn.close()
20
    sk.close()

测试:

1
➜  ~ python web.py
2
GET / HTTP/1.1
3
Host: 127.0.0.1:9900
4
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:56.0) Gecko/20100101 Firefox/56.0
5
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
6
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
7
Accept-Encoding: gzip, deflate
8
Cookie: Hm_lvt_2e6d7900beb38f4209678283d770cce5=1504283591
9
Connection: keep-alive
10
Upgrade-Insecure-Requests: 1
11
12
13
b'HTTP/1.1 200 ok \nAccept-Ranges:bytes \nContent-Type:text/html;charset=utf-8 \nContent-Length:12\n\nHello World!'

图片服务器

python 3.0

1
import socket
2
try:
3
    sk=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4
    sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
5
    sk.bind(("0.0.0.0",9900))
6
    sk.listen(10)
7
    response_header='HTTP/1.1 200 ok \nAccept-Ranges:bytes \nContent-Type:image/png;charset=utf-8 \nContent-Length:'
8
    while True:
9
        conn,addr=sk.accept()
10
        data=conn.recv(1024)
11
        print(data.decode())
12
        with open('test.png','rb') as f:
13
        	ret=f.read()
14
        	response_data=response_header.encode()+str(len(ret)).encode()+'\n\n'.encode()+ret
15
        print(response_data)
16
        conn.sendall(response_data)
17
except Exception as e:
18
    print(e)
19
finally:
20
    conn.close()
21
    sk.close()

测试:

父子进程通信(socketpair)

python 3.0

1
import socket
2
import os
3
4
parent,child=socket.socketpair()
5
pid=os.fork()
6
7
if pid:
8
	print('in parent,sending message')
9
	child.close()
10
	parent.sendall('ping'.encode())
11
	response=parent.recv(1024)
12
	print('response from child:',response.decode())
13
	parent.close()
14
else:
15
	print('in child,waiting for message')
16
	parent.close()
17
	message=child.recv(1024)
18
	print('message from parent:',message.decode())
19
	child.sendall('pong'.encode())
20
	child.close()

测试:

1
~]# python ping.py
2
in parent,sending message
3
in child,waiting for message
4
message from parent: ping
5
response from child: pong

I/O多路复用

I/O多路复用指:通过一种机制,可以监视多个socket,一旦某个描述符就绪,就通知程序进行相应的操作。
在Linux中poll,select,epoll都是I/O多路复用机制,Linux下使用socket来进行通信,普通I/O模型只监听一个socket,而I/O多路复用可同时监听多个socket,I/0多路复用避免阻塞在io上,原本多进程或多线程接受多个连接的消息变为单进程或单线程保存多个socket的状态后轮询处理。

python中select模块可以提供select、poll、epoll三个方法的分别调用select,poll,epoll从而实现IO多路复用。

模拟select,同时监听多个端口

python 2.7

1
import socket
2
import select
3
4
sk1=socket.socket()
5
sk1.bind(('0.0.0.0',8000))
6
sk1.listen(10)
7
8
sk2=socket.socket()
9
sk2.bind(('0.0.0.0',8001))
10
sk2.listen(10)
11
12
sk3=socket.socket()
13
sk3.bind(('0.0.0.0',8003))
14
sk3.listen(10)
15
16
inputs=[sk1,sk2,sk3,]
17
18
while True:
19
        r_list,w_list,x_list=select.select(inputs,[],[],1)
20
        for sk in r_list:
21
                conn,addr=sk.accept()
22
                print('connect from {}'.format(addr))
23
                conn.sendall('hello python!')
24
                conn.close()
25
26
        for sk in x_list:
27
                inputs.remove(sk)

解释:

  • select内部自动监听sk1,sk2,sk3三个对象socket,监听这三个句柄是否发生变化,把发生变化的加入r_list
  • select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist),rlist等待直到有读操作,wlist等待直到有写操作,xlist等到知道出现错误,timeout表示监听间隔
  • 当有人连入sk1,r_list=[sk1]

测试:

1
[root@INIT ~]# python server.py 
2
connect from ('127.0.0.1', 58351)
3
connect from ('127.0.0.1', 37359)
4
connect from ('127.0.0.1', 58354)
1
import socket
2
3
4
ports=[8000,8001,8003]
5
for port in ports:
6
        sk=socket.socket()
7
        sk.connect(('127.0.0.1',port))
8
        ret=sk.recv(1024)
9
        print(ret)
10
        sk.close()
11
12
[root@INIT ~]# python client.py 
13
hello python!
14
hello python!
15
hello python!

模拟多线程,是多用户可以同时连接

1
import socket
2
import select
3
4
sk=socket.socket()
5
sk.bind(('0.0.0.0',9000))
6
sk.listen(10)
7
8
inputs=[sk,]
9
outputs=[]
10
message_dict={}
11
12
while True:
13
	r_list,w_list,x_list=select.select(inputs,outputs,[],1)
14
	print('listen socket number: {}'.format(len(inputs)))
15
	for r in r_list:
16
		if r == sk:
17
			#表示新的用户连接
18
			conn,addr=r.accept()
19
			inputs.append(conn)
20
			message_dict[conn]=[]
21
		else:
22
			#老用户发信息
23
			try
24
				data=r.recv(1024)
25
			except Exception as e:
26
				inputs.remove(r)
27
			else:
28
				message_dict[r].append(data)
29
				outputs.append(r)
30
31
	#w_list记录了谁发过消息
32
	for conn in w_list:
33
		recv_str=message_dict[conn][0]
34
		message_dict[conn].pop(0)
35
		conn.sendall(recv_str+' ok')
36
		outputs.remove(conn)
CATALOG
  1. 1. socket
  2. 2. socket通信
    1. 2.1. socket方法总结
    2. 2.2. socket套接字内建方法
  3. 3. TCP服务器
  4. 4. UDP服务器
  5. 5. 文件socket TCP
  6. 6. 文件socket UDP
  7. 7. socket服务器的其他应用
    1. 7.1. web服务器
    2. 7.2. 图片服务器
  8. 8. 父子进程通信(socketpair)
  9. 9. I/O多路复用
    1. 9.1. 模拟select,同时监听多个端口
    2. 9.2. 模拟多线程,是多用户可以同时连接