学习本篇文章的前提,你需要了解网络技术基础,请参阅我的另一个分类的博文:网络互联技术(4)——计算机网络常识、原理剖析

网络通信要素

1.IP地址:

  • 用来标识网络上一台独立的终端(PC或者主机)
  • ip地址 = 网络地址+主机地址(网络号:用于主机所在的网络/网段;主机号:用于识别该网络中的主机)
  • 特殊的ip地址:127.0.0.1(本地环回地址),可用于检测本机网卡是否正常

比如我这台电脑的ip:

检测网卡正常与否就可以ping 环回地址:

如果ping通,本地网卡就没问题

2.端口号:

用于标识进程的逻辑地址,不同的进程都有不同的端口标识

端口:要将数据发送到对方指定的应用程序上,为了标识这些应用程序,所以给这些网络应用程序都用数字进行标识;为了方便称呼这些数字,将这些数字称为端口,且是一个逻辑的端口

查看本机进程的端口

(我这有点不对称,知道怎么回事就行)

查看某个端口占用情况:

查看某端口占用的所有进程:

关闭进程:XXX.exe:taskkill /im XXX.exe /f 

这里就不演示了,因为我的这些进程都是系统进程,如果停掉一个系统崩了又得重启

3.协议:通讯的规则,一般有TCP,UDP,SMTP等等的,类似通用的语言

UDP:用户数据协议

  • 面向无连接:传输数据之前源端和目的端不需要建立连接
  • 每个数据包的大小都限制在64k(8个字符)以内
  • 面向报文的不可靠协议,发送出去不一定会被目的端接收到
  • 传输速度快,效率高

生活中,邮件,在线聊天(QQ,微信等),视频会议等就是用的UDP协议

TCP:传输控制协议

  • 面向连接:传输数据之前需要建立连接
  • 在连接过程中进行大量数据传输
  • 通过三次握手的安全可靠协议建立连接
  • 传输速度慢,效率低

三次握手协议:

步骤1:建立连接,发送syn包(syn=j)到目的端,通知目的端要发报文了,并进入SYN_SEND状态,等待目的端响应,“我可以发报文吗?”

步骤2:收到syn包,必须确认源端的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,回复发送源,表示收到通知,此时目的端进入SYN_RECV状态,“知道了,可以,发报文吧”

步骤3:源端收到目的端的SYN+ACK包,确认目的端已收到确认通知,并向目的端发送确认包ACK(ack=k+1),此包发送完毕,源端和目的端进入ESTABLISHED状态,完成三次握手,“我知道你知道我准备给你发报文了,现在准备发了”

通常情况,源端被叫做客户端,目的端被叫做服务端

注意:

  • 理想状态下,TCP连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去
  • 握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据
  • 其中,步骤1里的SYN(synchronous)就是TCP/IP建立连接时使用的握手信号
  • 断开连接时服务器和客户端均可以主动发起断开TCP连接的请求,断开过程需要经过“四次握手”

socket

1.什么是socket

英文翻译过来的意思就是插座,灯座的意思。在IT里,称为套接字。就好比电视要正常工作(能看电视),需要连接插座通电才可以看电视,这个插座就是一个媒介,通过它才能使电视连入电路,电视才能正常工作。

2.与tcp/ip协议的关系

socket就是对tcp/ip协议的封装和应用,只有通过Socket,我们才能使用TCP/IP协议。

Socket好比是一个发动机,提供了网络通信的能力

实际上,Socket跟TCP/IP协议没有必然的联系

3.socket建立网络连接:

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。
套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认
  1、服务器监听:服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,等待客户端的连接请求。
  2、客户端请求:指客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。
  为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。
  3、连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求

4.socket方法/属性

python中,已经内置有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

可靠的连续数据包服务

例:

服务端socket函数: 

s.bind(address):将套接字绑定到地址, 在AF_INET下,以元组(host,port)的形式表示地址.

s.listen(backlog):开始监听TCP传入连接。backlog指定在拒绝连接之前,操作系统可以挂起的最大连接数量。该值至少为1,大部分应用程序设为5就可以了。

s.accept():接受TCP连接并返回(conn,address),其中conn是新的套接字对象,可以用来接收和发送数据。address是连接客户端的地址

客户端socket函数:

s.connect(address):连接到address处的套接字。一般address的格式为元组(hostname,port),如果连接出错,返回socket.error错误。

s.connect_ex(adddress):功能与connect(address)相同,但是成功返回0,失败返回errno的值。

公共socket函数:

s.recv(bufsize[,flag]):接受TCP套接字的数据。数据以字符串形式返回,bufsize指定要接收的最大数据量。flag提供有关消息的其他信息,通常可以忽略。

s.send(string[,flag]):发送TCP数据。将string中的数据发送到连接的套接字。返回值是要发送的字节数量,该数量可能小于string的字节大小。

s.sendall(string[,flag]):完整发送TCP数据。将string中的数据发送到连接的套接字,但在返回之前会尝试发送所有数据。成功返回None,失败则抛出异常。

s.recvfrom(bufsize[.flag]):接受UDP套接字的数据。与recv()类似,但返回值是(data,address)。其中data是包含接收数据的字符串,address是发送数据的套接字地址。

s.sendto(string[,flag],address):发送UDP数据。将数据发送到套接字,address是形式为(ipaddr,port)的元组,指定远程地址。返回值是发送的字节数。

s.close():关闭套接字。

s.getpeername():返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。

s.getsockname():返回套接字自己的地址。通常是一个元组(ipaddr,port)

s.setsockopt(level,optname,value):设置给定套接字选项的值。

s.getsockopt(level,optname[.buflen]):返回套接字选项的值。

s.settimeout(timeout):设置套接字操作的超时期,timeout是一个浮点数,单位是秒。值为None表示没有超时期。一般,超时期应该在刚创建套接字时设置,因为它们可能用于连接的操作(如connect())

s.gettimeout():返回当前超时期的值,单位是秒,如果没有设置超时期,则返回None。

s.fileno():返回套接字的文件描述符。

s.setblocking(flag):如果flag为0,则将套接字设为非阻塞模式,否则将套接字设为阻塞模式(默认值)。非阻塞模式下,如果调用recv()没有发现任何数据,或send()调用无法立即发送数据,那么将引起socket.error异常。

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

然后test方法/属性中,最常用的就是:accept,bind,close,connect,recv,send,sendall,这里暂且不细说,下面的简单实例会提到

5.简单实例:

1)服务端和客户端简单单向通信:

服务端server.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva
import socket

s = socket.socket()             #创建socket对象
address = ('127.0.0.1',8800)    #设定ip和端口
s.bind(address)                 #将socket对象绑定到设定的ip和端口
s.listen(3)                     #设置监听量为3,表示允许同时3个客户端连接
print('waiting......')
conn,addr = s.accept()          #接受TCP连接并返回conn,addr,其中conn是新的套接字对象(客户端的),address是连接客户端的地址
data = conn.recv(1024)          #设定一次接收客户端socket传来最大的数据量
print(data)

客户端client.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import socket

c = socket.socket()                 #创建socket对象
address = ('127.0.0.1',8800)        #设定ip和端口,必须和服务端的socket对象的ip和端口一致
c.connect(address)                  #连接设定的ip和端口
data = input('>>>:')
c.send(bytes(data,encoding='utf8')) #传输数据,并以bytes格式传

注意:

1.socket传输必须以字节形式,传输不然报错。python2中不存在这个问题,所以可以直接传byte和str,也不会报上面的错误,因为python2的编码和python3有很大区别,这个前面已经说过很多次了,有机会再细说

 2.先运行服务端,再运行客户端去连接,不然客户端找不到目的ip和端口而报错:

正确运行方式的运行结果:

client:

server:

如果想让server端显示正常的结果,把得到的数据转为str就行,其他不变,但必须和你程序的默认编码一致

3.recv和send方法处都会阻塞住,一个是等待接收,一个发送后等待确认接收

4.socket必须是一收一发,有发必有收,有收必有发

5.服务端和客户端谁先发谁后发都可以的,但前提必须已经建立连接

6.服务端和客户端之间的通信必须使用的是同一socket对象,不然无法通信

server端:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva
import socket

s = socket.socket()             #创建socket对象
address = ('127.0.0.1',8800)    #设定ip和端口
s.bind(address)                 #将socket对象绑定到设定的ip和端口
s.listen(3)                     #设置监听量为3,表示允许同时3个客户端连接
print('waiting......')
conn,addr = s.accept()          #接受TCP连接并返回conn,addr,其中conn是新的套接字对象(客户端的),address是连接客户端的地址

data = conn.recv(1024)          #设定一次接收客户端socket传来最大的数据量
print(str(data,encoding='utf8'))

inp = input('>>>:')
conn.send(bytes(inp,encoding='utf8')) #这里一定要用conn对象,不能用s对象

  

client端:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import socket

c = socket.socket()                 #创建socket对象
address = ('127.0.0.1',8800)        #设定ip和端口,必须和服务端的socket对象的ip和端口一致
c.connect(address)                  #连接设定的ip和端口

data = input('>>>:')
c.send(bytes(data,encoding='utf8')) #传输数据,并以bytes格式传

test = c.recv(1024)
print(str(test,encoding='utf8'))

 

运行结果:

当然这里客户端和服务端都只有收发两次 ,那么就可以写一个循环语句,使对话长时间存在,就成了一个简单的双向对话了

2)做一个简单的聊天室

server:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva
import socket

s = socket.socket()             #创建socket对象
address = ('127.0.0.1',8800)    #设定ip和端口
s.bind(address)                 #将socket对象绑定到设定的ip和端口
s.listen(3)                     #设置监听量为3,表示允许同时3个客户端连接
print('waiting......')
conn,addr = s.accept()          #接受TCP连接并返回conn,addr,其中conn是新的套接字对象(客户端的),address是连接客户端的地址

while True:
    data = conn.recv(1024)          #设定一次接收客户端socket传来最大的数据量

    print(str(data,'utf8'))
    #当客户端输入exit时,关闭客户端连接,重新获取新的连接
    if not data:
        conn.close()
        conn,addr = s.accept()
        continue

    inp = input('>>>:')
    conn.send(bytes(inp,'utf8'))

s.close()

 

client.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva
import socket

c = socket.socket()                 #创建socket对象
address = ('127.0.0.1',8800)        #设定ip和端口,必须和服务端的socket对象的ip和端口一致
c.connect(address)                  #连接设定的ip和端口

while True:
    data = input('>>>:')
    if data == 'exit':break
    c.send(bytes(data,'utf8')) #传输数据,并以bytes格式传
    recvdata = c.recv(1024)
    print(str(recvdata,'utf8'))

c.close()

 

运行效果:

 注意,不能发空信息,不然客户端和服务端两边都会阻塞住:

3)做一个远程执行命令并反馈结果的程序(ssh)

cmd_server.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva
import socket,sys,subprocess

s = socket.socket()
address = ('127.0.0.1',8888)
s.bind(address)
s.listen(3)
conn,addr = s.accept()
while True:
    try:
        data = conn.recv(1024)
    except Exception as re:
        print(re)
        break
    else:
    # 拿到客户端传来的命令利用sub模块执行
        obj = subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
        conn.send(obj.stdout.read())

conn.close()

cmd_client.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import socket

c = socket.socket()
address = ('127.0.0.1',8888)
c.connect(address)
while True:
    inp = input('>>>:')
    if inp == 'exit':break
    c.send(bytes(inp,'utf8'))
    data = c.recv(1024)
    print(str(data,'gbk'))

c.close()

 

结果测试:

插一句,不同操作系统会有不同的显示结果,同样的命令,有的会报编码错误,有的不会,比如我的电脑输入ipconfig 会报这个:

解决办法是,在str后面加参数属性:errors='ignore'

完美对不对?

但是,细心的朋友发现了,我使用net -nao命令好像得到的结果不大对,不全啊:

这里明显没显示完啊,再次使用看看,结果还是这样,使用dir呢:

卧槽,这啥情况?我使用的dir,怎么是刚才netstat -nao的结果?

注意了,这里这个现象不是报错,是一种现象,因为数据太多,没有一次传完,第二次使用命令又接着传

因为这一次的没传完,导致后面的命令都开始错位:

这个怎么解决呢,既然知道是没有传完的,那么用sendall或者改接受recv()里的值,改得很大不就行了?亲测改sendall也没法,改recv值只是暂时的,如果以后的开发中,很大的数据,好几个G等等的,难道还要这么改吗?也就是你每次传一个数据都先得知道它有多大,再根据大小改recv值吗?当然是不行的啊,那有没有一劳永逸且适用的方法呢?有的啊,还是事先对数据大小进行判断,不过不用知道它有多大,使用一个while循环判断就OK

cmd_server.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import socket,subprocess

s = socket.socket()
address = ('127.0.0.1',8888)
s.bind(address)
s.listen(3)
conn,addr = s.accept()
while True:
    try:
        data = conn.recv(1024)
        # 拿到客户端传来的命令利用sub模块执行
        obj = subprocess.Popen(str(data,'utf8'),shell=True,stdout=subprocess.PIPE)
        result = obj.stdout.read()
        result_len = str(len(result)) #长度

        conn.sendall(bytes(result_len,'utf8'))
        conn.sendall(result)
    except Exception as re:
        print(re)
        break
conn.close()

  

cmd_client.py:

#!usr/bin/env python
#-*- coding:utf-8 -*-

# author:yangva

import socket,time

c = socket.socket()
address = ('127.0.0.1',8888)
c.connect(address)
while True:
    inp = input('>>>:')
    if inp == 'exit':break
    c.send(bytes(inp,'utf8'))

    data_len = int(str(c.recv(1024),'utf8')) #数据长度
    print('操作结果长度为:%s'%data_len)
    time.sleep(1)
    data = bytes()
    while  len(data) != data_len:
        data +=  c.recv(1024)
    print(str(data,encoding='gbk',errors='ignore'))
c.close()

  

结果测试:

服务端没有数据输出,所以略过。稳得一笔对不对?这样我们就做出来了一个简单的远程执行程序

这里还有个问题,服务端发了两次,一次是告诉客户数据的长度,第二次才是真实的数据,那么有时候会出现错误,就好比货车拉货,如果放上去的货太少,货车司机就会停留一会,等货车装满了再发车,那么这两次的长度和真数据就有可能会被socket等待得放在一起一并发过去,然后客户端接收到再int就会报错了,这种现象叫粘包,解决办法是在send长度和send真实数据之间用time.sleep()一会儿就可以解决了,socket有一个很短暂的停留,这个情况也是时不时发生的,只要time.sleep时间足以,就可以避免这个情况。

当然还有个绝佳的办法:

在中间加一个接收命令,recv会阻塞的,客户端那边随便发送一个数据就行。有朋友问了,为何不加一个send,send也会阻塞住啊,干嘛要加一个recv啊?咳咳,你加一个send,那不是有三个send了吗?又会出现粘包现象了,理解了吧?send确实会阻塞,但是两个send紧挨着进行就会出现粘包现象,recv则不会,记住了

好的,关于socket就基本介绍完了,涉及到socket的开发也基本就这些东西了

洗礼灵魂,修炼python(84)-- 知识拾遗篇 —— 网络编程之socket的更多相关文章

  1. 【python之路35】网络编程之socket相关

    Socket socket通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,应用程序通常通过"套接字"向网络发出请求或者应答网络请求. sock ...

  2. Python自动化运维之15、网络编程之socket、socketserver、select、twisted

    一.TCP/IP相关知识 TCP/UDP提供进程地址,两个协议互不干扰的独自的协议       TCP :Transmission Control Protocol 传输控制协议,面向连接的协议,通信 ...

  3. Python网络编程之socket应用

    1 引言 本篇主要对Python下网络编程中用到的socket模块进行初步总结.首先从网络基础理论出发,介绍了TCP协议和UDP协议:然后总结了socket中的常用函数:最后通过实际代码展示基本函数的 ...

  4. Python网络编程之socket编程

    什么是Socket? Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面 ...

  5. python 网络编程之socket开发和socketserver模块

    一 客户端/服务器架构 1.硬件C/S架构(打印机) 2.软件C/S架构 互联网中处处是C/S架构 如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种) 腾讯作为服务端为你提供视频 ...

  6. python(十三):网络编程之socket与socketserver

    socket是操作系统中I/O系统延伸部分,支持TCP和UDP等网络通信协议,它使计算机之间(或其本身)的进程通信称为可能.socket中的socket()函数.recv()函数和send()函数,相 ...

  7. python网络编程之socket

    *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: 0 !important; } /* ...

  8. Python网络编程之Socket的简单实现

    一.引入 关于Python的网络编程,最基础莫过于socket了. socket,又称“套接字”,网络上的两个程序如果想要实现双向的数据通信,需要建立连接,这个连接的一端就称为一个socket. py ...

  9. Python 之网络编程之socket(2)黏包现象和socketserver并发

    一:黏包 ###tcp协议在发送数据时,会出现黏包现象.     (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,     缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区 ...

随机推荐

  1. [NewLife.XCode]数据层缓存(网站性能翻10倍)

    NewLife.XCode是一个有10多年历史的开源数据中间件,支持nfx/netcore,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和 ...

  2. Docker中运行nginx并挂载本地目录到镜像中

    1.1 从hup上pull镜像1.2 创建将要挂载的目录1.3 先要有配置文件才能启动容器1.3.1 vim /data/nginx/conf/nginx.conf1.3.2 vim /data/ng ...

  3. eclipse制作exe文件

    1.右击你的项目,选择Export: 2.选择Java目录下的JAR file: 3.设置导出jar文件的路径,我这里选择的是桌面,点击Next: 4.这一步默认,不用改动,直接Next: 5.设置项 ...

  4. 面试题·HashMap和Hashtable的区别(转载再整理)

    原文链接: Javarevisited 翻译: ImportNew.com- 唐小娟 译文链接: http://www.importnew.com/7010.html HashMap和Hashtabl ...

  5. MySQL中间件之ProxySQL(15):ProxySQL代理MySQL组复制

    返回ProxySQL系列文章:http://www.cnblogs.com/f-ck-need-u/p/7586194.html 1.ProxySQL+组复制前言 在以前的ProxySQL版本中,要支 ...

  6. Docker镜像管理基础与基于容器的镜像制作示例

    一.Docker镜像 Docker镜像是启动Docker容器的一个非常重要的组件.Docker各组件之间的关系如图: Docker镜像含有启动容器所需要的文件系统及其内容,因此Docker镜像用于创建 ...

  7. 以 SPI 方式获取 SD 卡容量(V2.0)

    下面是 SD 卡 V2.0 协议的 CSD 寄存器内容,来自官方手册: 单片机如何确定当前的 SD 卡遵循 V2.0 协议 CSD 寄存器为 128 个位,即 16 个字节.通过检测 CSD 寄存器的 ...

  8. yum一键安装企业级lamp服务环境-技术流ken

    1.简介 LAMP 是Linux Apache MySQL PHP的简写,其实就是把Apache, MySQL以及PHP安装在Linux系统上,组成一个环境来运行动态的脚本文件. 2.系统环境 cen ...

  9. vmware-vcsa6.5 基本管理

    这章介绍的是创建数据中心集群等操作 一.创建数据中心 创建数据中心 添加主机 创建集群 #配置ntp,上一章已经配置 #许可证,上一章已经添加,这章就不介绍了 统一存储,方便后面的识别和管理 1.创建 ...

  10. WPF DevExpress ChartControl用法

    WPF常用的第三方控件集,DevExpress中ChartControl的使用 下面介绍如何生成Chart界面: <dxc:ChartControl AnimationMode="On ...