socket

一、socket 

    我们知道两个进程如果需要进行通讯最基本的一个前提是能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟蹊径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。

    能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。

    socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。

二、基于TCP和UDP两个协议下socket的通迅流程

1. TCP和UDP对比

TCP:

  • 可靠的、面向连接的协议,传输效率低,全双工通信(发送缓存,接收缓存),面向字节流。应用:Web浏览器,文件传输程序。
  • TCP是面向流的,如果多次发送的数据很小,并且每次发送间隔时间很短,就有可能会被拼到一个数据流里面。TCP可以一段一段的取值,但是只能从头开始取,不能从中间取。

UDP:

  • 不可靠的、无连接的服务,传输效率高;可实现一对一,一对多,多对一,多对多;面向报文(数据包),无拥塞控制。应用:域名系统(DNS);视频流;IP语音
  • UDP不能取半个包,会报错,所以如果数据包很大,那么你一次接收的时候,设置的接收大小也要很大,不然会报错,缓冲区错误。

2. TCP协议下的socket

  • TCP协议下的socket通信,必须是一收一发对应好。
import socket

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

ip_port = ("127.0.0.1", 8001)

server.bind(ip_port)  # 绑定端口

server.listen()   # 监听
while 1:
    conn, addr = server.accept()  # 等待连接

    while 1:
        from_client_msg = conn.recv(1024)  # 接收消息
        ret = from_client_msg.decode("utf-8")
        print(ret)
        if ret == "bye":
            break
        msg = input("服务端>>>")
        conn.send(msg.encode("utf-8"))  # 发消息
        if msg == "bye":
            break
    conn.close()
server端

 

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_ip_port = ("127.0.0.1", 8001)  # 服务端IP,端口

client.connect(server_ip_port)  # 连接服务端

while 1:
    msg = input("客户端>>>")
    client.send(msg.encode("utf-8"))  # 发送消息
    if msg == "bye":
        break
    from_server_msg = client.recv(1024)  # 接收消息
    ret = from_server_msg.decode("utf-8")
    print(ret)
    if ret == "bye":
        break
client端

 

   当有多个客户端运行时,第一个客户端可以和服务端收发消息,但是第二个连接的客户端发消息,服务端是收不到的。

原因:tcp属于长连接,长连接就是一直占用着这个链接,这个连接的端口被占用了,第二个客户端过来连接的时候,他是可以连接的,但是处于一个占线的状态,就只能等着去跟服务端建立连接,除非一个客户端断开了(优雅的断开可以,如果是强制断开就会报错,因为服务端的程序还在第一个循环里面),然后就可以进行和服务端的通信了

3. UDP协议下的socket

import socket

server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)  # 创建对象

ip_port = ("127.0.0.1", 8001)

server.bind(ip_port)  # 绑定端口

while 1:
    msg, addr = server.recvfrom(1024)  # 阻塞状态,等待接收消息
    print("来自[%s:%s]的一条消息:%s" % (addr[0], addr[1],msg.decode("utf-8")))

    back_msg = input("回复消息:").strip()

    server.sendto(back_msg.encode("utf-8"), addr)
server端

 

import socket

client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
name_dict = {
    "Tom": ("127.0.0.1", 8001),
    "john": ("127.0.0.1", 8001),
    "Linda": ("127.0.0.1", 8001),
}
while 1:
    name = input("请选择聊天对象:").strip()
    while 1:
        msg = input("请输入消息,输入q结束:").strip()
        if msg.upper() == "Q":
            break
        if not msg or not name or name not in name_dict:
            continue
        client.sendto(msg.encode("utf-8"), name_dict[name])

        back_msg, addr = client.recvfrom(1024)
        print("来自[%s:%s]的一条消息:%s" % (addr[0], addr[1], back_msg.decode("utf-8")))
client端

 三、缓冲区

1. 原理 

     每个socket被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

 

  • write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。
  • read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。
  • TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

2. I/O缓冲区特性

  • I/O缓冲区在每个TCP套接字中单独存在
  • I/O缓冲区在创建套接字时自动生成
  • 即使关闭套接字也会继续传送输出缓冲区中遗留的数据
  • 关闭套接字将丢失输入缓冲区中的数据
  • 输入输出缓冲区的默认大小一般都是8K,可以通过getsockopt()函数获取
import socket
from socket import SOL_SOCKET, SO_SNDBUF, SO_RCVBUF

sk = socket.socket()

print(sk.getsockopt(SOL_SOCKET, SO_SNDBUF)/1024)  # 64.0
print(sk.getsockopt(SOL_SOCKET, SO_RCVBUF)/1024)  # 64.0

     使用udp协议时,你的一个数据包的大小超过了你一次recv能接受的大小,则报错,TCP不会,但是超出缓存区大小的时候,肯定会报错。

posted @ 2018-11-22 20:56  Ethan_Y  阅读(182)  评论(0)    收藏  举报