一、客户端/服务器架构

网络中到处都应有了C/S架构,我们学习socket就是为了完成C/S架构的开发。

二、scoket与网络协议

如果想要实现网络通信我们需要对tcpip,http等很多网络知识有比较深刻的学习以后才有这样的能力,但是对于我们程序开发程序员来说是一件漫长的时间,所以就有了封装比较好的socket来帮我们解决这些问题,使得我们的关注点不再是繁杂的网络协议等问题。socket已经为我们封装好了,我们只需要遵循socket的规定去编程,写出的程序自然就是遵循tcp/udp标准的。

socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。

简单来说我们可以把socket说成ip+端口,所以标识了互联网中独一无二的一个应用程序。

三、套接字

套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。

基于文件类型的套接字家族:AF_UNIX

unix一切皆文件,基于文件的套接字调用的就是底层的文件系统来取数据,两个套接字进程运行在同一机器,可以通过访问同一个文件系统间接完成通信

基于网络类型的套接字家族:AF_INET

还有AF_INET6被用于ipv6,还有一些其他的地址家族,AF_INET是使用最广泛的一个,python支持很多种地址家族,但是由于我们只关心网络编程,所以大部分时候我么只使用AF_INET

套接字函数:

1)socket()模块

import socket

获取tcp/ip套接字

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

获取udp/ip套接字

udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

使用 'from socket import *',我们就把 socket 模块里的所有属性都带到我们的命名空间里了,这样能 大幅减短我们的代码。例如tcpSock = socket(AF_INET, SOCK_STREAM)

2)服务端套接字函数

bind() 绑定(主机,端口号)到套接字

listen() 开始TCP监听

accept() 被动接受TCP客户的连接,(阻塞式)等待连接的到来

3)客户端套接字函数

connect() 主动初始化TCP服务器连接

connect_ex() connect()函数的扩展版本,出错时返回出错码,而不是抛出异常

4)公共用途的套接字函数

recv() 接收TCP数据

send() 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完)

sendall() 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完)

recvfrom() 接收UDP数据

sendto() 发送UDP数据

getpeername() 连接到当前套接字的远端的地址

getsockname() 当前套接字的地址

getsockopt() 返回指定套接字的参数

setsockopt() 设置指定套接字的参数

close() 关闭套接字

5)面向锁的套接字函数

setblocking() 设置套接字的阻塞与非阻塞模式

settimeout() 设置阻塞套接字操作的超时时间

gettimeout() 得到阻塞套接字操作的超时时间

6)面向文件的套接字函数

fileno() 套接字的文件描述符

makefile() 创建一个与该套接字相关的文件

四、基于tcp的套接字代码实现

tcp是基于链接的,必须先启动服务端,然后再启动客户端去链接服务端。

server:

ss = socket() #创建服务器套接字

ss.bind() #把地址绑定到套接字

ss.listen() #监听链接

inf_loop: #服务器无限循环

cs = ss.accept() #接受客户端链接

comm_loop: #通讯循环

cs.recv()/cs.send() #对话(接收与发送)

cs.close() #关闭客户端套接字

ss.close() #关闭服务器套接字

View Code

client:

cs = socket() #创建客户套接字

cs.connect() #尝试连接服务器

comm_loop: #通讯循环

cs.send()/cs.recv() #对话(发送/接收)

cs.close() #关闭客户套接字

View Code

socket通信与打电话的方式很相似:

server:

#_*_coding:utf-8_*_

importsocket

ip_port=('127.0.0.1',8080)#电话卡

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #买手机

s.bind(ip_port) #手机插卡

s.listen(5) #手机待机,监听最多五个

while True: #新增接收链接循环,可以不停的接电话

conn,addr=s.accept() #手机接电话

print('接到来自%s的电话' %addr[0])while True: #新增通信循环,可以不断的通信,收发消息

msg=conn.recv(BUFSIZE) #听消息,听话

#if len(msg) == 0:break #如果不加,那么正在链接的客户端突然断开,recv便不再阻塞,死循环发生

print(msg,type(msg))

conn.send(msg.upper())#发消息,说话

conn.close()#挂电话

s.close()#手机关机

server

client:

#_*_coding:utf-8_*_

importsocket

ip_port=('127.0.0.1',8081)

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

s.connect_ex(ip_port)#拨电话

while True: #新增通信循环,客户端可以不断发收消息

msg=input('>>:').strip()if len(msg) == 0:continues.send(msg.encode('utf-8')) #发消息,说话(只能发送字节类型)

feedback=s.recv(BUFSIZE) #收消息,听话

print(feedback.decode('utf-8'))

s.close()#挂电话

client

在操作的过程中重启服务端可能会出现OSError,地址已经在使用了,出现这种问题的原因是因为基于tcp四次挥手并没有结束,所以端口仍被占用,所以需要加入一条socket配置重新使用ip和端口。

phone=socket(AF_INET,SOCK_STREAM)

phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加

phone.bind(('127.0.0.1',8080))

View Code

我们还可以模拟ssh实现远程模拟命令:

importsocketimportsubprocess

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

phone.bind(('127.0.0.1',8080))

phone.listen(5)print('starting...')whileTrue:

conn,client_addr=phone.accept()whileTrue:try:

cmd=conn.recv(1024)#if not cmd:break #针对linux

#执行cmd命令,拿到cmd的结果,结果应该是bytes类型

res = subprocess.Popen(cmd.decode('utf-8'), shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

stdout=res.stdout.read()

stderr=res.stderr.read()#发送命令的结果

conn.send(stdout+stderr)exceptException:breakconn.close()#挂电话

phone.close() #关机

server

importsocket

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

phone.connect(('127.0.0.1',8080))whileTrue:

cmd=input('>>:').strip()if not cmd:continuephone.send(cmd.encode('utf-8'))

cmd_res=phone.recv(1024)print(cmd_res.decode('gbk'))

phone.close()

client

这里我们用到了subprocess模块,允许你去创建一个新的进程让其执行另外的程序,并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等。

subprocess模块中定义了一个Popen类,通过它可以来创建进程,并与其进行复杂的交互。subprocess模块的结果的编码是以当前所在的系统为准的,如果是windows,那么res.stdout.read()读出的就是GBK编码的,在接收端需要用GBK解码。

他的init函数是这样的:

__init__(self, args, bufsize=0, executable=None,

stdin=None, stdout=None, stderr=None, preexec_fn=None,

close_fds=False, shell=False, cwd=None, env=None,

universal_newlines=False, startupinfo=None,

creationflags=0)

args:必须是一个字符串或者序列类型,用于指定进程的可执行文件及其参数。如果是一个序列类型参数,则序列的第一个元素通常都必须是一个可执行文件的路径。当然也可以使用executeable参数来指定可执行文件的路径。

stdin,stdout,stderr:分别表示程序的标准输入、标准输出、标准错误。有效的值可以是PIPE,存在的文件描述符,存在的文件对象或None,如果为None需从父进程继承过来,stdout可以是PIPE,表示对子进程创建一个管道,stderr可以是STDOUT,表示标准错误数据应该从应用程序中捕获并作为标准输出流stdout的文件句柄。

shell:如果这个参数被设置为True,程序将通过shell来执行。

env:它描述的是子进程的环境变量。如果为None,子进程的环境变量将从父进程继承而来。

res = subprocess.Popen(r'dir', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

五、基于udp的套接字

udp是无链接的,先启动哪一端都不会报错。

ss = socket() #创建一个服务器的套接字

ss.bind() #绑定服务器套接字

while True : #服务器无限循环

cs = ss.recvfrom()/ss.sendto() #对话(接收与发送)

ss.close() #关闭服务器套接字

cs = socket() #创建客户套接字

while True : #通讯循环

cs.sendto()/cs.recvfrom() #对话(发送/接收)

cs.close() #关闭客户套接字

基于udp套接字的简单示例

importsocket

ip_port=('127.0.0.1',9000)

udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)

udp_server_client.bind(ip_port)whileTrue:

msg,addr=udp_server_client.recvfrom(1024)print(msg,addr)

udp_server_client.sendto(msg.upper(),addr)

udpserver

from socket import *udp_cs=socket(AF_INET,SOCK_DGRAM)whileTrue:

msg=input('>>:').strip()if not msg:continueudp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',9000))

msg,addr=udp_cs.recvfrom(1024)print(msg.decode('utf-8'),addr)

udpclient

qq聊天就是基于udp完成的,由于udp无连接,所以可以同时多个客户端去跟服务端通信。

from socket import *udp_ss=socket(AF_INET,SOCK_DGRAM)

udp_ss.bind(('127.0.0.1',8081))whileTrue:

msg,addr=udp_ss.recvfrom(1024)print('来自[%s]的一条消息:%s' %(addr,msg.decode('utf-8')))

msg_b=input('回复消息:').strip()

udp_ss.sendto(msg_b.encode('utf-8'),addr)

qqserver

from socket import *udp_cs=socket(AF_INET,SOCK_DGRAM)whileTrue :

msg= input('请输入消息,回车发送:').strip()if msg == 'quit' : break

if not msg : continueudp_cs.sendto(msg.encode('utf-8'),('127.0.0.1',8081))

back_msg,addr= udp_cs.recvfrom(1024)print('来自[%s]的一条消息:%s' %(addr,back_msg.decode('utf-8')))

udp_cs.close()

qqclient

client可以开启多个。

六、粘包

粘包现象只会发生在tcp的链接过程中,udp是不会发生粘包现象的(UDP是面向消息的协议,每个UDP段都是一条消息,应用程序必须以消息为单位提取数据,不能一次提取任意字节的数据)。

上图是socket收发消息的原理图,TCP协议是面向流的协议,应用程序得到的数据是一个整体数据流(stream),一条消息有多少字节对于应用程序是不可见的,消息从哪开始到哪结束,应用程序一无所知,这就导致出现粘包问题了。

粘包问题本质就是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

从底层数据报文来看:tcp收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了。而udp支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。

因为这些差异,tcp收发消息都不能为空,在客户端和服务端都要添加空消息处理机制,防止程序卡死。udp不是基于流的数据报,即使你输入的内容是空发出去的数据报还是由包头证明自己的长度是0。

udp虽然不粘包但是也有他的缺点,大家都叫他不可靠传输,udp的recvfrom是阻塞的,一个recvfrom(x)必须对唯一一个sendinto(y),收完了x个字节的数据就算完成,若是y>x数据就丢失,这意味着udp根本不会粘包,但是会丢数据,不可靠 tcp的协议数据不会丢,没有收完包,下次接收,会继续上次继续接收,己端总是在收到ack时才会清除缓冲区内容。数据是可靠的,但是会粘包。

粘包的两种情况:

1.在发送端发送数据的时间间隔很短,数据本身很小会合到一起产生粘包现象。

2.客户端发送的数据比较大超过了服务端一次可以接收的范围,所以服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包。大的数据报在发送端的缓冲区长度大于网卡的最大传输数据单元,tcp会将数据拆分成几个数据包再发送出去。

如何解决粘包问题?

客户端每次都把自己的长度告诉服务端,这样可以做到不粘包。

#_*_coding:utf-8_*_

importsocket,subprocess

ip_port=('127.0.0.1',8080)

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

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

s.bind(ip_port)

s.listen(5)whileTrue:

conn,addr=s.accept()print('客户端',addr)whileTrue:

msg=conn.recv(1024)if not msg:breakres=subprocess.Popen(msg.decode('utf-8'),shell=True,\

stdin=subprocess.PIPE,\

stderr=subprocess.PIPE,\

stdout=subprocess.PIPE)

err=res.stderr.read()iferr:

ret=errelse:

ret=res.stdout.read()

data_length=len(ret)

conn.send(str(data_length).encode('utf-8'))

data=conn.recv(1024).decode('utf-8')if data == 'recv_ready':

conn.sendall(ret)

conn.close()

View Code

importsocket,time

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

res=s.connect_ex(('127.0.0.1',8080))whileTrue:

msg=input('>>:').strip()if len(msg) == 0:continue

if msg == 'quit':breaks.send(msg.encode('utf-8'))

length=int(s.recv(1024).decode('utf-8'))

s.send('recv_ready'.encode('utf-8'))

send_size=0

recv_size=0

data=b''

while recv_size

data+=s.recv(1024)

recv_size+=len(data)print(data.decode('utf-8'))

View Code

这种方式只是一种解决问题的方法,实际上并不会这样做,程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗。这里用来帮助我们更好的理解粘包的问题。

在这个基础上,我们可以考虑将自己的长度等信息,写到报头头部,这样接收端拆开报头就能知道长度,就不会发生粘包的现象了。

我们先要认识一下struct模块。

该模块可以把一个类型,如数字,转成固定长度的bytes。

from socket import *

importsubprocessimportstruct

ss=socket(AF_INET,SOCK_STREAM)

ss.bind(('127.0.0.1',8082))

ss.listen(5)print('starting...')whileTrue:

conn,addr=ss.accept()print('-------->',conn,addr)whileTrue:try:

cmd=conn.recv(1024)

res= subprocess.Popen(cmd.decode('utf-8'), shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

stdout=res.stdout.read()

stderr=res.stderr.read()#先发报头(转成固定长度的bytes类型)

header = struct.pack('i',len(stdout)+len(stderr))

conn.send(header)#再发送命令的结果

conn.send(stdout)

conn.send(stderr)exceptException:breakconn.close()

ss.close()

客户端

from socket import *

importstruct

cs=socket(AF_INET,SOCK_STREAM)

cs.connect(('127.0.0.1',8082))whileTrue:

cmd=input('>>:').strip()if not cmd:continuecs.send(cmd.encode('utf-8'))#先收报头

header_struct=cs.recv(4)

unpack_res= struct.unpack('i', header_struct)

total_size=unpack_res[0]#再收数据

recv_size=0 #10241=10240+1

total_data=b''

while recv_size

recv_data=cs.recv(1024)

recv_size+=len(recv_data)

total_data+=recv_dataprint(total_data.decode('gbk'))

cs.close()

服务端

struct的i模式:

struct.pack('i',11111111)#struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #这个是范围

struct.pack用于将Python的值根据格式符,转换为字符串(因为Python中没有字节(Byte)类型)。它的函数原型为:struct.unpack(fmt, string)。

struct.unpack做的工作刚好与struct.pack相反,用于将字节流转换成python数据类型。它的函数原型为:struct.unpack(fmt, string),该函数返回一个元组。

importjson,struct#假设通过客户端上传1T:1073741824000的文件a.txt

#为避免粘包,必须自定制报头

header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T数据,文件路径和md5值

#为了该报头能传送,需要序列化并且转为bytes

head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并转成bytes,用于传输

#为了让客户端知道报头的长度,用struck将报头长度这个数字转成固定长度:4个字节

head_len_bytes=struct.pack('i',len(head_bytes)) #这4个字节里只包含了一个数字,该数字是报头的长度

#客户端开始发送

conn.send(head_len_bytes) #先发报头的长度,4个bytes

conn.send(head_bytes) #再发报头的字节格式

conn.sendall(文件内容) #然后发真实内容的字节格式

#服务端开始接收

head_len_bytes=s.recv(4) #先收报头4个bytes,得到报头长度的字节格式

x=struct.unpack('i',head_len_bytes)[0] #提取报头的长度

head_bytes=s.recv(x) #按照报头长度x,收取报头的bytes格式

header=json.loads(json.dumps(header)) #提取报头

#最后根据报头的内容提取真实的数据,比如

real_data_len=s.recv(header['file_size'])

s.recv(real_data_len)

View Code

使用自定制报头的方式来解决粘包问题。

importsocket,struct,jsonimportsubprocess

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

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

phone.bind(('127.0.0.1',8080))

phone.listen(5)whileTrue:

conn,addr=phone.accept()whileTrue:

cmd=conn.recv(1024)if not cmd:break

print('cmd: %s' %cmd)

res=subprocess.Popen(cmd.decode('utf-8'),

shell=True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

err=res.stderr.read()print(err)iferr:

back_msg=errelse:

back_msg=res.stdout.read()

conn.send(struct.pack('i',len(back_msg))) #先发back_msg的长度

conn.sendall(back_msg) #在发真实的内容

conn.close()

服务端自定制报头

importsocket,time,struct

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

res=s.connect_ex(('127.0.0.1',8080))whileTrue:

msg=input('>>:').strip()if len(msg) == 0:continue

if msg == 'quit':breaks.send(msg.encode('utf-8'))

l=s.recv(4)

x=struct.unpack('i',l)[0]print(type(x),x)#print(struct.unpack('I',l))

r_s=0

data=b''

while r_s

r_d=s.recv(1024)

data+=r_d

r_s+=len(r_d)#print(data.decode('utf-8'))

print(data.decode('gbk')) #windows默认gbk编码

客户端自定制报头

当然我们的报头可以添加更多信息。

我们把报头做成字典,字典里包含将要发送的真实数据的详细信息,然后json序列化,然后用struck将序列化后的数据长度打包成4个字节(4个自己足够用了)

发送时先发报头长度,再编码报头内容然后发送,最后发真实内容。

接收时先将报头长度,用struct取出来,根据取出的长度收取报头内容,然后解码,反序列化,从反序列化的结果中取出待取数据的详细信息,然后去取真实的数据内容。

from socket import *

importsubprocessimportstructimportjson

ss=socket(AF_INET,SOCK_STREAM)

ss.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)

ss.bind(('127.0.0.1',8082))

ss.listen(5)print('starting...')while True : #链接循环

conn,addr = ss.accept() #链接,客户的的ip和端口组成的元组

print('-------->',conn,addr)#收,发消息

while True :#通信循环

try:

cmd= conn.recv(1024)

res= subprocess.Popen(cmd.decode('utf-8'), shell =True,

stdout=subprocess.PIPE,

stderr=subprocess.PIPE)

stdout=res.stdout.read()

stderr=res.stderr.read()#制作报头

h_dic ={'total_size': len(stdout) +len(stderr),'filename': None,'md5': None}

h_json=json.dumps(h_dic)

h_bytes= h_json.encode('utf-8')#发送阶段

#先发报头长度

conn.send(struct.pack('i',len(h_bytes)))#再发报头

conn.send(h_bytes)#最后发送命令的结果

conn.send(stdout)

conn.send(stderr)exceptException :breakconn.close()

ss.close()

json序列化报头server

from socket import *

importstructimportjson

cs= socket(AF_INET,SOCK_STREAM) #买手机

cs.connect(('127.0.0.1',8082)) #绑定手机卡

#发,收消息

whileTrue :

cmd= input('>>:').strip()if not cmd : continuecs.send(cmd.encode('utf-8'))#先收报头的长度

h_len = struct.unpack('i',cs.recv(4))[0]#再收报头

h_bytes =cs.recv(h_len)

h_json= h_bytes.decode('utf-8')

h_dic=json.loads(h_json)

total_size= h_dic['total_size']#最后收数据

recv_size =0

total_data= b''

while recv_size

recv_data= cs.recv(1024)

recv_size+=len(recv_data)

total_data+=recv_dataprint(total_data.decode('gbk'))

cs.close()

client

python网络编程基础知识_python网络编程基础相关推荐

  1. python3语法基础知识_Python语法笔记 - 基础知识

    本文为博主原创文章,请遵守文章最后的版权申明. 有很多程序员在学习一门技术之前,都会有一番思想斗争.究竟要不要花时间去学?学了有什么意义?我大Java包办一切,何必要去学那些"旁门左道&qu ...

  2. python网络爬虫基础知识_Python网络爬虫基础知识

    一.网络爬虫 网络爬虫又被称为网络蜘蛛,我们可以把互联网想象成一个蜘蛛网,每一个网站都是一个节点,我们可以使用一只蜘蛛去各个网页抓取我们想要 的资源.举一个最简单的例子,你在百度和谷歌中输入'Pyth ...

  3. python第三项基础知识_Python学习心得——基础知识(三)

    一.常见的Python种类 1.Cpython 使用C语言实现,Python的官方版本,CPython实现会将源文件(py文件)转换成字节码文件(pyc文件),然后运行在Python虚拟机上.我们目前 ...

  4. 线程基础知识——Windows核心编程学习手札系列之六

    线程基础知识 --Windows核心编程学习手札系列之六 线程与进程一样由两部分构成:一是线程的内核对象,操作系统用它来对线程实施管理,也是系统用来存放线程统计信息的地方:二是线程堆栈,用于维护线程在 ...

  5. 清华计算机文化基础网站,数据库基础知识清华大学计算机文化基础

    <数据库基础知识清华大学计算机文化基础>由会员分享,可在线阅读,更多相关<数据库基础知识清华大学计算机文化基础(32页珍藏版)>请在人人文库网上搜索. 1.第三部分数据库基础( ...

  6. 计算机信息处理的基础知识,计算机和信息处理基础知识.ppt

    第一章 计算机与信息处理基础知识 1.1 计算机概述 1.2 电子计算机的发展 1.3 计算机系统的组成 堪庭锨婆铝胰饼箱娘窜禁磁括痢若互腆怖筏溺橡般颂骆桑猴溅躺怖摄滓现计算机与信息处理基础知识计算机 ...

  7. 计算机基础知识精品课程,计算机基础精品课程网站

    计算机基础精品课程网站 2008年7月,本课程正式确定为学校首批精品课程建设项目,课程研究团队正式形成,在学校的支持下,大家群策群力,教学改革硕果累累.正式出版了<大学计算机基础>教材,建 ...

  8. 硬件工程师入门基础知识(一)基础元器件认识(二)

    硬件工程师入门基础知识 (一)基础元器件认识(二) 1.二极管 2.三极管 3.MOS管 4.IGBT 5.晶振 tips:学习资料和数据来自<硬件工程师炼成之路>.百度百科.网上资料. ...

  9. 硬件工程师入门基础知识(一)基础元器件认识(一)

    硬件工程师入门基础知识 (一)基础元器件认识(一) 今天水一篇hhh.介绍点基础但是实用的东西. tips:学习资料和数据来自<硬件工程师炼成之路>.百度百科.网上资料. 1.贴片电阻 2 ...

最新文章

  1. map(&:name)在Ruby中是什么意思?
  2. VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNTION(翻译)
  3. ‘shared_ptr‘ is not a member of ‘std’
  4. 计算机一级b考试理论知识,全国计算机等级考试一级b知识点
  5. beta冲刺总结那周余嘉熊掌将得队
  6. java锁原理_Java锁原理学习
  7. android pdf重排软件,PDF拆分重排(paper for kindle)
  8. 导出域控中不活动的计算机_满满干货丨小薇和你聊聊计算机二级的那些事儿
  9. ubuntu服务器长时间不连接显示器后,连上显示器没反应
  10. 【Cloud Foundry 应用开发大赛】“相助”专业问答系统
  11. wps的流程图怎么导出_WPS如何绘制流程图? WPS绘制流程图的详细教程
  12. 初探TVM--TVM优化resnet50
  13. Hibernate 学习的书-夏昕(2)
  14. [树形dp]P3174 [HAOI2009]毛毛虫 题解
  15. 数学建模——评价模型之层次分析法
  16. heartbeat和keepalive
  17. Mysql(下载、安装、环境配置详细图文)
  18. 资源|最新WEB前端开发全套视频教程
  19. 北大软微一年ABCD
  20. android实时视频网络传输方案总结(一共有五套)

热门文章

  1. 机器学习基础(十四)—— 统计计数、majority count 与其数学记号
  2. 矩阵等式 matrix identity(numpy仿真)
  3. fodora lianjie mysql_Fedora 16 下安装MySql 服务器及linux c 连接MySql
  4. qt中实现息屏开平mousepress_Qt元对象(Meta-Object)系统与反射
  5. c++删除数组中重复元素_在VBA中如何使用动态数组,以及利用动态数组去除重复值的方法...
  6. python 字符串加密 唯一数字_python实现字符串加密 生成唯一固定长度字符串
  7. excel模糊匹配两列文字_如何使用Power Pivot进行模糊匹配
  8. python必备基础代码-python基础知识和练习代码
  9. 0基础学python要多久-零基础学习python,要多久才可以学好并且找到工作?
  10. python读音检测-python – 一个音符的录音音频会产生多个发音时间