套接字(SOCKET)

socket套接字.是一种通信机制,用于描述IP地址和端口,是一个通信链的句柄.操作socket就像操作Unix一样,一切皆是文件,.

0.0.0.0有多少个网卡,都可以连接到本机.
程序之间进行内存数据交换(通信)比较麻烦:每个程序的内存空间都是被保护的,不能被别的程序直接访问,所以要通过某种介质,管道或第三方工具,去通信.
nosql(第三方):将内存的数据缓存进来,供其它的程序调用,所有的程序都可以往里存数据,所有的程序都可以取数据.相当于实现了一个共享的内存空间.

而所谓的网络编程就是,让在不同的电脑上的软件能够进行数据传递,即进程之间的通信(网络socket)

socket.AF_UNIX;只能够用于单一的Unix系统进程间通信
socket.AF_INET:服务器之间网络通信
socket.AF_INET6 IPv6

socket 处于第4层(传输层) ICMP(网络层)
socket通信的数据格式
socket.SOCK_STREAM :PCP三次握手
socket.SOCKDGRAM:UDP
socket.SOCK_RAW 原始套接字 可以处理普通套接字不能处理的ICMP,IGMP等网络报文 可以可以伪造IP

tcp_server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from socket import *
#IP协议(socket type) TCP协议(数据包格式)
socketobj = socket(AF_INET,SOCK_STREAM)# 生成Socket对象
socketobj.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
socketobj.bind(('localhost',8080)) # 绑定IP和端口
socketobj.listen(128)# 监听连接数量
# 1
#客户端实例,客户端地址
# connection,address=socketobj.accept()
# 每个客户端一连接就会返回的结果
# 但只能连接一个客户端,只有当前客户端下线,下个客户端才能连上
# A,B客户端都运行 但A先连接 B后连接 B处于阻塞状态,没有生成connection
# A断开之后,因为只有一个connection,所以执行了 socketobj.close(),
# 服务也跟着断了 B不会自动连上
while True:
# 2
connection,address=socketobj.accept()
try:
while True:
data = connection.recv(1024) #服务器只接收1024个字节
if len(data) > 0:
# 当recv接收数据时,返回值为空,即没有返回数据,那么意味着客户端
# 已经调用了close关闭了
print('server端收到client端的信息:'+ str(data,encoding = "utf-8"))
connection.sendall(bytes("hello client","utf-8"))
#服务器返回给客户的信息
else:
print('[%s]客户端已经关闭'%str(address))
break
finally:
connection.close()
socketobj.close()

accept():等待客户端连接时阻塞与非阻塞
recv():等待客户端发送信息时阻塞与非阻塞,两者才是只能服务一个客户端的关键

client

1
2
3
4
5
6
7
8
9
10
11
12
from socket import *
socketobj = socket(AF_INET,SOCK_STREAM)
# socketobj.connect(('localhost',8080)) #绑定 阻塞的
socketobj.connect(('localhost',20000)) #asy
while True:
user_input =input("msg to server:").strip()
socketobj.send(bytes(user_input,'utf-8'))
#socketobj.sendall(bytes("hello server", 'utf-8'))
data = socketobj.recv(1024)
print("client端收到server端的信息 :"+str(data,encoding = "utf-8"))
socketobj.close()
多进程与多线程的Socket

利用进程来完成多任务,其实就是启动一个子进程(线程)来完成client.start()
也就是说: 来一个客户端连接分配一个进程或线程为其服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from socket import *
from multiprocessing import *
from threading import Thread
from time import sleep
# 处理客户端的请求并为其服务
def dealWithClient(newSocket,destAddr):
while True:
recvData = newSocket.recv(1024)
if len(recvData)>0:
print('recv[%s]:%s'%(str(destAddr), recvData))
else:
print('[%s]客户端已经关闭'%str(destAddr))
break
newSocket.close()
def main():
serSocket=socket(AF_INET, SOCK_STREAM)
serSocket.setsockopt(SOL_SOCKET, SO_REUSEADDR , 1)
localAddr=('', 8080)
serSocket.bind(localAddr)
serSocket.listen(5)
try:
while True:
print('-----主进程,,等待新客户端的到来------')
newSocket,destAddr = serSocket.accept()
# 此时已经连接的客户端Socket=服务端的Socket等待新的客户端到来
print('-----主进程,,接下来创建一个新的进程(线程)负责数据处理-----')
client = Process(target=dealWithClient, args=(newSocket, destAddr))
client.start()
# 因为已经向子进程中copy了一份(引用),并且父进程中这个套接字也没有用处了
# 所以关闭
newSocket.close()
# 多线程
# client = Thread(target=dealWithClient, args=(newSocket, destAddr))
# client.start()
# # newSocket.close()
# 因为线程中共享这个套接字, 如果关闭了会导致这个套接字不可用,
# 但是此时在线程中这个套接字可能还在收数据,因此不能关闭
finally: # 当为所有的客户端服务完之后再进行关闭,表示不再接收新的客户端的链接
serSocket.close()
if __name__ == '__main__':
main()

区别在于:进程的cow(写时copy):能共用的资源尽量共用,实在不行才copy

单进程-非堵塞socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# 只适合用于少数用户,没有实际的运用
from socket import *
#1.创建socket
serSocket = socket(AF_INET, SOCK_STREAM)
#2. 绑定本地ip以及port
localAddr = ('', 8080)
serSocket.bind(localAddr)
#3. 让这个socket 变为非堵塞
serSocket.setblocking(False)
#4. 将socket变为监听(被动)套接字
serSocket.listen(100)
# 用来保存所有已经连接的客户端的信息
clientAddrList = []
while True:
#等待一个新的客户端的到来(即完成3次握手的客户端)
try:
clientSocket,clientAddr = serSocket.accept()
except:
pass
else:
print("一个新的客户端到来:%s"%str(clientAddr))
clientSocket.setblocking(False) # 设置recv为非阻塞
clientAddrList.append((clientSocket,clientAddr))
for clientSocket,clientAddr in clientAddrList:
try:
recvData = clientSocket.recv(1024)
except:
pass
else:
if len(recvData)>0:
print("%s:%s"%(str(clientAddr), recvData))
else:
clientSocket.close()
clientAddrList.remove((clientSocket, clientAddr))
print("%s 已经下线"%str(clientAddr))

但如果当有一个Socket使用到耗时的代码时,其它的Socket也将被阻塞
同时使用轮询机制,所以效率不高

epoll版-TCP服务器

Linux平台下的通过epoll来高效管理socket(server_socket,client_socket)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import socket
import select
# 创建套接字
s = socket.socket(socket.AF_INET,socket.SOCK_ STREAM)
# 设置可以重复使用绑定的信息
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
s.bind(("",8080))
s.listen(10)
# 创建一个epoll对象
epoll = select.epoll()
epoll.register(s.fileno(),select.EPOLLIN|select.EPOLLET)
# s.fileno() 得到文件描述符(套接字对应的数字编号)
# fd:sys.stdin.fileno()
connections= {}
addresses ==={}
#循环等待客户端的到来或者对方发数据
while True:
# epoll进行fd扫描的地方--未指定超时时间则为阻塞等待
epoll_list=epoll.poll() # 得到可用的socket
# (包括等待客户端连接的Socket和连接上的客户端Socket,以及服务器的Socket )
# 对事件进行判断,对上面检测出来的Socket进行收发数据的处理
forfd,events in epoll_list:
# 如果是socket创建的套接字 被激活
iffd==s.fileno():
conn,addr=s.accept()
print('有新的客户端到来%s'%str(addr))
#将conn和addr信息分别保存起来
connections[conn.fileno()] = conn
addresses[conn.fileno()] = addr
#向epoll中注册连接socket的可读事件
epoll.register(conn .fileno(),select.EPOLLIN|select.EPOLLET)
# 判断事件是否æ¯接收数据的事件(客户端端发送信息)
elif events == select.EPOLLIN:
#从激活fd上接收
recvData = connections[fd].recv(1024)
if len(recvData)>0:
print('recv:%s'%recvData)
else:
#从epoll中移除该连接fd
epoll.unregister(fd)
#server侧主动关闭该连接fd
connections[fd].close()
print("%s---offline---"%str(addresses[fd]))

epoll的优点:socket通过事件通知机制(由socket本身的状态来告诉epoll,哪个socket可用,就不需要去轮询所有的socket了,更加高效的得到可用的Socket)
不是轮询的方式,epoll它只管你”活跃”的连接,而跟连接总数无关

asy_server

参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from socketserver import BaseRequestHandler, TCPServer, ThreadingTCPServer
import os
# socketserver 可以让我们很容易的创建简单的TCP服务器,
# 巨大部分Python的高层网络模块(比如HTTP,XML-RPC等)都是建立在socketserver功能之上
class EchoHandler(BaseRequestHandler):
def handle(self):#每个连接就会 调用此方法
print('Got connection from', self.client_address)
while True:
# 循环去监听用户的连接,否则只能像单进程的阻塞一样,只能连接一个用户
msg = self.request.recv(8192)
if not msg:#如果用户退出,没有这句的话 就变成死循环
print('connection close by', self.client_address)
break
print(msg)
# self.request.send(str(msg,encoding = "utf-8"))
self.request.send(msg)
if __name__ == '__main__':
# serv = TCPServer(('', 20000), EchoHandler)#单个线程
serv = ThreadingTCPServer(('0.0.0.0', 20000), EchoHandler)
serv.serve_forever()

Share Comments