网络中的进程是如何通信的?

在网络中进程之间进行通信的时候,那么每个通信的进程必须知道它要和哪个计算机上的哪个进程通信.否则通信无从谈起!在本地可以通过进程PID来唯一标识一个进程,但是在网络中这是行不通的.其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程).这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互.

什么是套接字?

套接字是作为4BDS UNIX的进程通信机制,它用于描述 IP 地址和端口,是一个用于通信连接的文件描述符.举个例子来说.套接字就好像是银行的服务窗口.然后每个服务窗口上面都有一个标号(对应套接字的 IP 地址),然后银行又有规定标号为多少的窗口提供什么服务(窗口标号就类似套接字中记录的 IP 地址,服务就类似着套接字的端口号),那么客户就可以根据这个窗口标号去请求所需要的服务.

在 socket 中是怎么做到对另一个套接字连接的

首先很明显,上面一直在强调两个进程在进行与网络通信的时候,两个进程都必须知道对方是谁(通过"ip地址+端口"),否则就无法进行通信,可以设想下你写了封信给你的朋友小明,那么你忘了写寄信地址和寄信人.那么你把这份信寄了.然后你就开始等待小明的回信,那么小明就算收到信了也不知道这封信是谁寄的和该把回复信寄给谁.那么你能收到回信么?

但是有时候你在写通信时候比如 tcp 的客户端,你发现你没有对套接字进行地址绑定也依旧可以和服务器通信,那这又是为什么?因为操作系统帮你做了这件事,操作系统在发现你没有将一个套接字绑定到对应的 ip 地址和端口号而调用 listen 和connect 的时候.那么它会自动给你分配 IP 地址和端口号.接着上面那个寄信的例子,假设你在寄信的时候是托给个朋友帮你寄.那么这个好心的朋友发现了你犯的这个粗心的错误.那么他便把在寄信人和寄信地址处填写上了你的名字和你的地址,那么小明就可以和你正常通信了.

既然这为什么服务器还要大费周章的去对一个监听端口进行地址绑定呢?我们还是用上面那个寄信的例子来说,假设你很有钱,有三套房子(即服务器对应三个 IP),你依旧没有写寄信人和寄信地址.那么你虽然托朋友给你寄,但是你这个朋友也不确定你现在住那套房子里,那么他便把这个三个地址之中的一个地址填了上去.而恰恰很不幸的是你已近很久没有住那套房子了.那么小明即使回复了你的信,你也可能收不到信了.所以服务器必须要对监听套接字进行地址进行绑定!

什么是大小端

因为计算机内存是以字节(八位)为单位来存储东西的.那么在存储大于一字节的数据时候就会存在一个问题,按什么样的顺序去存放这样的数据,是低位字节排放在内存的低地址端还是低位字节排放在内存的高地址端.然而这个往往和具体CPU架构有关,而非操作系统.那么首先我们来说明下大端模式和小端模式的区别:

    • 小端模式(Little-Endian):    就是低位字节排放在内存的低地址端,高位字节排放在内存的高地址端.(逻辑上的低低高高)
    • 大端模式(Big-Endian):      就是高位字节排放在内存的低地址端,低位字节排放在内存的高地址端.(像数据流一样填充)

这样也许不好理解那么举个例子来说.如果将一个 16 位的整数 0x1234 存放到一个短整型变量(short)中,这个短整型变量采用大端或者小端模式在内存中的存储由下表所示.

socket 中的 tcp 编程的大致流程

注意问题

  1. 大型数据的发送与接收.这里打大型数据指的是大于接收缓冲区或者发送缓冲区的数据.对于这种数据就需要多次调用 recv 或 send 等等的函数来发送或者接收.
  2. TCP 是基于流发送的.所以当间隔很短发送的数据,在服务器中可能因此一次性收到,对此要进行相关处理.
  3. 若 TCP 保持长连接的话,要进行检查连接是否存在.常见的方法是用心跳包来检查连接是否存在.
  4. 大小端问题.在网络传输数据的时候记得只要超过两字节的二进制数据就要用 htosXX 函数和 stohXX 函数来对数据进行处理,因为在终端平台的大小端问题.但是超过两字节的字符数据不需要注意终端平台大小端问题.在网络中 TCP/IP 各层协议将字节序(网络字节序)定义为Big-Endian
  5. 对于为绑定地址和端口的套接字系统会随机分配端口和绑定本机上的 IP 给套接字用.对于因特网域,如果指定IP地址为 INADDR_ANY,套接字端点可以被绑定到所有的系统网络接口.这意味着可以接受到这个系统锁安装的所有网卡的数据包,如果调用 connect 或 listen ,但没有绑定地址到一个套接字,系统会选一个地址并将其绑定到套接字.

地址结构体

IPv4:  为了使用不同格式地址能够被传入到套接字函数,地址被转换成通用的地址结构 sockaddr IPv4,在 IPv4 因特网域(AF_INET)中,套接字地址用如下结构 sockaddr_in 表示:
struct in_addr{in_addr_t s_addr;};
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
IPv6:  在 IPv6 因特网域(AF_INET6)套接字地址用如下结构 sockaddr_in6 表示:

struct in6_addr{
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};

地址查询

通过调用 gethostent 获取主机信息,当 gethostent 返回时,得到一个指向 hostent 结构体指针,该结构体可能包含一个静态的数据缓存区,每次调用 gethostlent 将会覆盖这个缓存区.返回的地址采用网络字节序.相关函数:

    • gethostent
    • sethostent
    • endhostent

相关结构体:

struct hostent{
char *h_name;
char **h_aliases;
char h_addrtype;
char h_length;
char **h_addr_list;
...
};

将协议名字和协议号采用以下函数映射

相关函数

    • getprotobyname
    • getprotobynumber
    • getprotoent
    • setprotoent
    • endprotoend

相关结构体

struct protoent{
char* p_name;
char** p_aliases;
int p_proto;

服务查询

服务是由地址的端口号部分表示的.每个服务由唯一的熟知的端口号来提供,采用函数 getservbyname 可以将一个服务名字映射到一个端口号,函数getservbyport将一个端口号映射到一个服务器名,或者采用函数 getservent顺序扫描服务数据库.相关函数:

    • getservbyname
    • getservbyport
    • getservent
    • setservent
    • endservent
相关结构体:

struct servent{
char *s_name;
char **s_aliases;
int s_port;
cjar *s_proto;
};

套接字的常用 API

  • 创建套接字:           socket()
    SOCK_RAW 套接字提供一个数据报接口用于直接访问下面的网络层(因特网域中为IP).使用这个接口时,应用程序负责构造自己的协议首部,这是因为传输协议(TCP和UDP等)被绕过了.当创建一个原始套接字时需要具有超级用户权限,用以防止恶意程序绕过内建安全机制来创造报文.
  • 对套接字进行端口和地址的绑定:  bind()
    bind一些限制
    • 在进程所运行的机器上,制定的地址必须有效,不能制定一个其他机器的地址
    • 地址必须和创建套接字时的地址族所支持的格式相匹配
    • 端口号必须不小与1024,除非该进程具有相应的特权
    • 一般只有套接字端点能够与地址绑定,尽管有些协议可以多重绑定

    假设我服务器绑定的套接字地址为 123.255.1.3 客户端只能从 123.255.1.3 地址建立请求连接请求

  • 监听套接字:           listen()
  • 连接套件子:           connect() 
    如果套接字描述符处于非阻塞模式下,那么在连接不能马上建立的时,connect 将会返回 -1,并且将 errno 设为特殊的错误码,EINPROGRESS . 应用程序可以使用 select 或 poll 来判断文件描述符何时可写,如果可写完成连接.函数 connect 还可以用于无连接的网络服务(SOCK_DGRAM).若在 SOCK_DGRAM 套接字上调用 connect,所有发送报文目的地址设字为 connect 调用中所制定的那个地址,这样每次传送报文时就不需要在提供地址,另外,仅接受来自指定地址的报文
  • 就收请求:            accept()
    函数 accept 锁返回的文件描述符是套接字描述符,该描述符连接到调用 connect 的客户端.这个新的套接字描述符和原始套接字(sockfd)具有相同的套接字类型和地址族.传给 accept 的原始套接字没有关联到这个连接,而是继续保持可用状态并接受其他连接请求.如果没有连接请求等待处理,accept会阻塞直到一个请求到来,如果sockfd处于非阻塞模式,accept会返回-1并将errno设置为 EAGAIN 或 EWOULDBLOCK
  • 数据通信:            
    • send() \ recv()
    • write() \ read()
    • recvfrom() \ sendto()
  • 关闭套接字:           close()
  • 解析主机名和地址:        gethostbyname()/gethostbyaddr()
  • 获得到接字地址getpeername 获得以连接的套接字连接着的对方地址
  • getsockname 获得套接字绑定的地址
  • 查询/设置套件子选项:         getsockopt()/setsockopt()
  • 资料

参考资料

Linux套接字编程的更多相关文章

  1. linux 套接字编程入门--Hello World

    下述代码是linux套接字编程的入门代码.分为服务端和客户端源码. 服务端代码的主要流程是绑定ip地址和端口号建立套接字,等待客户端发起访问.接受客户端请求之后,向客户端发送字符串"hell ...

  2. Linux 套接字编程中的 5 个隐患(转)

    本文转自IBM博文Linux 套接字编程中的 5 个隐患. “在异构环境中开发可靠的网络应用程序”. Socket API 是网络应用程序开发中实际应用的标准 API.尽管该 API 简单,但是开发新 ...

  3. (转载)Linux 套接字编程中的 5 个隐患

    在 4.2 BSD UNIX® 操作系统中首次引入,Sockets API 现在是任何操作系统的标准特性.事实上,很难找到一种不支持 Sockets API 的现代语言.该 API 相当简单,但新的开 ...

  4. Linux 套接字编程中的 5 个隐患

    http://www.ibm.com/developerworks/cn/linux/l-sockpit/ 在 4.2 BSD UNIX® 操作系统中首次引入,Sockets API 现在是任何操作系 ...

  5. Linux 套接字编程中要注意的细节

    隐患 1.忽略返回状态 第一个隐患很明显,但它是开发新手最容易犯的一个错误.如果您忽略函数的返回状态,当它们失败或部分成功的时候,您也许会迷失.反过来,这可能传播错误,使定位问题的源头变得困难. 捕获 ...

  6. Linux 套接字编程 - TCP连接基础

    第五章的内容,实现一个echo服务器和对应的客户端,主要收获: 0. TCP socket编程主要基本步骤 1. SIGCHLD信号含义(子进程退出时向父进程发送,提醒父进程对其状态信息进行一个获取) ...

  7. Linux 套接字编程 - select

    select 可以感知文件表述符集合中的变化,如果办fd0(即标准输入)放入select的read fd set,发现只有按回车的时候select才会返回.查了下要把终端的缓冲大小设为1,这样就能实现 ...

  8. 【转】Linux 套接字编程中的 5 个隐患

    地址:请点击这里

  9. Linux Socket 原始套接字编程

    对于linux网络编程来说,可以简单的分为标准套接字编程和原始套接字编程,标准套接字主要就是应用层数据的传输,原始套接字则是可以获得不止是应用层的其他层不同协议的数据.与标准套接字相区别的主要是要开发 ...

随机推荐

  1. Python函数解析

    对于Python的函数,我们需要记住的是: 1. 函数的默认返回值是None. 2. python是一个自上而下逐行解释并执行的语言.因此,函数的定义必须在函数被调用之前.同名的函数,后定义的会覆盖前 ...

  2. Android的init过程(二):初始化语言(init.rc)解析

    Android的init过程(一) 本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 在上一篇文章中介绍了init的初始化第一阶段,也就是处理各种属性.在本文将会详细分析i ...

  3. Entity FrameWork 延迟加载本质(二)

    1.对于外键实体而言,EF会在用到这个外键属性的时候,才会去查对应的表.这就是按需加载了... 2.按需加载的缺点:每次调用外键实体的时候,都会去查询数据库(EF有小优化:相同的外键实体只查一次) I ...

  4. JAVA - 大数类详解

    写在前面 对于ACMer来说,java语言最大的优势就是BigInteger,Bigdecimal,String三个类. 这三个类分别是高精度整数,高精度浮点数和字符串,之所以说这个是它的优势是因为j ...

  5. Use the PDFs below or the HTML contents to the left to install and configure P6 EPPM and its additional components.

    Welcome to Your Documentation   Use the PDFs below or the HTML contents to the left to install and c ...

  6. 创建Google网站地图Sitemap.xml

    Sitemap.xml是google搞出来的,也就是网站地图,不过这个网站地图是用xml写的,而且要按google的标准来写,并且要将写出来的这个文件sitemap.xml上传到自己的服务器空间中去. ...

  7. WebApi传参总动员(填坑)

    本以为系列文章已经Over,突然记起来前面留了个大坑还没填,真是自己给自己挖坑. 这个坑就是: (body 只能被读取一次)Only one thing can read the body MVC和W ...

  8. Urlencode and Urldecode 命令行

    由于经常使用,简单记录之 $ alias urlencode='python -c "import sys, urllib as ul; print ul.quote_plus(sys.ar ...

  9. lavarel框架中如何使用ajax提交表单

    开门见山,因为laravel以post形式提交数据时候需要加{{csrf_field()}}防止跨站攻击,所以当你用ajax提交表单时候自然也要加 在网上看了很多的解决方式,我是用下面这种方法解决的: ...

  10. jQuery使用ajaxStart()和ajaxStop()方法

    ajaxStart()和ajaxStop()方法是绑定Ajax事件.ajaxStart()方法用于在Ajax请求发出前触发函数,ajaxStop()方法用于在Ajax请求完成后触发函数.它们的调用格式 ...