Windows系统下基于TCP的Socket编程
.
一、前情提要
.
what’s TCP?
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC 793 [1] 定义。
TCP旨在适应支持多网络应用的分层协议层次结构。 连接到不同但互连的计算机通信网络的主计算机中的成对进程之间依靠TCP提供可靠的通信服务。TCP假设它可以从较低级别的协议获得简单的,可能不可靠的数据报服务。 原则上,TCP应该能够在从硬线连接到分组交换或电路交换网络的各种通信系统之上操作。
.
socket是什么?
套接字(socket)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合。
.
sockets又是什么?
.
Sockets是Windows下网络编程的规范,Windows Sockets是Windows下得到广泛应用的、开放的、支持多种协议的网络编程接口。从1991年的1.0版到1995年的2.0.8版,经过不断完善并在Intel、Microsoft、Sun、SGI、Informix、Novell等公司的全力支持下,已成为Windows网络编程的事实上的标准。
.
套接字类型
.
这个世界上有很多种套接字(socket),比如 DARPA Internet 地址(Internet 套接字)、本地节点的路径名(Unix套接字)、CCITT X.25地址(X.25 套接字)等。
根据数据的传输方式,可以将 Internet 套接字分成两种类型。通过 socket() 函数创建连接时,必须告诉它使用哪种数据传输方式。
.
流格式套接字(SOCK_STREAM)
.
流格式套接字(Stream Sockets)也叫“面向连接的套接字”,在代码中使用 SOCK_STREAM 表示。
SOCK_STREAM 是一种可靠的、双向的通信数据流,数据可以准确无误地到达另一台计算机,如果损坏或丢失,可以重新发送。
流格式套接字有自己的纠错机制。
SOCK_STREAM 有以下几个特征:
- 数据在传输过程中不会消失;
- 数据是按照顺序传输的;
- 数据的发送和接收不是同步的(有的教程也称“不存在数据边界”)。
.
数据报格式套接字(SOCK_DGRAM)
.
数据报格式套接字(Datagram Sockets)也叫“无连接的套接字”,在代码中使用 SOCK_DGRAM 表示。
计算机只管传输数据,不作数据校验,如果数据在传输中损坏,或者没有到达另一台计算机,是没有办法补救的。也就是说,数据错了就错了,无法重传。
因为数据报套接字所做的校验工作少,所以在传输效率方面比流格式套接字要高。
SOCK_DGRAM 没有想象中的糟糕,不会频繁的丢失数据,数据错误只是小概率事件。
可以将 SOCK_DGRAM 比喻成高速移动的摩托车快递,它有以下特征:
- 强调快速传输而非传输顺序;
- 传输的数据可能丢失也可能损毁;
- 限制每次传输的数据大小;
- 数据的发送和接收是同步的(“存在数据边界”)。
.
二、Windows下socket编程
.
Windows 下的 socket 程序和 Linux 思路相同,但细节有所差别:
-
Windows 下的 socket 程序依赖 Winsock.dll 或 ws2_32.dll,必须提前加载。DLL 有两种加载方式,请查看:动态链接库DLL的加载
-
Linux 使用“文件描述符”的概念,而 Windows 使用“文件句柄”的概念;Linux 不区分 socket 文件和普通文件,而 Windows 区分;Linux 下 socket() 函数的返回值为 int 类型,而 Windows 下为 SOCKET 类型,也就是句柄。
-
Linux 下使用 read() / write() 函数读写,而 Windows 下使用 recv() / send() 函数发送和接收。
-
关闭 socket 时,Linux 使用 close() 函数,而 Windows 使用 closesocket() 函数。
.
三、TCP连接的socket步骤
.
.
四、简易代码实践
.
server
.
.
client
.
.
简易版tcpServer
.
###单线程
import socket
#建立socket连接
server = socket.socket()
ipaddr = ('127.0.0.1', 9999)
server.bind(ipaddr)
server.listen()
#等待连接
s, raddr = server.accept()
while True:
#连接后接收的数据
data = s.recv(1024)
print(data)
s.send('ack.{}'.format(data.decode()).encode())
s.close()
server.close()
.
简易的tcpclient
.
#客户端
import socket
host = '127.0.0.1'
port = 9999
raddr = (host, port)
#指定接收数据
bufsize = 1024
#创建TCP Socket
tcpCilent = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#连接
tcpCilent.connect(raddr)
while True:
cmd = input("输入quit断开当前连接:\n")
print(cmd)
if cmd.strip(':') == 'quit':
break
else:
tcpCilent.send(cmd.encode())
data = tcpCilent.recv(bufsize)
print(data)
tcpCilent.close()
.
进阶版tcpServer–简单的实现了群聊
.
###多线程群聊
import socket
import threading #基于线程的并行
import logging #Python 的日志记录工具
import datetime #基本的日期和时间类型
FORMAT = "%(asctime)s %(threadName)s %(thread)d %(message)s"
logging.basicConfig(format=FORMAT, level=logging.INFO)
#创建类
class TcpChatServer:
#初始化、ip、port
def __int__(self, ip='127.0.0.1', port=9999):
self.addr = (ip, port)
self.sock = socket.socket()
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 True:
s, raddr = self.sock.accept() #返回新的套接字对象和客户端IP地址
logging.info(s)
logging.info(raddr)
#key = raddr, values = s
self.clients[raddr] = s
threading.Thread(target=self.recv, name='recv', args=(s,)).start()
def recv(self, sock:socket.socket):
while True:
try: #异常处理
data = sock.recv(1024) #阻塞点
logging.info(data) # 打印拿到的数据
except Exception as e:
logging.error(e)
data = b'quit'
if data == b'quit':
self.clients.pop(sock.getpeername())
break
msg = "ack{}. {} {}".format( #数据整理=地址+时间+内容
sock.getpeername(),
datetime.datetime.now().strftime("%Y/%m/%d-%H:%M:%S"),
data.decode()).encode()
#一对多、发送所有接受的数据
for s in self.clients.values():
s.send(msg)
#停止
def stop(self):
for s in self.clients.values():
s.close()
self.sock.close()
#custom
cs = TcpChatServer()
cs.__int__()
cs.start()
#测试、输入显示当前进程
while True:
cmd = input("输入任意键查看当前进程,输入quit退出\n")
if cmd.strip() == 'quit': #移除字符串头尾指定的字符(默认为空格或换行符)或字符序列
cs.stop()
threading.Event.wait(3) #阻塞线程直到内部变量为true
break
else:
logging.info(threading.enumerate()) #以列表形式返回当前所有存活的 Thread 对象
TCP/UDP socket调试工具 v2.3:
[https://download.csdn.net/download/qq_42404383/12235016]