博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
网络编程之TCP编程(socket、服务端、客户端)
阅读量:4131 次
发布时间:2019-05-25

本文共 9141 字,大约阅读时间需要 30 分钟。

1. socket介绍

1.1 socket套接字

python中提供socket.py标准库,非常底层的接口库

socket是一种通用的网络编程接口,和网络层次没一一对应的关系

1.2 协议族

AF表示Address Family,用于socket()第一个参数

协议族

1.3 Socket类型

socket类型

2. TCP编程

  socket编程,需要两端,一般来说需要一个服务端、一个客户端,服务端称为Server,客户端称为Client。这种编程模式也称为CS编程。

2.1 TCP服务端编程

2.1.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)发送数据

TCP编程步骤TCP server端开发:

# TCP server 端开发import socket# import timeserver = socket.socket()  # TCP 连接 IPv4ip = '127.0.0.1'  # 本机回环地址,永远指向本机port = 9999  # 建议使用1000以上; TCP 65536种状态server.bind((ip, port))  # address,此方法只能绑定一次server.listen()  # 真正的显示出端口,监听不是阻塞函数# time.sleep(100)print(server)# print(server.accept())  # 默认阻塞,不懂千万不要修改new_socket, client_info = server.accept()print(new_socket)print('new_socket', client_info)while True:    # new_socket.send(b'server ack')    data = new_socket.recv(1024)  # 缺省情况下是阻塞的    print(data)    new_socket.send('server ack. data={}'.format(data.decode()))new_socket.close()# new2, client2 = server.accept()  # 新的连接,之前的连接已经关闭,并且两次连接可能在不同的进程# print(new2)# print('new2', client2)# data = new2.recv(1024)# print(data)# new2.send('server new2 ack. data={}'.format(data.encode()))# new2.close()server.close()

实战——写一个群聊程序

需求分析
聊天工具是CS程序,C是每一个客户端client,S是服务器端server
服务器应该具有的功能:

  1. 启动服务,包括绑定地址和端口,并监听
  2. 建立连接,能和多个客户端建立连接
  3. 接收不同用户的信息
  4. 分发,将接收的 某个用户的信息转发到已连接的所有客户端
  5. 停止服务
  6. 记录连接的客户端

代码实现

# tcp server 端开发import socketimport threadingimport loggingfrom datetime import datetimeFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatServer:    def __init__(self, ip='127.0.0.1', port=9999):        self.sock = socket.socket()        self.address = ip, port        self.event = threading.Event()        self.lock = threading.Lock()        self.clients = {
} def start(self): self.sock.bind(self.address) self.sock.listen() threading.Thread(target=self.accept, name='accept').start() def accept(self): while not self.event.is_set(): try: new_sock, client_info = self.sock.accept() # 阻塞等待连接 except Exception as e: logging.error(e) with self.lock: self.clients[client_info] = new_sock threading.Thread(target=self.rec, name='rec', args=(new_sock, client_info)).start() def rec(self, sock, client): while not self.event.is_set(): try: data = sock.recv(1024) # 阻塞等待接收信息,接收信息也可能出现异常 except Exception as e: logging.error(e) data = b'' print(data.decode(encoding='cp936')) if data.strip() == b'quit' or data.strip() == b'': # 客户端主动断开连接 with self.lock: self.clients.pop(client) # 将断开连接的客户ip和端口从字典中移除,因为下文还要遍历字典 sock.close() # 此句比较耗时,可以放在锁的外面 break msg = "{:%Y/%m/%d %H:%M:%S} [{}:{}] - {}".format(datetime.now(), *client, data.decode(encoding='cp936')) exc = [] exs = [] with self.lock: for c, s in self.clients.items(): # 遍历的是时候不允许别人pop和add,所以加锁 try: s.send(msg.encode(encoding='cp936')) # 给所有的new_sock发送的信息。注意此句可能会出现异常,如网络断了 except Exception as e: logging.error(e) exc.append(c) exs.append(s) for c in exc: self.clients.pop(c) for s in exs: # 此句比较耗时,所以放在锁外面 s.close() def stop(self): self.event.set() with self.lock: values = list(self.clients.values()) self.clients.clear() # 这是一个好习惯 for s in values: s.close() self.sock.close()cs = ChatServer()cs.start()while True: cmd = input(">>>").strip() if cmd == 'quit': cs.stop() break logging.info(threading.enumerate()) logging.info(cs.clients)

socket常用方法

makefile

socket.makefile(mode=‘r’, buffering=None, *, encoding=None, error=None, newline=None)
创建一个与该套接字相关联的文件对象,将recv方法看做读方法,将send方法看做写方法。

import sockets = socket.socket()s.bind(('127.0.0.1', 9999))s.listen()s1, info = s.accept()print(s1.getpeername())  # ('127.0.0.1', 55934)print(s1.getsockname())  # ('127.0.0.1', 9999)f = s1.makefile('rw')data = f.read(10)  # 一次读取10个字节print(data)msg = 'server rec {}'.format(data)f.write(msg)f.flush()print(f, s1, sep='\n')f.close()s1.close()s.close()

makefile练习

使用makefile改写群聊类

import datetimeimport threadingimport socketimport loggingFORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"logging.basicConfig(level=logging.INFO, format=FORMAT)class ChatServer:    def __init__(self, ip='127.0.0.1', port=9999):        self.sock = socket.socket()        self.address = ip, port        self.event = threading.Event()        self.clients = {
} self.lock = threading.Lock() def start(self): self.sock.bind(self.address) self.sock.listen() threading.Thread(target=self.accept, name='accept').start() def accept(self): while not self.event.is_set(): new_sock, client_info = self.sock.accept() new_file = new_sock.makefile('rw') with self.lock: self.clients[client_info] = new_file, new_sock # 增加了item,所以必须加锁,多线程处理同一个资源 threading.Thread(target=self.rec, name='rec', args=(new_file, client_info)).start() def rec(self, f, client): while not self.event.is_set(): line = f.readline() # 阻塞等一行来,输入数据的时候要加换行符 print(line) if line.strip() == 'quit' or line.strip() == '': # line为字符串。不能再写成b''和b'quit'了 with self.lock: _, s = self.clients.pop(client) f.close() s.close() break msg = '{:%Y/%m/%d %H:%M:%S} [{}: {}] {}'.format(datetime.datetime.now(), *client, line) # 此处的line不用解码了,因为readline读取的是字符串 with self.lock: for ff, _ in self.clients.values(): # 注意ff不能与上面的f重复 ff.write(msg) ff.flush() # def rec(self, f, client): # while not self.event.is_set(): # try: # line = f.readline() # except Exception as e: # logging.error(e) # line = 'quit' # msg = line.strip() # if msg == 'quit' or msg == '': # with self.lock: # _, sock = self.clients.pop(client) # f.close() # sock.close() # logging.info('{} quits.'.format(client)) # break # msg = '{:%Y/%m/%d %H:%M:%S} [{}: {}] {}'.format(datetime.datetime.now(), *client, line) # logging.info(msg) # with self.lock: # for ff, _ in self.clients.values(): # ff.write() # ff.flush() def stop(self): self.event.set() # keys = [] with self.lock: for f, s in self.clients.values(): f.close() s.close() self.sock.close()def main(): cs = ChatServer() cs.start() while True: cmd = input(">>>").strip() if cmd == 'quit': cs.stop() threading.Event().wait(3) break logging.info(threading.enumerate()) logging.info(cs.clients)if __name__ == '__main__': main()

2.2 客户端编程

2.2.1 客户端编程步骤

  • 创建socket对象
  • 连接到远端服务端的ip和port,connect()方法
  • 传输数据:使用send、recv方法发送、接收数据
  • 关闭连接,释放资源
import socketclient = socket.socket()ip_address = ('127.0.0.1', 9999)client.connect(ip_address)  # 直接连接服务器client.send(b'abc\n')data = client.recv(1024)  # 阻塞等待client.close()
import socketimport threadingimport datetimeimport loggingFORMAT = "%(threadName)s %(thread)d %(message)s"logging.basicConfig(format=FORMAT, level=logging.INFO)class ChatClient:    def __init__(self, ip='127.0.0.1', port=9999):        self.client = socket.socket()        self.address = ip, port        self.event = threading.Event()    def start(self):        self.client.connect(self.address)        self.send("I'm ready.")        threading.Thread(target=self.rec, name='rec').start()    def rec(self):        while not self.event.is_set():            try:                data = self.client.recv(1024)  # 此句可能会出现异常,如网络中断            except Exception as e:                logging.error(e)                break                            msg = "{:%Y/%m/%d %H:%M:%S} [{}:{}] {}".format(datetime.datetime.now(), *self.address, data)            logging.info(msg)    def send(self, msg: str):        data = "{}\n".format(msg.strip()).encode()        self.client.send(data)    def stop(self):        self.client.close()        self.event.wait(3)        self.event.set()        logging.info('Client stops')cc = ChatClient()cc.start()while True:    cmd = input(">>>").strip()    if cmd == 'quit':        cc.stop()        break    cc.send(cmd)

转载地址:http://opfvi.baihongyu.com/

你可能感兴趣的文章
用Asp.Net写Rss
查看>>
数据挖掘算法——决策树
查看>>
数据挖掘算法——关联规则挖掘算法
查看>>
Linux环境下编程(一)——进程fork()的使用
查看>>
Linux环境下编程(二)——线程基础概念
查看>>
自己动手实现数据结构——排序算法1(冒泡、插入、归并、简单选择)(C++实现)
查看>>
Linux网络编程(附1)——封装read、write
查看>>
EXPLAINING AND HARNESSING ADVERSARIAL EXAMPLES
查看>>
通过源码了解Java中的HashMap的属性细节以及扩容内幕哦!
查看>>
Java中的线程--对线程来个多方位理解!
查看>>
了解TCP连接中的3次握手与4次挥手
查看>>
快速掌握二叉树的7种遍历方式哦!!!
查看>>
删除链表中等于给定值val的所有节点。
查看>>
用JavaScrip实现选项卡切换的效果
查看>>
用javascript实现网页中表格的行的添加与删除
查看>>
用javascript实现菜单子选项的隐藏和显示
查看>>
用javascript实现发送验证码和60秒计时重试
查看>>
用javascrip实现一个简单的加减乘除计算器
查看>>
JavaScript调试技巧之console.log()详解
查看>>
CSS中 overflow:hidden 的作用
查看>>