zoukankan      html  css  js  c++  java
  • 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 => {}:{}
     data => {}
    ".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 => {}:{}
     data => {}
    ".format(datetime.datetime.now(),*self.addr, data.strip())
    39             logging.info(msg)
    40 
    41     def send(self, msg:str):
    42         data = "{}
    ".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()
  • 相关阅读:
    高性能TcpServer(Java)
    高性能TcpServer(C#)
    高性能TcpServer(C#)
    高性能TcpServer(C#)
    高性能TcpServer(C#)
    高性能TcpServer(C#)
    高性能TcpServer(C#)
    MySQL连表Update修改数据
    windows服务器安装安全狗时服务名如何填写
    织梦ckeditor编辑器 通过修改js去除img标签内的width和height样式
  • 原文地址:https://www.cnblogs.com/yinzhengjie/p/11974087.html
Copyright © 2011-2022 走看看