网络编程基础
1. socket之send和recv的原理剖析
- 当创建一个Tcp Socket对象时,会有一个发送缓冲区与一个接收缓冲区,这个发送缓冲区与接收缓冲区指的是内存中的一片空间。
- send原理:应用程序把发送的数据先写入到发送缓冲区,再由操作系统控制网卡把发送缓冲区的数据发送给服务端网卡。
- recv原理:应用程序调用操作系统接口,由操作系统通过网卡接收数据,把接收的数据写入到接收缓冲区,应用程序再从接收缓冲区获取客户端发送的数据。
2. 端口和端口号
- 端口号有65536个。
- 应用程序通过ip地址找到设备,通过端口号找到对应的端口,然后通过端口把数据传输给应用程序。
- 端口号可以标识唯一的一个端口。
- 端口号的分类
- 知名端口号(众所周知的端口号,范围:0~1023)
- 动态端口号(开发应用程序使用的端口号,范围:1024~65535)
3. tcp
- tcp是一个稳定、可靠的传输协议,常用于对数据进行准确无误的传输。
- 特点:面向连接、可靠传输
4. socket
- 作用:负责进程之间的网络数据传输。
5. tcp网络应用程序的开发流程
- 客户端开发流程
- 服务端开发流程
6. tcp客户端程序示例
- 示例
import socket if __name__ == '__main__': # 创建tcp客户端套接字 # AF_INET:ipv4地址类型 # SOCK_STREAM:tcp传输协议类型 tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 和服务端套接字建立连接 tcp_client_socket.connect("192.168.132.128", 8080) # 发送数据到服务器 send_content = "hello" # 对字符串进行编码成为二进制数据 send_data = send_content.encode("utf-8") tcp_client_socket.send(send_data) # 接收服务端的数据 # 1024表示每次接收的最大字节数 recv_data = tcp_client_socket.recv(1024) recv_content = recv_data.decode("utf-8") print("Recieved data: ", recv_content) # 关闭套接字 tcp_client_socket.close()
7. tcp服务端程序示例
- 示例
import socket if __name__ == '__main__': # 创建tcp服务端套接字 # AF_INET:ipv4,AF_INET6:ipv6 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用 # SOL_SOCKET:表示当前套接字 # SO_REUSEADDR:表示复用端口号的选项 # True:确定复用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 # 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可 # 第二个参数表示端口号 tcp_server_socket.bind(("", 9090)) # 设置监听 # 128:表示最大等待建立连接的个数 tcp_server_socket.listen(128) # 等待接受客户端的连接请求 # 每次当客户端和服务端建立连接成功都会返回一个新的套接字 new_client, ip_port = tcp_server_socket.accept() print("Client's ip and port: ", ip_port) # 接收客户端的数据 recv_data = new_client.recv(1024) recv_content = recv_data.decode("utf-8") print("Recieved Content: ", recv_content) # 发送数据到客户端 send_content = "......" send_data = send_content.encode("utf-8") new_client.send(send_data) # 关闭套接字 new_client.close() tcp_server_socket.close()
8. 端口号复用
- 当客户端和服务端建立连接后,服务端程序退出后端口后不会立即释放,需要等待大概1~2分钟。
- 解决方法:
- 更换服务端端口号
- 设置端口号复用(推荐),让服务端程序退出后,端口号立即释放。
- 示例
参照【7. tcp服务端程序示例】
9. tcp网络应用程序的注意点
- 当客户端的套接字调用close后,服务器端的recv会解阻塞,返回的数据长度为0,服务端可以根据返回的数据长度来判断客户端是否已经下线,反之服务端关闭套接字,客户端的recv也会解阻塞,返回的数据长度也为0。
10. tcp服务端服务于多个客户端示例
- 示例
import socket if __name__ == '__main__': # 创建tcp服务端套接字 # AF_INET:ipv4,AF_INET6:ipv6 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用 # SOL_SOCKET:表示当前套接字 # SO_REUSEADDR:表示复用端口号的选项 # True:确定复用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 # 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可 # 第二个参数表示端口号 tcp_server_socket.bind(("", 9090)) # 设置监听 # 128:表示最大等待建立连接的个数 tcp_server_socket.listen(128) # 循环等待接受客户端的连接请求 while: # 等待接受客户端的连接请求 # 每次当客户端和服务端建立连接成功都会返回一个新的套接字 new_client, ip_port = tcp_server_socket.accept() print("Client's ip and port: ", ip_port) # 接收客户端的数据 recv_data = new_client.recv(1024) recv_content = recv_data.decode("utf-8") print("Recieved Content: ", recv_content) # 发送数据到客户端 send_content = "......" send_data = send_content.encode("utf-8") new_client.send(send_data) # 关闭套接字 new_client.close() tcp_server_socket.close()
11. 多人版tcp服务端示例
- 示例
import socket import threading def handle_client_request(ip_port, new_client): print("Client's ip and port: ", ip_port) # 循环接收客户端信息 while: # 接收客户端的数据 recv_data = new_client.recv(1024) # 客户端断开连接判断 if recv_data: recv_content = recv_data.decode("utf-8") print("Recieved Content: ", recv_content, ip_port) # 发送数据到客户端 send_content = "......" send_data = send_content.encode("utf-8") new_client.send(send_data) else: break # 关闭套接字 new_client.close() if __name__ == '__main__': # 创建tcp服务端套接字 # AF_INET:ipv4,AF_INET6:ipv6 tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 设置端口号复用 # SOL_SOCKET:表示当前套接字 # SO_REUSEADDR:表示复用端口号的选项 # True:确定复用 tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 绑定端口号 # 第一个参数表示ip地址,一般不用指定,表示本机的任何一个ip即可 # 第二个参数表示端口号 tcp_server_socket.bind(("", 9090)) # 设置监听 # 128:表示最大等待建立连接的个数 tcp_server_socket.listen(128) # 循环等待接受客户端的连接请求 while: # 等待接受客户端的连接请求 # 每次当客户端和服务端建立连接成功都会返回一个新的套接字 new_client, ip_port = tcp_server_socket.accept() # 当客户端和服务端建立连接成功,创建子线程,让子线程专门负责接收客户端的消息 sub_thread = threading.Thread(target=handle_client_request, args=(ip_port, new_client)) # 设置守护主线程,主线程退出,子线程直接销毁 sub_thread.setDaemon(True) # 启动子线程执行对应的任务 sub_thread.start() tcp_server_socket.close()