目录

  • 导入
  • Server端
  • 创建基于网络的TCP
  • 端口占用问题
  • 起因
  • 解决
  • 绑定ip和port
  • 监听
  • 接收连接
  • 收与发数据
  • 收数据
  • 发数据
  • 什么时候使用send()什么时候使用sendall()
  • 断开与client的连接
  • 断开socket的连接
  • 说明
  • server.py整体代码
  • Client端
  • 创建基于网络的TCP
  • 发起请求连接
  • 收与发数据
  • 发数据
  • 收数据
  • 关闭socket连接
  • client.py整体代码


导入

import socket

Server端

创建基于网络的TCP

socket.socket()

# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

端口占用问题

端口占用会抛出 OSError

起因

这是因为服务端仍然存在四次挥手的time_wait状态在占用地址

解决

sk.setsockopt()

sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

绑定ip和port

ip:port,用来唯一标识一台主机上的一个应用程序
注意点:以元组形式传入
sk.bind((ip, port)) 以下四种形式都可以:

  • 绑定回环地址
  • sk.bind(('localhost', 8080))
  • sk.bind(('', 8080))
  • sk.bind(('127.0.0.1', 8080))
  • 绑定本机地址
  • sk.bind(('192.168.1.103', 8080)) 如果绑定了本机地址,那么就表示只要能ping通这个地址的主机,就能够访问该server

监听

sk.listen(backlog)

sk.listen(5)

标识最大等待连接数。注意:是客户端的等待连接,正在通信的不算。

换句话说:排队的人数(就是那个n) + 正在就餐的人数(服务器正在处理的socket连接数) = 允许接待的总人数(socket允许的最大连接数)

深入理解
        accept()方法一次只能接受一个Client的连接申请,但Client是多个的,于是socket
设计了一个队列来存储Client的连接申请,从而accept()方法就从这个队列中提取首位
成员处理即可。
所以,参数backlog指的就是这个队列的最大值,也就是同时受理连接申请的最大值,如果满了,就需要Client重新Connect。
关于backlog该设置多少,从Skynet得到的参考为32

具体可以参考以下两篇博文:



接收连接

sk.accept()accept(),返回一个元祖

  • [0]是一个与Client通信的双向连接
  • [1]Client地址
conn, addr = sk.accept()

收与发数据

收数据

conn.recv()recv两个阶段

  1. 等待数据到达内核态
  2. 从内核态copy数据到用户态
data = conn.recv(1024)  # 参数1024表示,最多接收1024个字节
print(data.decode(encoding='utf-8'))

发数据

conn.send()返回值:发送数据的字节数

conn.send(bytes('received', encoding='utf-8'))

说明
send一个阶段

  1. copy数据到内核态

使用send()进行发送的时候,Python将内容传递给系统底层的send接口,也就是说,Python并不知道这次调用是否会全部发送完成,比如MTU是1500,但是此次发送的内容是2000,那么除了包头等等其他信息占用,发送的量可能在1000左右,还有1000未发送。

但是,send()不会继续发送剩下的包,因为它只会发送一次,发送成功之后会返回此次发送的字节数,如上例,会返回数字1000给用户,然后就结束了

如果需要将剩下的1000发送完毕,需要用户自行获取返回结果,然后将内容剩下的部分继续调用send()进行发送,比如:

sended = 0
while 1:
    sended = conn.send(result[sended:])
    if not sended:
        break

conn.sendall()sendall()是对send()的包装,完成了用户需要手动完成的部分,它会自动判断每次发送的内容量,然后从总内容中删除已发送的部分,将剩下的继续传给send()进行发送。

什么时候使用send()什么时候使用sendall()

一般情况下,我们都应该使用sendall(),除非自己弄懂了他们的原理,并且有必要在每次包发送之后进行一些必要的处理,不然我们都不需要去使用send(),而应该使用已经包装好的sendall()


断开与client的连接

conn.close()

conn.close()

断开socket的连接

sk.close()

sk.close()

说明

Server端有两个socket套接字

  1. conn
  2. sk

基于TCPsocket,得先启动Server

Server可以先收也可以先发,Client也一样

TCP是面向连接的、安全的、可靠的、面向流的无消息保护边界

server.py整体代码

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""简单的socket通信

Server端
"""

import socket

# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

# 端口占用会抛出 OSError
# 端口占用的起因
# 这是因为服务端仍然存在四次挥手的time_wait状态在占用地址

# 解决端口占用问题
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定ip和port,用来唯一标识一台主机上的一个应用程序
# 注意点:以元组形式传入
# 以下四种形式都可以:
# 绑定回环地址
sk.bind(('localhost', 8080))
# sk.bind(('', 8080))
# sk.bind(('127.0.0.1', 8080))
# 绑定本机地址
# sk.bind(('192.168.1.103', 8080))
# 如果绑定了本机地址,那么就表示只要能ping通这个地址的主机,就能够访问该server

# 监听
# listen(backlog)
# 标识最大等待连接数。注意:是客户端的等待连接,正在通信的不算
# 换句话说:排队的人数(就是那个n) + 正在就餐的人数(服务器正在处理的socket连接数) = 允许接待的总人数(socket允许的最大连接数)
# 深入理解:
# accept()方法一次只能接受一个Client的连接申请,但Client是多个的,于是socket
# 设计了一个队列来存储Client的连接申请,从而accept()方法就从这个队列中提取首位
# 成员处理即可。
# 所以,参数backlog指的就是这个队列的最大值,也就是同时受理连接申请的最大值,
# 如果满了,就需要Client重新Connect。
# 关于backlog该设置多少,从Skynet得到的参考为32
# 具体可以参考以下两篇博文:
# 
# 
sk.listen(5)

# 接收连接
# accept(),返回一个元祖,[0]是一个与Client通信的双向连接;[1]是Client的地址
conn, addr = sk.accept()

# 收、发数据
# 收
# recv两个阶段
# 1. 等待数据到达内核态
# 2. 从内核态copy数据到用户态
data = conn.recv(1024)  # 参数1024表示,最多接收1024个字节
print(data.decode(encoding='utf-8'))

# 发
# 返回值:发送数据的字节数
# send一个阶段
# 1. copy数据到内核态
conn.send(bytes('received', encoding='utf-8'))

# 断开与Client的连接
conn.close()

# 断开socket的连接
sk.close()

# 说明:
# Server端有两个socket套接字
# 1. conn
# 2. sk

# 基于TCP的socket,得先启动Server
# Server可以先收也可以先发,Client也一样

# TCP是面向连接的、安全的、可靠的、面向流的无消息保护边界

Client端

创建基于网络的TCP

socket.socket()

# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

发起请求连接

ip:port发起连接请求
客户端connect的时候ip不能写空,跟服务器端稍微有点区别
sk.connect((ip, port))

sk.connect(('localhost', 8080))

当目标主机不存在时,connect抛出ConnectionRefusedError异常
connect()函数的扩展版本connect_ex(),出错时返回出错码,而不是抛出异常;连接正常时,返回0

print(sk.connect_ex(('localhost', 8080)))

收与发数据

发数据

conn.send()

sk.send(bytes('我要连你...', encoding='utf-8'))

收数据

conn.recv()

data = sk.recv(1024)
print(data.decode(encoding='utf-8'))

关闭socket连接

sk.close()

sk.close()

client.py整体代码

#!/usr/bin/python3
# -*- coding: utf-8 -*-
"""简单的socket通信

Client端
"""

import socket

# 基于网络的TCP
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)

# 向ip:port发起连接请求
sk.connect(('localhost', 8080))

# 发
sk.send(bytes('我要连你...', encoding='utf-8'))

# 收
data = sk.recv(1024)
print(data.decode(encoding='utf-8'))

# 关闭socket连接
sk.close()