Socket网络编程-TCP编程
Socket网络编程-TCP编程
作者:尹正杰
版权声明:原创作品,谢绝转载!否则将追究法律责任。
一.socket介绍
1>.TCP/IP协议
2>.跨网络的主机间通讯
在建立通信连接的每一端,进程间的传输要有两个标志:
IP地址和端口号,合称为套接字地址 socket address
客户机套接字地址定义了一个唯一的客户进程
服务器套接字地址定义了一个唯一的服务器进程
3>.什么是socket套接字
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
Socket:套接字,进程间通信IPC的一种实现,允许位于不同主机(或同一主机)上不同进程之间进行通信和数据交换,SocketAPI出现于1983年,4.2 BSD实现。
Socket API:封装了内核中所提供的socket通信相关的系统调用。
Python中提供了socket标准库,非常底层的接口库。
4>.协议族(Socket Domain)
AF表示Address Family,用于socket()第一个参数
AF_INET:
对应IPV4
AF_INET6
对应IPV6
AF_UNIX
同一主机不同进程之间通信时使用,对应Unix Domain Socket,windows没有。
5>.socket Type(根据使用的传输层协议)
SOCK_STREAM
可靠的传递,面向连接的流套接字。默认值,TCP协议。
SOCK_DGRAM
不可靠的传递,无连接的数据报文套接字。UDP协议。
SOCK_RAW:
裸套接字,无须tcp或udp,APP直接通过IP包通信
6>.C/S编程
Socket编程,需要两端,一般来说需要一个服务端、一个客户端,服务端称为Server,客户端称为Client。 这种编程模式也称为C/S编程。 套接字相关的系统调用: socket(): 创建一个套接字 bind(): 绑定IP和端口 listen(): 监听 accept(): 接收请求 connect(): 请求连接建立 write(): 发送 read(): 接收 close(): 关闭连接
7>.python中的socket常用方法
socket.recv(bufsize[, flags]) 获取数据。默认是阻塞的方式
socket.recvfrom(bufsize[, flags]) 获取数据,返回一个二元组(bytes, address)
socket.recv_into(buffer[, nbytes[, flags]]) 获取到nbytes的数据后,存储到buffer中。如果 nbytes没有指定或0,将buffer大小的数据存入buffer中。返回接收的字节数。
socket.recvfrom_into(buffer[, nbytes[, flags]]) 获取数据,返回一个二元组(bytes, address)到buffer中
socket.send(bytes[, flags]) TCP发送数据
socket.sendall(bytes[, flags]) TCP发送全部数据,成功返回None
socket.sendto(string[,flag],address) UDP发送数据
socket.sendfile(file, offset=0, count=None) python 3.5版本开始,发送一个文件直到EOF,使用高性能的os.sendfile机制,返回发送的字节数。如果win下不支持sendfile, 或者不是普通文件,使用send()发送文件。offset告诉 起始位置。 socket.getpeername() 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)
socket.getsockname() 返回套接字自己的地址。通常是一个元组(ipaddr,port)
socket.setblocking(flag) 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值),非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常
socket.settimeout(value) 设置套接字操作的超时期,timeout是一个浮点数,单位是 秒。值为None表示没有超时期。一般,超时期应该在刚创 建套接字时设置,因为它们可能用于连接的操作(如 connect())
socket.setsockopt(level,optname,value) 设置套接字选项的值。比如缓冲区大小。太多了,去看文档。不同系统,不同版本都不尽相同 socket.makefile(mode='r', buffering=None, *, encoding=None, errors=None, newline=None) 创建一个与该套接字相关连的文件对象,将recv方法看做读方法,将send方法看做写方法。
二.TCP服务端编程
1>.服务器端编程步骤
创建Socket对象 绑定IP地址Address和端口Port。bind()方法 IPv4地址为一个二元组('IP地址字符串', Port) 开始监听,将在指定的IP的端口上监听。listen()方法 获取用于传送数据的Socket对象 socket.accept() -> (socket object, address info) accept方法阻塞等待客户端建立连接,返回一个新的Socket对象和客户端地址的二元组 地址是远程客户端的地址,IPv4中它是一个二元组(clientaddr, port) 接收数据 recv(bufsize[, flags]) 使用缓冲区接收数据 发送数据 send(bytes)发送数据 Server端开发 socket对象 --> bind((IP, PORT)) --> listen --> accept --> close |--> recv or send --> close
2>.实战案例写一个群聊程序的服务端ChatServer
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import logging 7 import socket 8 import threading 9 import datetime 10 11 logging.basicConfig(level=logging.INFO, format="%(asctime)s %(thread)d %(message)s") 12 13 14 """ 15 注意,这个代码为实验用,代码中瑕疵还有很多。Socket太底层了,实际开发中很少使用这么底层的接 口。 16 """ 17 class ChatServer: 18 def __init__(self,ip="127.0.0.1",port=6666): 19 self.conn = socket.socket() #创建socket对象 20 self.addr = (ip,port) 21 self.clients = {} #存放客户端连接容器 22 self.event = threading.Event() #判断服务是否启动 23 self.lock = threading.Lock() #为了线程安全而引入的 24 25 def start(self): #启动服务,会监听IP地址和端口哟~ 26 self.conn.bind(self.addr) #将IP地址和端口绑定到套接字上 27 self.conn.listen() #监听绑定的套接字 28 29 #accept会阻塞主线程,所以开一个新线程 30 threading.Thread(target=self.accept).start() 31 32 def accept(self): #处理客户端的链接 33 while not self.event.is_set(): 34 conn,client = self.conn.accept() #该方法默认会进入阻塞状态 35 f = conn.makefile("rw") #将socket封装成一个文件对象来操作,支持读写 36 with self.lock: 37 self.clients[client] = f,conn #如果有新链接就添加到客户端字典 38 39 #准备接收数据,recv是阻塞的,开启新的线程 40 threading.Thread(target=self.recv,args=(f,client)).start() 41 42 def recv(self,f,client): #处理客户端数据 43 print("in recv") 44 while not self.event.is_set(): 45 try: 46 data = f.readline() 47 print(data) 48 except Exception as e: 49 logging.error(e) 50 data = "quit" 51 52 msg = data.strip() 53 print(msg, "++++++++++++++++++") 54 55 #和客户端约定退出命令 56 if msg == "quit" or msg == "": #主动端口得到空串 57 with self.lock: 58 _,conn = self.clients.pop(client) 59 f.close() 60 conn.close() 61 logging.info("{} quit ...".format(client)) 62 break 63 64 msg = "{:%Y/%m/%d %H:%M:%S} ip:port => {}:{}\n data => {}\n".format(datetime.datetime.now(),*client, data) 65 logging.info(msg) 66 67 68 with self.lock: 69 for f1,_ in self.clients.values(): 70 f1.write(msg) 71 f1.flush() 72 73 def stop(self): #停止服务 74 self.event.set() 75 with self.lock: 76 for f,s in self.clients.values(): 77 f.close() 78 s.close() 79 self.conn.close() 80 81 82 def main(): 83 server = ChatServer() 84 server.start() 85 86 while True: 87 cmd = input(">>> ").strip() 88 if cmd == "quit": 89 server.stop() 90 threading.Event.wait(3) #关闭服务需要等待时间 91 break 92 logging.info(threading.enumerate()) #用来观察断开后线程的变化 93 logging.info(server.clients) 94 95 if __name__ == '__main__': 96 main()
三.TCP客户端编程
1>.客户端编程步骤
创建Socket对象
连接到远端服务端的ip和port,connect()方法
传输数据
使用send、recv方法发送、接收数据
关闭连接,释放资源
2>.实战案例写一个群聊程序的客户端ChatClient
1 #!/usr/bin/env python 2 #_*_conding:utf-8_*_ 3 #@author :yinzhengjie 4 #blog:http://www.cnblogs.com/yinzhengjie 5 6 import socket 7 import threading 8 import datetime 9 import logging 10 11 12 FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s" 13 logging.basicConfig(format=FORMAT, level=logging.INFO) 14 15 """ 16 同样,这样的客户端还是有些问题的,仅用于测试。 17 """ 18 class ChatClient: 19 def __init__(self, ip='127.0.0.1', port=6666): 20 self.sock = socket.socket() 21 self.addr = (ip, port) 22 self.event = threading.Event() 23 24 def start(self): # 启动对远端服务器的连接 25 self.sock.connect(self.addr) 26 self.send("I'm ready.") 27 # 准备接收数据,recv是阻塞的,开启新的线程 28 threading.Thread(target=self.recv, name="recv").start() 29 30 def recv(self): # 接收服务端的数据 31 while not self.event.is_set(): 32 try: 33 data = self.sock.recv(1024) # 阻塞 34 except Exception as e: 35 logging.error(e) 36 break 37 38 msg = "{:%Y/%m/%d %H:%M:%S} ip:port => {}:{}\n data => {}\n".format(datetime.datetime.now(),*self.addr, data.strip()) 39 logging.info(msg) 40 41 def send(self, msg:str): 42 data = "{}\n".format(msg.strip()).encode() # 服务端需要一个换行符 43 self.sock.send(data) 44 45 def stop(self): 46 self.sock.close() 47 self.event.wait(3) 48 self.event.set() 49 logging.info('Client stops...') 50 51 def main(): 52 client = ChatClient() 53 client.start() 54 while True: 55 cmd = input('>>>') 56 if cmd.strip() == 'quit': 57 client.stop() 58 break 59 client.send(cmd) # 发送消息 60 61 62 if __name__ == '__main__': 63 main()
本文来自博客园,作者:尹正杰,转载请注明原文链接:https://www.cnblogs.com/yinzhengjie/p/11974087.html,个人微信: "JasonYin2020"(添加时请备注来源及意图备注,有偿付费)
当你的才华还撑不起你的野心的时候,你就应该静下心来学习。当你的能力还驾驭不了你的目标的时候,你就应该沉下心来历练。问问自己,想要怎样的人生。