Linux-socket 模型理解

Linux-socket 模型理解

环境:Win10 
  python

一、socket

  • 3.7

  一般来说socket有一个别名也叫做套接字。

Socket工作原理和基本概念

  socket起源于Unix,都可以用“打 开open –> 读写write/read –>
关闭close”模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写
IO、打开、关闭)。

        Socket的中文翻译是套接字,它是TCP/IP网络环境下应用程序与底层通信驱动程序之间运行的开发接口,它可以将应用程序与具体的TCP/IP隔离开来,使得应用程序不需要了解TCP/IP的具体细节,就能够实现数据传输。

  说白了Socket是应用层与TCP/IP
协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后
面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议,而不需要让用户自己去定义什么时候需要指定哪个协议哪个函数。

         在网络应用程序中,Socket通信是基于客户端/服务器结构。客户端是发送数据的一方。服务器时刻准备接受来自客户端的数据,对做出响应.

   
其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。

实现基于tcp网络通信与现实中打电话很像:

1.1套接字描述符

1)     
客户端(相当于打电话的一方),需要了解服务器的ip地址,如果该服务器有多个网络应用程序,单单ip地址则不够,这时候socket通信借用tcp/ip中端口的概念,不同应用程序使用不同端口通信(这就很类似电话分机)。

  其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE
*结构的表示就是stdin、stdout、stderr

2)     
服务器应用程序必须早于客户端启动,并在指定ip地址和端口上执行监听,端口被占用,服务器则无法正常启动。(服务器处于监听状态就类似电话接通好电话线,等待被拨打的状态)

  套接字API最初是作为UNIX操作系统的一部分而开发的,所以套接字API
与系统的其他I/O设备集成在一起。特别是,当应用程序要为因特网通信而创建一个套接字(socket)时,操作系统就返回一个小整数作为描述符
(descriptor)来标识这个套接字。然后,应用程序以该描述符作为传递参数,通过调用函数来完成某种操作(例如通过网络传送数据或接收输入的数
据)。

3)     
客户端在申请发送数据时,服务器端应用程序必须有足够的时间响应才能进行正常通信(电话响,却无人接听)。通常情况下,服务器的应用程序都需要具备同时处理多个客户端请求的能力,应用程序设计不合理或访问量过高都会导致响应超时。

  在许多操作系统中,套接字描述符和其他I/O描述符是集成在一起的,所以应用程序可以对文件进行套接字I/O或I/O读/写操作。

4)     
使用Socket协议进行通信的双方必须使用相同的通信协议,Socket支持的底层通信协议包括tcp和udp两种,通信过程中,双方还必须采用相同的字符编码,按照约定的方式进行通信(打电话时,双方必须语言相同,才能进行信息交流)

  当应用程序要创建一个套接字时,操作系统就返回一个小整数作为描述符,应用程
序则使用这个描述符来引用该套接字需要I/O请求的应用程序请求操作系统打开一个文件。操作系统就创建一个文件描述符提供给应用程序访问文件。从应用程序
的角度看,文件描述符是一个整数,应用程序可以用它来读写文件。下图显示,操作系统如何把文件描述符实现为一个指针数组,这些指针指向内部数据结构。

5)     
通信时,物理网络必须保持通畅,否则通信将会中断(电话线有效,且连接正常)。

澳门新葡亰游戏网址 1

6)     
通信结束之前,客户端和服务器端都可以中断连接(任何一方都可以挂电话)。

 对于每个程序系统都有一张单独的表。精确地讲,系统为每个运行的进程维护一张单
独的文件描述符表。当进程打开一个文件时,系统把一个指向此文件内部数据结构的指针写入文件描述符表,并把该表的索引值返回给调用者 。应用程序只需记住
这个描述符,并在以后操作该文件时使用它。操作系统把该描述符作为索引访问进程描述符表,通过指针找到保存该文件所有的信息的数据结构。

TCP是基于连接的通信协议,即先建立稳定连接后,再数据传输

针对套接字的系统数据结构:

如果Socket通信基于UDP,则数据传输前不需要连接,类似发短信或发电报,即使对方不在线,也可以发送数据,发送的数据在指定时间没有得到对方响应,则视为操作超时,可以选择超时后重新发送数据。

 
 1)、套接字API里有个函数socket,它就是用来创建一个套接字。套接字设计的总体思路是,单个系统调用就可以创建任何套接字,因为套接字是相当
笼统的。一旦套接字创建后,应用程序还需要调用其他函数来指定具体细节。例如调用socket将创建一个新的描述符条目:

相关函数:

澳门新葡亰游戏网址 2

1.socket()函数

 
 2)、虽然套接字的内部数据结构包含很多字段,但是系统创建套接字后,大多数字字段没有填写。应用程序创建套接字后在该套接字可以使用之前,必须调用其他的过程来填充这些字段。

        socket()函数用于创建与指定的服务提供者绑定套接字,函数原型如下:

二、基本的socket接口函数

        socket=socket.socket(familly,type)

澳门新葡亰游戏网址 3

参数说明如下:

 

        familly,指定协议的地址家族,可为AF_INET或AF_UNIX。AF_INET家族包括Internet地址,AF_UNIX家族用于同一台机器上的进程间通信。

  服务器端先初始化/创建Socket,然后与端口绑定/绑定地址
(bind),对端口进行监听(listen),调用accept阻塞/等待连续,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连
接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送
给客户端,客户端读取数据,最后关闭连接,一次交互结束。

type,指定套接字的类型。

2.1socket函数

套接字类型

说    明

SOCK_STREAM

提供顺序、可靠、双向和面向连接的字节流数据传输机制,使用TCP

SOCK_DGRAM

支持无连接的数据报,使用UDP

SOCK_RAW

原始套接字,可以用于接收本机网卡上的数据帧或者数据包

函数原型

2.bind()函数

int socket(int protofamily, int type, int protocol);

bind()函数可以将本地地址与一个Socket绑定在一起,函数原型如下:

 

socket.bind(
address )

 

参数address是一个双元素元组,格式是(host,port)。host代表主机,port代表端口号。

返回值:

3.listen()函数

澳门新葡亰游戏网址,  //返回sockfd     sockfd是描述符,类似于open函数。

listen()函数可以将套接字设置为监听接入连接的状态,函数原型如下:

函数功能:

listen(backlog);

  socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件
描述字,而socket()用于创建一个socket描述符(socket
descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些
读写操作。

参数backlog指定等待连接队列的最大长度。

函数参数:

4.accept()函数

  protofamily:即协议域,又称为协议族(family)。常用的协
议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、
AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号
(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。

在服务器端调用listen()函数监听接入连接后,可以调用accept()函数来等待接受连接请求。accept()的函数原型如下:

     澳门新葡亰游戏网址 4

connection, address = socket.accept()

  type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

调用accept()方法后,socket会进入waiting状态。客户请求连接时,accept()方法会建立连接并返回服务器。accept()方法返回一个含有两个元素的元组(connection,address)。第一个元素connection是新的socket对象,服务器必须通过它与客户通信;第二个元素
address是客户的Internet地址。

     澳门新葡亰游戏网址 5

5.recv()函数

  protocol:就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议

调用recv()函数可以从已连接的Socket中接收数据。recv()的函数原型如下:

  注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。

buf
= sock.recv(size)

当我们调用socket创建一个socket时,返回的socket描述字它存在
于协议族(address
family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、
listen()时系统会自动随机分配一个端口

参数sock是接收数据的socket对象,参数size指定接收数据的缓冲区的大小。recv()的函数的返回接收的数据。

2.2bind()函数

6.send()函数

函数功能:

调用send()函数可以在已连接的Socket上发送数据。send()的函数原型如下:

  bind()函数把一个地址族中的特定地址赋给socket,也可以说是绑定ip端口和socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

sock.recv(buf)

函数原型:

参数sock是在已连接的Socket上发送数据。参数buf是也要已连接的Socket上发送数据。

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

7.close()函数

函数参数:

close
()函数用于关闭一个Socket,释放其所占用的所有资源。socket()的函数原型如下:

  1.函数的三个参数分别为:sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

s.closesocket();

  2.addr:一个const struct sockaddr
*指针,指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同,

参数s表示要关闭的Socket。

  3.addrlen:对应的是地址的长度。

 connection.close()

通用函数类型:

使用socket通讯的简易服务

struct sockaddr{
  sa_family_t  sa_family;
  char         sa_data[14];
}

如ipv4对应的是:

import socket 

import sys 

# 创建 socket 对象 

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

# 获取本地主机名 

host = socket.gethostname()
port = 9999  

# 绑定端口号 s.bind((host, port)) 

# 设置最大连接数,超过后排队 

s.listen(5) 

# 等待客户端连接 

while True:
    conn, addr = s.accept() # 建立客户端连接  

  print("连接地址: %s" % str(addr))
    msg = '|------成功连接服务端!------|'  

 conn.send(msg.encode('utf-8'))

    conn.close() # 关闭连接

澳门新葡亰游戏网址 6

使用socket通讯的简易客户端

struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order 2字节*/
    struct in_addr sin_addr;   /* internet address 4字节*/
  unsigned char sin_zero[8];
};
/* Internet address. */
struct in_addr {
    uint32_t       s_addr;     /* address in network byte order */
};
import socket 

澳门新葡亰游戏网址 7

import sys

ipv6对应的是: 

 创建 socket 对象 


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

澳门新葡亰游戏网址 8

# 获取本地主机名

struct sockaddr_in6 { 
    sa_family_t     sin6_family;   /* AF_INET6 */ 
    in_port_t       sin6_port;     /* port number */ 
    uint32_t        sin6_flowinfo; /* IPv6 flow information */ 
    struct in6_addr sin6_addr;     /* IPv6 address */ 
    uint32_t        sin6_scope_id; /* Scope ID (new in 2.4) */ 
};
struct in6_addr { 
    unsigned char   s6_addr[16];   /* IPv6 address */ 
};
网站地图xml地图