源 IP 地址和目的 IP 地址以及源端口号和目的端口号的组合称为套接字,是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,解决网络上两台主机之间的进程通信问题。简单的说就是通信双方的一种约定,用套接字中的相关函数来完成通信过程。其用于标识客户端请求的服务器和服务,是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息:连接使用的协议、本地主机的IP地址、本地进程的协议端口、远地主机的 IP 地址、远地进程的协议端口(Socket = IP address + TCP/UDP + port)。应用层(HTTP)和传输层(TCP/UDP)就可以通过套接字接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。
Socket 可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络驱动程序的桥梁,Socket 在应用程序中创建,通过绑定与网络驱动建立关系。此后,应用程序送给 Socket 的数据,由 Socket 交给网络驱动程序向网络上发送出去。计算机从网络上收到与该 Socket 绑定 IP 地址和端口号相关的数据后,由网络驱动程序交给 Socket,应用程序便可从该 Socket 中提取接收到的数据,网络应用程序就是这样通过 Socket 进行数据的发送与接收的。要通过 Internet 进行通信,至少需要一对套接字,其中一个运行在客户端,称之为 ClientSocket,另一个运行于服务器端面,称为 ServerSocket。根据连接启动的方式以及本地要连接的目标,套接字之间的连接过程可以分为三个步骤:
服务器监听:是指服务端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态。
客户端请求:是由客户端的套接字提出连接请求,要连接的目标是服务器端套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器套接字的地址和端口号,然后再向服务器端套接字提出连接请求。
连接确认:连接确认是当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,它就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的信息发送给客户端,一旦客户端确认了此连接,连接即可建立。而服务器端继续处于监听状态,继续接收其他客户端的连接请求。
同步模式:同步模式的特点是在通过 Socket 进行连接、接收、发送数据时,客户机和服务器在接收到对方响应前会处于阻塞状态,即一直等到收到对方请求才继续执行下面的语句。可见,同步模式只适用于数据处理不太多的场合。当程序执行的任务很多时,长时间的等待可能会让用户无法忍受,但同步模式的好处在于应用接口的访问有着更好的稳定性。
异步模式:异步模式的特点是在通过 Socket 进行连接、接收、发送操作时,客户机或服务器不会处于阻塞方式,而是利用 callback 机制进行连接、接收、发送处理,这样就可以在调用发送或接收的方法后直接返回,并继续执行下面的程序。可见,异步套接字特别适用于进行大量数据处理的场合,使用 s.setblocking(0) 启用异步模式。
UDP 和 TCP 的区别: tcp是可靠的、面向连接的、尽力传输的协议,而udp是不可靠 的、面向非连接的、不尽力传输的协议。但是不可靠不代表它没有用,udp有自己的应用场景,语音和视频几乎都在使用udp协议,它的不可靠只是相对于 tcp来说的,但是它的好处就是效率,高效在某些场景要比可靠性重要。这就涉及trade-off了,也就是权衡,需要根据你的应用权衡利弊,然后进行选择。
NOTE: 1)TCP发送数据时,已建立好TCP连接,所以不需要指定地址。UDP是面向无连接的,每次发送要指定是发给谁。 2)服务端与客户端不能直接发送列表,元组,字典。需要字符串化 repr(data)。
TCP 服务端:
创建套接字,绑定套接字到本地 IP 与端口开始监听连接进入循环,不断接受客户端的连接请求 然后接收传来的数据,并发送给对方数据 传输完毕后,关闭套接字TCP 客户端:
创建套接字,连接远端地址连接后发送数据和接收数据传输完毕后,关闭套接字Step 1:创建套接字,如果创建 socket 函数失败,会抛出一个 socket.error 的异常,需要捕获。
try: # create an AF_INET, STREAM socket (TCP) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, msg: print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1] sys.exit()Step 2:绑定 socket
HOST = '' # Symbolic name meaning all available interfaces PORT = 8888 # Arbitrary non-privileged port ADDR = (HOST, RORT) try: s.bind(ADDR) except socket.error , msg: print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1] sys.exit()Step 3: 监听连接
s.listen(5)Step 4:接收请求
# wait to accept a connection - blocking call conn, addr = s.accept()Step 5:发送响应
# now keep talking with the client data = conn.recv(1024) conn.sendall(data)Step 6:关闭 socket
s.close()NOTE1:将上面的服务器程序改造成一直运行,最简单的办法是将 accept 放到一个循环中,那么就可以一直接收连接了。
# now keep talking with the client while True: #wait to accept a connection - blocking call conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1]) data = conn.recv(1024) reply = 'OK...' + data if not data: break conn.sendall(reply) conn.close()NOTE2:多个 Client 可以随时建立连接,而且每个客户端可以跟服务器进行多次通信,将处理的程序与主程序的接收连接分开。一种方法可以使用线程来实现,主服务程序接收连接,创建一个线程来处理该连接的通信,然后服务器回到接收其他连接的逻辑上来。
定义线程体函数 # Function for handling connections. This will be used to create threads def client_thread(conn): # Sending message to connected client conn.send('Welcome to the server. Type something and hit enter\n') # send only takes string # infinite loop so that function do not terminate and thread do not end. while True: # Receiving from client data = conn.recv(1024) reply = 'OK...' + data if not data: break conn.sendall(reply) # came out of loop conn.close() 创建多线程 # now keep talking with the client while True: # wait to accept a connection - blocking call conn, addr = s.accept() print 'Connected with ' + addr[0] + ':' + str(addr[1]) # start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function. start_new_thread(client_thread ,(conn,)) s.close()Step 1:创建套接字,如果创建 socket 函数失败,会抛出一个 socket.error 的异常,需要捕获。
try: # create an AF_INET, STREAM socket (TCP) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, msg: print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1] sys.exit()Step 2: 连接
remote_hostname = 'www.google.com' try: # 获得远程主机的 IP 地址 remote_ip = socket.gethostbyname(remote_hostname) except socket.gaierror: #could not resolve print 'Hostname could not be resolved. Exiting' sys.exit() s.connect((remote_ip , port))Step 3:发送请求
try : #Set the whole string s.sendall(message) except socket.error: #Send failed print 'Send failed' sys.exit()Step 4:接收响应
reply = s.recv(4096)Step 5:关闭 socket
s.close()