前言
最近在做项目的时候需要进行一个简单的跨语言通信,有用到socket和websocket,这次详细记录下学习socket的过程。
什么是TCP?
1.TCP是面向连接的协议,是允许系统通过Internet进行通信的标准,它定义了如何建立和维护应用程序可以通过其交换数据的网络对话;保证了数据的传递,并保证数据包的发送顺序与发送数据包的顺序相同。(传输控制协议)
2.TCP是OSI层中的传输层协议,用于通过传输和确保通过支持网络和Internet传递消息来在远程计算机之间创建连接。
3.TCP是数字网络通信中最常用的协议之一,是Internet协议套件的一部分,它和IP一起是定义Internet的基本规则,通常称为TCP / IP套件。
4.主要作用:数据传输,确保不同不同节点之间的端到端的数据传输,确保不同节点之间的端到端的数据传输,在远程计算机之间创建连接。在传输过程中,为保证传输质量,TCP层将大数据分成长度合适的较小的数据包分别发送,并确保在目标节点重组后数据完整性保持不变。
一、什么是Socket?
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
二、Socket模块功能
1.socket()函数
socket.socket([family[, type[, proto]]] )使用给定的地址族 、套接字类型 、协议编号 (默认为0 )来创建套接字。
套接字格式:
socket类型 | 描述 |
---|---|
socket.AF_UNIX | 只能够用于单一的Unix系统进程间通信 |
socket.AF_INET | 服务器之间通信 |
socket.AF_INET6 | IPV6 |
socket.SOCK_STREAM | 流式socket,for Tcp |
socket.SOCK_DGRAM | 数据报式socket,for UDP |
socket.SOCK_RAW | 原始套接字,普通的套接字无法处理ICMP、IGMP等网络报文,而SOCK_RAW可以;其次,SOCK_RAW也可以处理特殊的IPv4报文;此外,利用原始套接字,可以通过IP_HDRINCL套接字选项由用户构造IP头。 |
socket.SOCK_SEQPACKET | 可靠的连续数据包服务 |
创建TCP Socket: | s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) |
创建UDP Socket: | s=socket.socket(socket.AF_INET,socket.SOCK_DGRAM |
2.socket 对象(内建)方法
函数 | 描述 |
---|---|
服务端socket函数 | |
s.bind() | 绑定地址(host,port)到套接字, 在 AF_INET下,以元组(host,port)的形式表示地址 |
s.listen() | 开始 TCP 监听。backlog 指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为 1,大部分应用程序设为 5 就可以了 |
s.accept() | 被动接受TCP客户端连接,(阻塞式)等待连接的到来 |
客户端 socket函数 | |
s.connect() | 主动初始化TCP服务器连接,。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误 |
s.connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数 | |
s.recv() | 接收 TCP 数据,数据以字符串形式返回,bufsize 指定要接收的最大数据量。flag 提供有关消息的其他信息,通常可以忽略 |
s.send() | 发送 TCP 数据,将 string 中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于 string 的字节大小 |
s.sendall() | 完整发送 TCP 数据。将 string 中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回 None,失败则抛出异常。 |
s.recvfrom() | 接收 UDP 数据,与 recv() 类似,但返回值是(data,address)。其中 data 是包含接收数据的字符串,address 是发送数据的套接字地址 |
s.sendto() | 发送 UDP 数据,将数据发送到套接字,address 是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数 |
s.close() | 关闭套接字 |
s.getpeername() | 返回连接套接字的远程地址。返回值通常是元组(ipaddr,port) |
s.getsockname() | 设置给定套接字选项的值 |
s.setsockopt(level,optname,value) | 返回套接字选项的值 |
s.getsockopt(level,optname[.buflen]) | 设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect()) |
s.gettimeout() | 返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。 |
s.fileno() | 返回套接字的文件描述符 |
s.setblocking(flag) | 如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常 |
s.makefile() | 创建一个与该套接字相关连的文件 |
三、简单实例
1.通用实例
服务端
python
#!/usr/bin/python3
# -*-coding:utf-8 -*-
from socket import *
import time
COD = 'utf-8'
HOST = '192.168.164.141' # 主机ip
PORT = 21566 # 软件端口号
BUFSIZ = 1024
ADDR = (HOST, PORT)
SIZE = 10
tcpS = socket(AF_INET, SOCK_STREAM) # 创建socket对象
tcpS.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #加入socket配置,重用ip和端口
tcpS.bind(ADDR) # 绑定ip端口号
tcpS.listen(SIZE) # 设置最大链接数
while True:
print("服务器启动,监听客户端链接")
conn, addr = tcpS.accept()
print("链接的客户端", addr)
while True:
try:
data = conn.recv(BUFSIZ) # 读取已链接客户的发送的消息
except Exception:
print("断开的客户端", addr)
break
print("客户端发送的内容:",data.decode(COD))
if not data:
break
msg = time.strftime("%Y-%m-%d %X") #获取结构化事件戳
msg1 = '[%s]:%s' % (msg, data.decode(COD))
conn.send(msg1.encode(COD)) #发送消息给已链接客户端
conn.close() #关闭客户端链接
tcpS.closel()
客户端
python
#!/usr/bin/python3
# -*-coding:utf-8 -*-
from socket import *
from time import ctime
HOST = '192.168.164.141' #服务端ip
PORT = 21566 #服务端端口号
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM) #创建socket对象
tcpCliSock.connect(ADDR) #连接服务器
while True:
data = input('>>').strip()
if not data:
break
tcpCliSock.send(data.encode('utf-8')) #发送消息
data = tcpCliSock.recv(BUFSIZ) #读取消息
if not data:
break
print(data.decode('utf-8'))
tcpCliSock.close() #关闭客户端
2.文件传输
传输文件主要分以下两步:
1.将要传输的文件的基本信息发送到接收端(文件名、大小等其他信息)
2.发送端读取文件内容并发送过去,接受端将缓存里面的内容写入文件
服务端
python
#!/usr/bin/env python
# -*- coding=utf-8 -*-
"""
file: send.py
socket client
"""
import socket
import os
import sys
import struct
def socket_client():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 6666))
except socket.error as msg:
print msg
sys.exit(1)
print s.recv(1024)
while 1:
filepath = raw_input('please input file path: ')
if os.path.isfile(filepath):
# 定义定义文件信息。128s表示文件名为128bytes长,l表示一个int或log文件类型,在此为文件大小
fileinfo_size = struct.calcsize('128sl')
# 定义文件头信息,包含文件名和文件大小
fhead = struct.pack('128sl', os.path.basename(filepath),
os.stat(filepath).st_size)
s.send(fhead)
print 'client filepath: {0}'.format(filepath)
fp = open(filepath, 'rb')
while 1:
data = fp.read(1024)
if not data:
print '{0} file send over...'.format(filepath)
break
s.send(data)
s.close()
break
if __name__ == '__main__':
socket_client()
客户端
python
#!/usr/bin/env python
# -*- coding=utf-8 -*-
"""
file: recv.py
socket service
"""
import socket
import threading
import time
import sys
import os
import struct
def socket_service():
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 6666))
s.listen(10)
except socket.error as msg:
print msg
sys.exit(1)
print 'Waiting connection...'
while 1:
conn, addr = s.accept()
t = threading.Thread(target=deal_data, args=(conn, addr))
t.start()
def deal_data(conn, addr):
print 'Accept new connection from {0}'.format(addr)
#conn.settimeout(500)
conn.send('Hi, Welcome to the server!')
while 1:
fileinfo_size = struct.calcsize('128sl')
buf = conn.recv(fileinfo_size)
if buf:
filename, filesize = struct.unpack('128sl', buf)
fn = filename.strip('\00')
new_filename = os.path.join('./', 'new_' + fn)
print 'file new name is {0}, filesize if {1}'.format(new_filename,
filesize)
recvd_size = 0 # 定义已接收文件的大小
fp = open(new_filename, 'wb')
print 'start receiving...'
while not recvd_size == filesize:
if filesize - recvd_size > 1024:
data = conn.recv(1024)
recvd_size += len(data)
else:
data = conn.recv(filesize - recvd_size)
recvd_size = filesize
fp.write(data)
fp.close()
print 'end receive...'
conn.close()
break
if __name__ == '__main__':
socket_service()
3.测试题目
本机实现一个服务器+客户端,包含以下功能
1、服务器可以同时接受一个客户端的链接
2、客户端连接成功后,可以输入以下信息并且获得正确的返回
a. Send: Hello Recive: Hello,This is Server {ip}:{port}
b. Send: 数学计算公式字符串 Recive: 计算结果后返回
c. 发送一个字符串 和一个匹配格式,返回匹配格式中的每一个值 : "Cpu:1235Id, \nTestId:0928N;" "Cpu:{cpuid}, \nTestId:{testid}" , 自己设计这个指令
3.断开链接后,服务器不能Crash,随时等待另外一次链接
4.客户端可以通过输入的方式,获取需要发送的信息,特殊指令:exit,断开客户端链接
服务端
python
# !/usr/bin/python
# -*- coding: utf-8 -*-
"""
@File : server.py
@Time : 2022/04/12
@Author : Algabeno
---------
Function List:
---------
"""
import re
import socket # 导入 socket 模块
from threading import Thread
import time
ADDRESS = ('127.0.0.1', 8712) # 绑定地址
g_socket_server = None # 负责监听的socket
def init():
"""
初始化服务端
"""
global g_socket_server
g_socket_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
g_socket_server.bind(ADDRESS)
g_socket_server.listen(5) # 最大等待数(有很多人理解为最大连接数,其实是错误的)
print("server start,wait for client connecting...")
def accept_client():
"""
接收新连接
"""
while True:
client, info = g_socket_server.accept() # 阻塞,等待客户端连接
# 给每个客户端创建一个独立的线程进行管理
thread = Thread(target=message_handle, args=(client, info))
# 设置成守护线程
thread.setDaemon(True)
thread.start()
# thread.join() # 阻塞线程,等待上一个线程处理完
def strip_str(string_value):
"""
@description : 去除字符串中的{}
---------
@param :字符串
-------
@Returns :去除了{}的字符串
-------
"""
string_value = string_value.replace('{', '')
string_value = string_value.replace('}', '')
return string_value
def get_value(string_value):
"""
@description :对字符串进行正则化处理,获取冒号后的值
---------
@param :格式为:"name:nameinfo???, \nAge:90" 或者 "name:{cpuid}, \nAge:{testid}
-------
@Returns :nameinfo,90 或者 cpuid,testid
-------
"""
string_list = re.split(',', string_value)
temp1 = re.split(':', string_list[0])[1]
temp2 = re.split(':', string_list[1])[1]
return strip_str(temp1), strip_str(temp2)
def match_str(string):
"""
@description : 对规定形式的字符串进行匹配
---------
@param :"name:nameinfo???, \nAge:90";"name:{cpuid}, \nAge:{testid}
-------
@Returns :{"cpuid":"nameinfo???","testid":"90"}
-------
"""
value_list = re.split(';', string)
name_info, name = value_list[0], value_list[1]
value1, value2 = get_value(name_info)
key1, key2 = get_value(name)
final_value = '{' + f'{key1}:{value1},{key2}:{value2}' + '}'
return final_value
def entrance_func(client, message):
"""
@description :对传入的message判断是计算字符串值还是进行字符串匹配
---------
@param :连接客户端的值,客户端发送过来的信息
-------
@Returns : None
-------
"""
try:
data = eval(message)
client.sendall(f'The result is {data}'.encode('utf-8')) # 发送计算好的结果
except Exception:
try:
data = match_str(message)
client.sendall(f'{data}'.encode('utf-8')) # 发送处理好的字符串
except Exception:
error = f'请发送正确的格式'
client.sendall(f'{error}'.encode('utf-8')) # 发送报错信息
def message_handle(client, info):
"""
@description :对连接的客户端发送过来
---------
@param :连接的客户端,客户端ip地址
-------
@Returns :None
-------
"""
addr = client.getsockname()
print(f"当前连接客户端ip为{info}")
while True:
try:
msg = client.recv(1024).decode(encoding='utf8') # 解析传来的信息
except:
print("断开的客户端", info)
break
if not msg:
break
print(f'已接收到{info}的信息:{msg}')
if msg == 'exit':
client.close() # 关闭客户端
break
elif msg == 'Hello':
host_info = g_socket_server.getsockname()
client.sendall(f' Hello,This is Server {host_info[0]}:{host_info[1]}'.encode('utf-8')) # 向客户端打招呼
else:
entrance_func(client, msg)
if __name__ == '__main__':
init()
# 新开一个线程,用于接收新连接
thread = Thread(target=accept_client)
thread.setDaemon(True)
thread.start()
# 主线程逻辑
while True:
time.sleep(0.1)
客户端
python
#!/usr/bin/python3
# -*-coding:utf-8 -*-
from socket import *
from time import ctime
HOST = '127.0.0.1' # 服务端ip
PORT = 8712 # 服务端端口号
BUFSIZ = 1024
ADDR = (HOST, PORT)
tcpCliSock = socket(AF_INET, SOCK_STREAM) # 创建socket对象
tcpCliSock.connect(ADDR) # 连接服务器
while True:
data = input('请输入内容\n>>').strip()
tcpCliSock.send(data.encode('utf-8')) # 发送消息client.py
if data == 'exit':
break
data = tcpCliSock.recv(BUFSIZ) # 读取消息
if not data:
break
print(data.decode('utf-8'))
# tcpCliSock.close() # 关闭客户端