TCP/IP简介
互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议,所以,大家把互联网的协议简称TCP/IP协议。
通信的时候,双方必须知道对方的标识,好比发邮件必须知道对方的邮件地址。互联网上每个计算机的唯一标识就是IP地址,类似10.132.3.186
。如果一台计算机同时接入到两个或更多的网络,比如路由器,它就会有两个或多个IP地址,所以,IP地址对应的实际上是计算机的网络接口,通常是网卡。IP地址实际上是一个32位整数(IPv4),如192.168.119.1
(把32位整数按8位分组后的数字表示)。IPv6是IPv4的升级版,是128位整数,类似于2001:0db8:85a3:0042:1000:8a2e:0370:7334
。
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。
TCP协议是建立在IP协议之上,负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。该协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。(常用的更高级的协议是建立在TCP协议上,比如SMTP协议)
一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。(为什么需要端口呢?只发IP地址是不够的,一个电脑上跑着多个应用程序,比如QQ和微信,一个TCP报文发送过来后,判断发给QQ还是微信,就是用端口来分辨的。每个网络程序都向操作系统申请唯一的端口号,两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。一个进程也可能同时与多个计算机建立链接,因此它会申请很多端口。)
TCP编程
客户端编程
以下代码是客户端向新浪新闻服务器发送TCP请求,然后将接收到的信息输出。
- 首先是创建一个socket连接,使用IPv4协议和TCP协议。
- 然后连接新浪新闻服务器,80端口是Web服务的标准端口,其他服务都有对应的标准端口号,例如SMTP服务是25端口,FTP服务是21端口,等等。
- 将接收到的数据进行输出。
import socket
# 创建socket AF_INET指定使用IPv4协议(IPv6-AF_INET6) SOCK_STREAM指定使用面向流的TCP协议
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 80端口是Web服务的标准端口。
# 其他服务都有对应的标准端口号: SMTP服务是25端口,FTP服务是21端口。
s.connect(('www.sina.com.cn', 80))
# 发送数据
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')# 接收数据
buffer = []
while True:
d = s.recv(1024)
if d:
# print(d)
buffer.append(d)
else:
break
data = b''.join(buffer)
header, html = data.split(b'\r\n\r\n')
print(data)
print(header.decode('utf-8'))
print(html.decode('utf-8'))
s.close()
服务端编程
以下代码是服务器端代码。
- 首先创建一个基于IPv4和TCP协议的Socket。
- 绑定了本地的9999端口(小于1024的端口号必须要有管理员权限才能绑定)。
- 设置最大连接数为5,由于考虑到服务器可能同时响应多个客户端的请求,所以,每个连接都需要一个新的进程或者新的线程来处理。
- tcplink函数:首先输出接受的连接的地址,然后向客户端发送hello信息,接受从客户端传输过来的字符串,然后拼接输出。
import socket
import threading
import time
def tcplink(sock, addr):
print("Accept new connection from %s:%s" % addr)
sock.send(b"hello !")
while True:
data = sock.recv(1024)
time.sleep(1)
if not data:
break
sock.send(("nihao, %s" % data.decode('utf-8')).encode('utf-8'))
sock.close()
print("connection from %s:%s close" % addr)
def main():
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本机地址和9999端口号
# 小于1024的端口必须要有管理员权限才能绑定
s.bind(('127.0.0.1', 9998))
# 指定等待连接的最大数量为5
s.listen(5)
print("Waiting for connection...")
while True:
sock, addr = s.accept()
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
if __name__ == "__main__":
main()
客户端发送命令,服务端接收命令并执行
为了实现客户端与以上的服务端通信,我们创建一个客户端,向服务端发送数据。连接建立后,服务器首先发一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 9998))
# 发送数据
print(s.recv(1024).decode('utf-8'))
for data in [b'qqqq', b'wwwww', b'eeeeee']:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.close()
结果如下:
UDP简介
UDP协议与TCP协议的不同在于,它是面向无连接,不可靠的数据报协议。而且不需要建立连接就可以,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。
UDP编程
和TCP类似,使用UDP的通信双方也分为客户端和服务器。
服务端编程
- SOCK_DGRAM指定了这个Socket的类型是UDP。
- recvfrom()方法返回数据和客户端的地址与端口,服务器收到数据后,直接调用sendto()就可以把数据用UDP发给客户端。
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(('127.0.0.1', 9998))
print("bind UDP on 9998...")
while True:
data, addr = s.recvfrom(1024)
print('Received from %s:%s', addr)
s.sendto(b'Hello, %s' % data, addr)
客户端编程
- 不需要调用connect(),直接通过sendto()给服务器发数据。
- 从服务器接收数据仍然调用recv()方法。
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'qqq', b'www', b'eee']:
s.sendto(data, ('127.0.0.1', 9998))
print(s.recv(1024).decode('utf-8'))
s.close()
结果如下: