[toc]

怎么来模拟一下我们日常的网络连接呢?

网络连接首先要理解成为信息数据的传输。在传输层中我们需要符合一些“规则”,让数据们“符合规范”,才能在网络中进行传输,这也是理所当然且可以理解的事情。一旦没有了规则,做什么事都会乱套

一般有两套规则可以使用:TCP和UDP协议。

1 什么叫TCP呢?

TCP(Transmission Control Protocol),叫做传输控制协议,是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接。一个TCP连接必须要经过三次“对话”才能建立起来,也就是俗称的“三次握手”。三次握手简单来说是这样的:A向B发出连接请求数据包:“我想给你发数据,可以吗?”,这是第一次对话;B向A发送同意连接和要求同步(同步就是两台主机一个在发送,一个在接收,协调工作)的数据包:“可以,你什么时候发?”,这是第二次对话;A再发出一个数据包确认B的要求同步:“我现在就发,你接着吧!”,这是第三次对话。三次“对话”的目的是使数据包的发送和接收同步,经过三次“对话”之后,A才向B正式发送数据。

归纳一下,就是TCP规则下,双方先试着传一下数据看看能否接受同步,如果可以就认为是建立好连接了,接下来就可以正式进行数据的传输。

2 什么叫UDP呢?

UDP(User Data Protocol,用户数据报协议)应该和TCP差不多都是一种数据传输规则。差别在于UDP是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去!

UDP适用于一次只传送少量数据、对可靠性要求不高的应用环境。比如,我们经常使用“ping”命令来测试两台主机之间TCP/IP通信是否正常,其实“ping”命令的原理就是向对方主机发送ICMP数据包,然后对方主机确认收到数据包,如果数据包是否到达的消息及时反馈回来,那么网络就是通的。例如,在默认状态下,一次“ping”操作发送4个数据包(可以在cmd中执行ping www.endcat.cn)。可以看到,发送的数据包数量是4包,收到的也是4包(因为对方主机收到后会发回一个确认收到的数据包)。这充分说明了UDP协议是面向非连接的协议,没有建立连接的过程。正因为UDP协议没有连接的过程,所以它的通信效率高;但也正因为如此,它的可靠性不如TCP协议高。QQ就使用UDP发消息,因此有时会出现收不到消息的情况。

3 接下来我们来模拟吧

3.1 TCP连接模拟

3.1.1 首先要知道一下Socket是什么东西

谷歌翻译socket是插座的意思?!但是好像在网络编程里面并不是“插座”的意思鸭…

网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。等一下插座这个意思可以解释的通。想象一下哈,我们有两台主机,各自带有一个插孔(插孔就是Socket啦),现在又有一条两端都是插头的绳子,两端插在两台主机上面使得主机之间可以通信。这条绳子就差不多是像网线一样的东西吧,应该是这样的(自言自语)。所以建立网络通信连接至少要一对端口(socket)。

socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。句柄就是当一个应用程序要引用其他系统(如数据库、操作系统)所管理的内存块或对象时,就要使用句柄来工作。 在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。Socket正如其英文原义那样,像一个多孔插座。一台主机犹如布满各种插座的房间,每个插座有一个编号,有的插座提供220伏交流电, 有的提供110伏交流电,有的则提供有线电视节目。 客户软件将插头插到不同编号的插座,就可以得到不同的服务。

3.1.2 简单的TCP客户端(Client)

客户端是什么意思?简单的来说,主动发起连接的就是客户端,被动接受连接的就是服务器(server) 。举个例子,当我们在浏览器中访问网站时,我们自己的计算机就是客户端,浏览器会主动向网站的服务器发起连接。如果一切顺利,网站的服务器接受了我们的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容了。

既然要发起连接,前面说过了,我们应该要有一个socket,这样才能和远程网站服务器的socket进行连接,这样我们才能看到网站的信息。

所以接下来我们要模拟创建一个socket。

#利用python来创建一个socket
#首先导入一个socket库,里面提供了创建socket的必要函数
import socket
#创建一个socket,名字叫做s
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#开始建立连接
s.connect(('127.0.0.1', 9999))
#接受数据并把它们打印出来
print(s.recv(1024).decode('utf-8'))
#从客户端发送给服务器一些数据
for data in [b'Aumi', b'Endcat']:

    s.send(data)
    print(s.recv(1024).decode('utf-8'))
#发送一个exit信息,表示我要退出连接辽
s.send(b'exit')
#关闭socket,本次连接结束
s.close()

有几点地方要注意一下:

  • connect函数要注意参数是一个元组tuple,包含地址和端口号。
  • 端口号小于1024的是Internet标准服务的端口,可能需要管理员权限。端口号大于1024的,可以任意使用。
  • 创建socket时,AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。SOCK_STREAM指定使用面向流的TCP协议,这样,一个socket对象就创建成功,但是还没有建立连接。
  • 127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时在本机运行才能连接,也就是说,外部的计算机无法连接进来。
  • 接收数据时,调用recv(max)方法,一次最多接收指定的字节数 .

3.1.3 简单的TCP服务器(Server)

现在假设我们是一个服务器了,我们需要接受远程客户端发来的连接请求。不管怎么说,创建socket是头等大事,之后我们要按照对方发来的数据进行分析,并返回给客户端数据。

#引用一些库,某些不一样的地方会在后面提示
import socket
import threading
import time

#新线程的函数
def tcplink(sock, addr):
    print('Accept new connection from %s:%s...' % addr)
    #发送一个欢迎标识
    sock.send(b'Welcome!')
    #死循环接受客户端数据
    while True:
        data = sock.recv(1024)
        #哦,在这里停顿一下
        time.sleep(1)
        #如果没有数据或者接收到了exit,那么就跳出循环
        if not data or data.decode('utf-8') == 'exit':
            break
        #发送打招呼的数据
        sock.send(('Hello, %s' % data.decode('utf-8')).encode('utf-8'))
    #关闭连接,本次连接结束
    sock.close()
    print('Connection from %s:%s closed.' % addr)

#创建一个socket对象,还是熟悉的味道
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#绑定接口,注意参数tuple
s.bind(('127.0.0.1', 9999))
#开始监听有无数据到达,参数表示最大等待链接数量
s.listen(5)
print('Waiting for connection...')
#死循环接受客户端连接
while True:
    #接收到了客户端请求
    sock, addr = s.accept()
    #创建一个线程来处理改客户端数据,多线程可以同时处理不同客户端的请求
    t = threading.Thread(target=tcplink, args=(sock, addr))
    t.start()

3.1.4 开始连接尝试

可以打开两个终端,先运行server再运行client,看看两个终端各自返回了什么信息。接下来的UDP也是同理哦。

3.2 UDP连接模拟

相比TCP,感觉UDP更加简单一点。其实原因也是显而易见的,前面说过UDP是不讲究连接的可靠性而是直接把数据发送过去。在代码量上也可以看出UDP更加简单。

3.2.1 简单的UDP客户端(Client)

import socket
#DGRAM指定了UDP
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

for data in [b'Endcat', b'Aumi']:
    #不需要connect,直接使用sendto发送数据
    s.sendto(data, ('127.0.0.1', 9999))

    print(s.recv(1024).decode('utf-8'))

s.close()

3.2.2 简单的UDP服务器(Server)

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

s.bind(('127.0.0.1', 9999))

print('Bind UDP on 9999...')
while True:
    #recvfrom返回数据端口信息
    data, addr = s.recvfrom(1024)
    print('Received from %s:%s.' % addr)
    s.sendto(b'Hello, %s!' % data, addr)

#这里省略了多线程

同样的,可以自己开两个终端进行实验。


Melancholy.