创建一个基于 TCP 连接的 Socket:
- # coding: utf-8
- import socket
- # 创建一个TCP连接:
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # 建立连接:
- s.connect(('52fhy.com', 80))
- # 发送HTTP请求
- s.send(b'GET / HTTP/1.1\r\nHost: 52fhy.com\r\nConnection: close\r\n\r\n')
- # 读取响应
- buffer = []
- while True:
- d = s.recv(1024)
- if d:
- buffer.append(d)
- else:
- break
- data = b''.join(buffer)
- # 关闭连接
- s.close()
- # 解析响应
- header, body = data.split(b'\r\n\r\n', 1)
- print(header.decode('utf-8'))
- with open('52fhy.html', 'wb') as f:
- f.write(body)
输出:
- HTTP/1.1 200 OK
- Server: nginx/1.4.4
- Date: Sat, 11 Feb 2017 08:16:43 GMT
- Content-Type: text/html
- Content-Length: 8368
- Last-Modified: Mon, 19 Sep 2016 07:04:02 GMT
- Connection: close
- Vary: Accept-Encoding
- ETag: "57df8de2-20b0"
- Accept-Ranges: bytes
代码说明:
1、创建 socket 连接的时候使用
指定使用 IPv4 协议,如果要用更先进的 IPv6,就指定为
- AF_INET
。
- AF_INET6
指定使用面向流的 TCP 协议。 2、建立连接的
- SOCK_STREAM
接受一个 tuple,包含地址和端口号。 3、发送请求是模拟浏览器发送一个 Request,实际浏览器请求时包含更多项:
- connect()
- GET / HTTP / 1.1 Host: 52fhy.com Connection: keep - alive Cache - Control: max - age = 0 Accept: text / html,
- application / xhtml + xml,
- application / xml;
- q = 0.9,
- image / webp,
- *
- /*;q=0.8
- Upgrade-Insecure-Requests: 1
- User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
- Accept-Encoding: gzip, deflate, sdch
- Accept-Language: zh-CN,zh;q=0.8,en;q=0.6*/
换行使用
,
- \r\n
则表示该段内容的结束,用于分隔请求的 header 和请求的 body。 4、读取远程服务器响应则使用
- \r\n\r\n
每次接受 1024byte 内容。注意接收的是字节内容,不是字符串,所以拼接以
- recv()
开头。 5、读取响应完毕,关闭 socket 连接。 6、通过
- b
可以区分响应头和返回的 body 内容(即网页),这里将网页内容保存到了文件里。
- \r\n\r\n
上节例子说明了如何编写客户端,这节讲如何创建一个基于 TCP 的 Server 端。
和客户端编程相比,服务器编程就要复杂一些。
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来了,服务器就与该客户端建立 Socket 连接,随后的通信就靠这个 Socket 连接了。
- # coding: utf-8
- import socket, threading
- # 创建一个TCP连接
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # 绑定IP和端口
- s.bind(('127.0.0.1', 9999))
- # 开始监听客户端连接
- # 传入的参数指定等待连接的最大数量
- s.listen(3)
- # 输出提示语
- print('Server is running on %s:%s' % ('127.0.0.1', 9999))
- print('Waiting for connection...')
- def tcp_link(sock, addr):
- print('Accept new connection from %s:%s...' % addr) # 参数addr是一个tuple
- sock.send(b'Welcome!') # 发送问候语给客户端
- while True:
- r = sock.recv(1024)
- if not r or r.decode('utf-8') == 'exit': # 结束连接指令
- break
- # 输出客户端发来的信息,注意元组拼接
- msg = addr + (r.decode('utf-8'),)
- print('%s:%s : %s' % msg)
- # 回复客户端
- sock.send( ('you say: %s' % r.decode('utf-8') ).encode('utf-8') )
- sock.close()
- print('Client %s:%s is closed.' % addr)
- while True:
- # 接受一个新连接:
- sock, addr = s.accept()
- print(addr) # ('127.0.0.1', 62090)
- # 创建新线程来处理TCP连接:
- # 每个连接都必须创建新线程(或进程)来处理,否则,单线程在处理连接的过程中,无法接受其他客户端的连接
- t = threading.Thread(target=tcp_link, args=(sock, addr))
- t.start()
还需要个客户端发送消息:
- # coding: utf-8
- import socket
- # 创建一个TCP连接
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- # 连接服务端
- s.connect(('127.0.0.1', 9999))
- # 接收来自服务端的信息
- print(s.recv(1024).decode('utf-8'))
- # 发送数据给服务端
- for x in [b'Python', b'PHP']:
- s.send(x)
- print(s.recv(1024).decode('utf-8'))
- # 发送断开连接命令
- s.send(b'exit')
- s.close()
先运行服务端:
- erver is running on 127.0.0.1:9999
- Waiting for connection...
这时候运行一个客户端:
- Welcome!
- you say: Python
- you say: PHP
服务端输出:
- ('127.0.0.1', 62428)
- Accept new connection from 127.0.0.1:62428...
- 127.0.0.1:62428 : Python
- 127.0.0.1:62428 : PHP
- Client 127.0.0.1:62428 is closed.
编写代码需要注意:
1、服务端和客户端互相发送和接收到的是字节 Bytes,需要使用
或者
- encode()
转码; 2、服务端使用
- decode()
接收到的是一个元组,例如:
- accept()
; 3、服务端必须使用多线程以处理来自多个客户端的连接; 4、服务端同一个端口,被一个 Socket 绑定了以后,就不能被别的 Socket 绑定了。
- ('127.0.0.1', 62090)
TCP 建立的是可靠的连接,而使用 UDP,无需建立连接,只要知道对方的 IP 和端口,就可以发送数据。
UDP 是一个非连接的协议,传输数据之前源端和终端不建立连接。UDP 使用尽最大努力交付,即不保证可靠交付。
使用 UDP 虽然传输数据不可靠,但比 TCP 要快。
user_udp_server.py:
- # coding: utf-8
- import socket
- # 建立UDP连接:SOCK_DGRAM表示UDP
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- # 绑定地址和端口,之后无需监听
- s.bind(('127.0.0.1', 9999))
- print('UDP server is running on %s:%s' % ('127.0.0.1', 9999))
- while True:
- # recvfrom()返回客户端发过来的数据及地址端口信息
- data, addr = s.recvfrom(1024)
- msg = addr + (data.decode('utf-8'),)
- print('Received from %s:%s : %s' % msg)
- # 使用sendto()发送消息,注意第二个参数是addr
- s.sendto(('you say : %s ' % data.decode('utf-8')).encode('utf-8') , addr)
user_udp_client.py
- # coding: utf-8
- import socket
- # 建立UDP连接:SOCK_DGRAM表示UDP
- s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
- # 服务端地址
- serv_addr = ('127.0.0.1', 9999)
- #无需使用connect()连接
- # 发送数据给服务端
- for x in [b'Python', b'PHP']:
- s.sendto(x, serv_addr)
- print(s.recv(1024).decode('utf-8'))
- # s.close()
先运行服务端:
- UDP server is running on 127.0.0.1:9999
再运行客户端:
- you say : Python
- you say : PHP
服务端输出:
- Received from 127.0.0.1:57827 : Python
- Received from 127.0.0.1:57827 : PHP
与 TCP 不同的是:
1、服务端和客户端建立连接时选择 UDP:
; 2、服务端无需进行监听
- SOCK_DGRAM
; 3、服务端使用
- listen()
返回客户端发过来的数据及地址端口信息,而不是
- recvfrom()
或者
- recv()
;
- accept()
发送数据,第二个参数必填,是 addr 元组; 5、客户端创建连接后无需使用
- sendto()
连接服务端;
- connect()
这里服务端我们没有使用多线程,因为比较简单。需要注意的是客户端发送数据如果加入一句
, 本次连接关闭,服务端也会退出。
- s.close()
来源: