socket:套接字是作为端点在服务器端和客户端程序之间建立双向网络通信链路的软件对象。在 UNIX 中,套接字也可以被称为操作系统(OS)中进程间通信(IPC)的端点。可以把进程比作房子,而socket就是房门。应用相当于房子内的一侧,而传输层协议相当于房门外的一侧。
包括两种传输层服务的socket类型:
TCP:可靠的、字节流的服务、面向连接(connection-oriented)
UDP:不可靠(对数据的递送无保证)服务、无连接、发送独立的数据报
什么是可靠?
传过去是什么,那边收到的就是什么,原原本本,不丢失
字节流:
A reliable byte stream is a common service paradigm in computer networking; it refers to a byte stream in which the bytes which emerge from the communication channel at the recipient are exactly the same, and in exactly the same order, as they were when the sender inserted them into the channel.
面向连接:
A connection between client and server is established before data can be sent
The server must be listening (passive open) for connection requests from clients before a connection is established.
Three-way handshake (active open), retransmission, and error detection adds to reliability but lengthens latency.
一个典型的网络应用包括一个客户端程序和一个服务端程序,对应两个不同的端点系统。当执行这两个程序时,客户端进程和服务端进程生成了,这两个进程会通过向socket读写来进行交流。
有两种网络应用。一种是遵循协议标准的,例如RFC或者其他标准文档。
RFC:Request For Comments.它是一个描述 Internet 和 TCP/IP 的标准、协议和技术的文档。自1969年以来,已经发布了大约2400个关于各种网络协议、程序、应用程序和概念的评论请求(RFC)
另一种是专有的网络应用,客户端和服务端程序遵循的并不是开源的RFC或者其他文档。这种情况下开发者需要同时写客户端和服务端的程序,并且由于协议不开源,其他开发者不能够参与开发。
使用UDP时,在发送进程(sending process)可以通过socket发出数据报之前,必须要给数据报附上这段数据的目的地的地址。 在数据报通过发送方的socket之后,Internet会根据地址路由到接收进程(receiving process)。 当数据报到达接收方的socket时,接收进程将通过socket检索数据报,然后检查数据报的内容并采取适当的行动。
那么附着在数据报中的地址包含了哪些内容呢?你可能会想到IP地址会是目的地地址的一部分,通过在数据报中包含IP地址,Internet中的路由器就可以将数据报发送到目的地主机(host)。但是一个host可能运行着许多的网络应用进程,每一个进程都带有一个或者多个socket,因此需要对特定的socket进行区分。当一个socket被创建时,就会有一个对应的端口号(port number)分配给这个socket。所以在目的地地址中还包含了socket的端口号。
总而言之,发送进程向数据包附加一个目的地址,该目的地址由目的地主机的 IP 地址和目的地套接字的端口号组成。此外,正如我们即将看到的,发送方的源地址ーー包括源主机的 IP 地址和源套接字的端口号ーー也附加到数据包中。但是,将源地址附加到数据包通常不是由 UDP 应用程序代码完成的; 相反,它是由底层操作系统自动完成的。
为什么还要附带源主机的地址和端口号?
让目的地主机能够把回答(response)发送回发送方。
接下来根据以下的cs应用来进行UDP和TCP的socket编程。
客户端从键盘读取一行字符(数据)并将数据发送到服务器。
服务器接收数据并将字符转换为大写。
服务器将修改后的数据发送给客户端。
客户端接收修改后的数据,并在其屏幕上显示该行
让我们从UDP开始吧。
from socket import * serverName = '127.0.0.1' serverPort = 12000 clientSocket = socket(AF_INET, SOCK_DGRAM) message = input('Input lowercase sentence:') clientSocket.sendto(str.encode(message), (serverName, serverPort)) modifiedMessage, serverAddress = clientSocket.recvfrom(2048) print(modifiedMessage) clientSocket.close()
from socket import *
: This line imports the socket
module, which provides low-level network functionality.
serverName = '127.0.0.1'
: This line sets the IP address of the server that the client will send datagrams to.
serverPort = 12000
: This line sets the port number that the server is listening on.
clientSocket = socket(AF_INET, SOCK_DGRAM)
: This line creates a new socket object called clientSocket
that uses the IPv4 address family and the datagram-oriented protocol type.
message = input('Input lowercase sentence:')
: This line prompts the user to enter a message to send to the server.
clientSocket.sendto(str.encode(message), (serverName, serverPort))
: This line sends the message to the server at the specified IP address and port number.
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
: This line waits for a response from the server and stores the response in the modifiedMessage
variable. The serverAddress
variable stores the IP address and port number of the server that sent the response.
print(modifiedMessage)
: This line prints the response from the server to the console.
clientSocket.close()
: This line closes the socket object.
from socket import * serverPort = 12000 serverSocket = socket(AF_INET, SOCK_DGRAM) serverSocket.bind(('127.0.0.1', serverPort)) print("The server is ready to receive") while 1:message, clientAddress = serverSocket.recvfrom(2048) modifiedMessage = message.upper() serverSocket.sendto(modifiedMessage, clientAddress)
from socket import *
: This line imports the socket
module, which provides low-level network functionality.
serverPort = 12000
: This line sets the port number that the server will use to listen for incoming datagrams.
serverSocket = socket(AF_INET, SOCK_DGRAM)
: This line creates a new socket object called serverSocket
that uses the IPv4 address family and the datagram-oriented protocol type.
serverSocket.bind(('127.0.0.1', serverPort))
: This line binds the serverSocket
object to the server’s IP address and the specified port number.
print("The server is ready to receive")
: This line prints a message to the console indicating that the server is ready to receive datagrams.
while 1:
: This line starts an infinite loop that listens for incoming datagrams and sends a response back to the client.
message, clientAddress = serverSocket.recvfrom(2048)
: This line waits for an incoming datagram and stores the message in the message
variable. The clientAddress
variable stores the IP address and port number of the client that sent the datagram.
modifiedMessage = message.upper()
: This line converts the received message to uppercase and stores it in the modifiedMessage
variable.
serverSocket.sendto(modifiedMessage, clientAddress)
: This line sends the modified message back to the client at the IP address and port number stored in the clientAddress
variable.
编译并运行UDPClient.py和UDPServer.py,在UDPClient中输入要转化为大写的句子,这个字符串经过编码,以字节流的形式通过socket发送给UDPServer,在UDPServer中消息变成大写,返回给UDPClient。
与 UDP 不同,TCP 是一种面向连接的协议。这意味着在客户机和服务器开始相互发送数据之前,它们首先需要握手并建立 TCP 连接。TCP 连接的一端连接到客户端套接字,另一端连接到服务器套接字。在创建 TCP 连接时,我们将客户端套接字地址(IP 地址和端口号)和服务器套接字地址(IP 地址和端口号)与其关联。建立了 TCP 连接之后,当一方想要向另一方发送数据时,它只需通过套接字将数据发送到 TCP 连接。这不同于 UDP,对于 UDP,服务器必须在将数据包放入套接字之前将目的地址附加到数据包。
现在让我们更仔细地研究一下 TCP 中客户机和服务器程序的交互。客户端负责启动与服务器的联系。为了让服务器能够对客户端的初始联系人做出反应,服务器必须做好准备。 这意味着两件事。首先,与 UDP 的情况一样,TCP 服务器必须作为进程运行,然后客户机才会尝试发起联系。其次,服务器程序必须有一个特殊的门ーー更确切地说,是一个特殊的套接字ーー来自运行在任意主机上的客户机进程的初始联系。使用我们的房子/门类比进程/插座,我们有时将客户的初始接触称为“敲响欢迎的门”。
随着服务器进程的运行,客户端进程可以启动到服务器的 TCP 连接。这是通过在客户端程序中创建一个 TCP 套接字来完成的。当客户端创建其 TCP 套接字时,它指定了服务器端欢迎套接字(welcoming socket)的地址,即服务器主机的 IP 地址和套接字的端口号。创建套接字后,客户机发起三次握手并与服务器建立 TCP 连接。发生在传输层内的三次握手对于客户端和服务器程序来说是完全不可见的。
在三方握手过程中,客户端进程敲响了服务器进程的欢迎门。当服务器“听到”敲门声时,它就会创建一个新的门ーー更确切地说,是一个专门为该特定客户端设置的新socket。在下面的示例中,欢迎门(welcoming door)是一个 TCP 套接字对象,我们称之为 serverSocket; 新创建的用于建立连接的客户端套接字称为 connec-tionSocket。第一次接触到 TCP 插座的学生有时会混淆欢迎套接字(welcoming socket)(这是所有想要与服务器通信的客户端的初始联系点) ,以及每个新创建的服务器端连接套接字(connection socket)(后来为与每个客户端通信而创建)。
从应用程序的角度来看,客户端的套接字和服务器的连接套接字通过管道直接连接。如图所示,客户端进程可以向其套接字中发送任意字节,TCP 保证服务器进程将接收(通过连接套接字)发送的顺序中的每个字节。因此,TCP 在客户端和服务器进程之间提供了可靠的服务。此外,正如人们可以进出同一扇门一样,客户端进程不仅仅发送字节,还会从它的socket接收字节;相似的,服务端进程也不仅仅发送字节,还会从它的connection socket接收字节。
TCPClient.py
from socket import * serverName = '127.0.0.1' serverPort = 13000 clientSocket = socket(AF_INET, SOCK_STREAM) clientSocket.connect((serverName, serverPort)) sentence = input('Input lowercase sentence:') clientSocket.send(str.encode(sentence)) modifiedSentence = clientSocket.recv(1024) print('From Server:', modifiedSentence) clientSocket.close()
TCPServer.py
from socket import * serverPort = 13000 serverSocket = socket(AF_INET, SOCK_STREAM) serverSocket.bind(('127.0.0.1', serverPort)) serverSocket.listen(1) print('The server is ready to receive') while 1:connectionSocket, addr = serverSocket.accept()sentence = connectionSocket.recv(1024)capitalizedSentence = sentence.upper()connectionSocket.send(capitalizedSentence)connectionSocket.close()
回顾一下什么是协议。协议是两个或更多个实体之间交换信息的格式和顺序,以及在发送和/或接收信息或其他事件时所采取的行动。协议是计算机网络的核心概念,TCP和UDP也是协议,经过socket编程的学习,我对协议有了更多直观的感觉。