chapter12.1、socket编程,tcp实现通讯

网络编程

Socket编程

socket套接字

端到端编程,建立了一条数据传输的通道

网络编程的基础,所有的操作系统都支持socket

socket是一种通用的网路编程接口,不需要关心协议层,只是打通了一条通道,和网络层次没有一一对应关系。

IPC,RPC,最底层也是socket实现

协议族

名称 含义
AF_INET IPV4
AF_INET6 IPV6
AF_UNIX Unix Domain Socket,windows没有
 
 
 
 

socket 类型

名称 含义
SOCK_STREAM 面向连接的流套接字,默认值,TCP协议
SOCK_DGRAM 无连接的数据报文套接字,UDP协议,效率高,易产生乱序,丢包问题
 
 
 
 
 

TCP编程

服务端server,客户端client,cs编程,

服务器编程步骤

1、创建Socket对象

2、绑定IP地址和端口,IP负责地址,端口管应用程序

端口是进程的,不是线程的,只是线程共享进程的资源

3、监听,暴露端口,将指定的IP端口开始监听

4、socket accept 获取传送数据的socket对象

用于阻塞等待客户端建立连接,返回一个新的socket对象和客户端地址的二元组

地址是远程客户端地址,IPv4中是一个二元组(clientaddr,port)

  • recv(bufsize[,flags])  使用缓冲区接收数据

  • send(bytes)  发送数据

注意:两次绑定同一个端口会报错,操作系统级别的错误

Windows报错:  通常每个套接字地址(协议/网络地址/端口)只允许使用一次。

#1 socket
sevrer = socket.socket()

#2 bind
ip = '127.0.0.1'
port = 9999
addr = (ip,port)
server.bind(addr)

#3 listen
server.listen()

#4 accept
s1, info = server.accept()  #阻塞等待client连接

#5 recv,send
data = server.recv(1024)
s1.send("hello, I'm server")

#6 close
server.close()

netstat Windows使用 b 显示进程,要用管理员权限 windows下

ss -tanl l表示监听,t tcp协议,a全部,n数字显示端口 Linux下

windows下的管道也可以使用,findstr是过滤方法

群聊程序TCP协议服务器端简单实现

TCPChatserver
 1 import socket
 2 import threading
 3 import logging
 4 
 5 FORMAT = "%(thread)d %(threadName)s %(asctime)s %(message)s"
 6 logging.basicConfig(format=FORMAT,level=logging.INFO)
 7 
 8 class ChatServer:
 9     def __init__(self,ip="127.0.0.1",port=9999):
10         self.sock = socket.socket()
11         self.addr = (ip,port)
12         self.clients = {}
13         self.event = threading.Event()
14 
15     def start(self):#开始监听
16         self.sock.bind(self.addr)
17         self.sock.listen()
18         #accept会阻塞主线程,开线程跑
19         threading.Thread(target=self.accept,name="accept").start()
20 
21     def accept(self):#多人连接
22         while not self.event.is_set():
23             try:
24                 sock,raddr = self.sock.accept()#阻塞等待
25             except Exception:
26                 self.stop()
27                 break
28             self.clients[raddr] = sock#记录客户端
29             logging.info(raddr)
30             #开启新线程,一个新的socket对象处理单个client
31             threading.Thread(target=self.recv,args=(sock,raddr),name="r-{}".format(raddr)).start()
32 
33 
34     def recv(self,sock,raddr):#接受数据
35         while not self.event.is_set():
36             try:
37                 data = sock.recv(1024).strip()#阻塞等待
38             except Exception:
39                 break
40             logging.info(":{}".format(data.decode()))
41             #client退出机制
42             if data.strip() == b"quit" or data ==b"":
43                 print(data,"~~~~~~~~~~")
44                 self.clients.pop(raddr)
45                 sock.close()
46                 break
47 
48             msg = "{}".format(data)
49             msg = msg.encode()
50             for client in self.clients.values():
51                 client.sendall(msg)
52 
53     def stop(self):#停止服务
54         for c in self.clients.values():
55             c.close()
56         self.sock.close()
57         self.event.set()
58 
59 def main():
60     server = ChatServer()
61     server.start()
62 
63     while True:
64         cmd = input(">>>")
65         if cmd == "quit":
66             server.stop()
67             threading.Event().wait(3)
68             break
69         logging.info(threading.enumerate())#观察线程变化
70 
71 if __name__ == '__main__':
72     main()

只是实现主要功能,且现在不用该方法了

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) 发送一个文件直到EOF,使用高性能的os.sendfile机制,返回发送的字节数。如果win下不支持sendfile,或者不是普通文件,使用send()发送文件。offset告诉起始位置。3.5版本开始
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) 设置套接字选项的值。比如缓冲区大小。太多了,去看文档。不同系统,不同版本都不尽相同
 
 

makefile方法

类文件对象socket会占文件描述符,不用要归还
import socket
import threading
import logging

FORMAT = "%(asctime)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT,level=logging.INFO)

class ChatTCPServer:
    def __init__(self,ip="127.0.0.1",port=9999):
        self.sock = socket.socket()
        self.addr = ip,port
        self.event = threading.Event()
        self.clients = {}

    def start(self):
        self.sock.bind(self.addr)
        self.sock.listen()
        threading.Thread(target=self.accept,name="accept").start()

    def accept(self):
        while not self.event.is_set():
            try:
                s, client = self.sock.accept()
                f = s.makefile("rw")
            except:
                self.stop()
                break
            self.clients[client] = f

            threading.Thread(target=self.recv,args=(f, client,s),name="client-{}".format(client[1])).start()
            logging.info("{} login".format(client))

    def recv(self,f,raddr,s):

        while not self.event.is_set():
            try:
                msg = f.readline().strip()
            except Exception as e:
                logging.error(e)
                self.clients.pop(raddr)
                f.close()
                break
            logging.info(msg)
            if msg == "quit" or "":
                self.clients.pop(raddr)
                f.close()
                break

            for f1 in self.clients.values():
                f1.write(msg)
                f1.flush()

    def stop(self):
        for client in self.clients.values():
            client.close()
        self.sock.close()
        self.event.set()


def main():
    server = ChatTCPServer()
    server.start()
    while True:
        cmd = input(">>>")
        if cmd =="quit":
            server.stop()
            threading.Event().wait(3)
            break
        logging.info(threading.enumerate())

if __name__ == '__main__':
    main()
ChatTCPServer

 client 客户端实现

 创建socket对象

连接到远程服务端的ip,port,connect方法

传输数据

使用send,recv方法接受,发送数据

 关闭连接,释放资源

 1 import socket
 2 import threading
 3 import logging
 4 import datetime
 5 
 6 logging.basicConfig(format="%(asctime)s %(thread)s %(message)s",level=logging.INFO)
 7 
 8 class ChatTcpClient:
 9     def __init__(self,ip="127.0.0.1",port=9999):
10         self.sock = socket.socket()
11         self.event = threading.Event()
12         self.raddr = ip,port
13 
14     def start(self):
15         self.sock.connect(self.raddr)
16         self.sock.send(b"I'm ready")
17         threading.Thread(target=self.recv,name='recv').start()
18 
19     def recv(self):
20         while not self.event.is_set():
21             try:
22                 data = self.sock.recv(1024).decode()
23             except Exception as e:
24                 logging.info(e)
25                 break
26             msg = "{:%Y%m%d %H:%M:%S} {}:{}
{}".format(datetime.datetime.now(),*self.raddr,data.strip())
27             logging.info(msg)
28 
29     def send(self,msg:str):
30         data = "{}
".format(msg.strip()).encode()
31         self.sock.send(data)
32 
33     def stop(self):
34         self.sock.close()
35         self.event.wait(3)
36         self.event.set()
37         logging.info("Client stop")
38 
39 def main():
40     cc = ChatTcpClient()
41     cc.start()
42     while True:
43         cmd = input(">>>")
44         if cmd == "quit" or "":
45             cc.stop()
46             break
47         cc.send(cmd)
48 
49 if __name__ == '__main__':
50     main()
View Code
 
 
 
 
 
 
 
 
原文地址:https://www.cnblogs.com/rprp789/p/9901821.html