PPC的C/C++和人工智能学习笔记
每一篇学习笔记,都只是为了更好地掌握和理解

linux开发(6)_网络编程(1)socket基本概念

今天学习Linux的网络编程基础。

(Linux开发的基础:pclint,makefile,GDB调试等放在后面学习了。)

 

Linux擅长做服务器,比如:功能服务器(http,https,telnet,ssh,ftp…),集成服务器,高性能服务器等等。

 

服务程序的组成:(真正能用的服务器怎么写),分为三个部分。

一、IO通信单元:数据通信、IO读写

端口复用: select,poll,epoll三种机制,小规模的比如1K可以用前面2个,大规模(C10K)的用epoll。(主要学习这三个内容)

同步(accept,read/write等默认都是阻塞的),就是最基本的。

异步,用信号等方法来处理(类似windows的消息),如:signal,sigaction等。

异步IO,C10K,read和write–>aio_read,aio_write。

二、数据处理单元

多进程和多线程一般用在这里。

多线程:线程的互斥:mutex spin atomic等;线程的同步:PV sem等;线程的数据通信。

多进程:IPC(进程间通信) pipe fifo socketpair 等。

多线程+多进程:线程池,进程池

三、数据存储单元

内存池、slab、数据库等。

 

服务程序的结构:两种高性能服务结构(两种I/O多路复用模式)

Reactor模式:

主线程内只负责监听文件描述上是否有事件发生, 工作线程主要来进行读写数据等数据处理。

Proactor模式:

将所有的IO操作都交给主线程和内核处理,工作线程仅仅负责业务逻辑,因此该模式更适合服务器构架。

区别:reactor:能收了或者能发了,你通知我。proactor: 帮我收二十字节或者发二十字节,收好了或者发好了通知我。

 

OSI七层模型:

开放系统互连参考模型 (Open System Interconnect,简称OSI)是国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)联合制定的开放系统互连参考模型,为开放式互连信息系统提供了一种功能结构的框架。它从低到高分别是:物理层、数据链路层、网络层、传输层、会话层、表示层和应用层

 

TCP/IP参考模型:

TCP/IP是一组用于实现网络互连的通信协议。Internet网络体系结构以TCP/IP为核心。基于TCP/IP的参考模型将协议分成四个层次,它们分别是:网络接入层(对应物理层和数据链路层)、网际互联层(对应网络层)、传输层(对应传输层)、和应用层(对应OSI的高三层)

  1. 应用层:应用层对应于OSI参考模型的高层,为用户提供所需要的各种服务,例如:FTP、Telnet、DNS、SMTP等。
  2. 传输层:传输层对应于OSI参考模型的传输层,为应用层实体提供端到端的通信功能,保证了数据包的顺序传送及数据的完整性。该层定义了两个主要的协议:传输控制协议(TCP)和用户数据报协议(UDP)。TCP协议提供的是一种可靠的、通过“三次握手”来连接的数据传输服务;而UDP协议提供的则是不保证可靠的(并不是不可靠)、无连接的数据传输服务.
  3. 网际互联层:网际互联层对应于OSI参考模型的网络层,主要解决主机到主机的通信问题。它所包含的协议设计数据包在整个网络上的逻辑传输。注重重新赋予主机一个IP地址来完成对主机的寻址,它还负责数据包在多种网络中的路由。该层有三个主要协议:网际协议(IP)、互联网组管理协议(IGMP)和互联网控制报文协议(ICMP)。IP协议是网际互联层最重要的协议,它提供的是一个可靠、无连接的数据报传递服务。
  4. 网络接入层(即主机-网络层):网络接入层与OSI参考模型中的物理层和数据链路层相对应。它负责监视数据在主机和网络之间的交换。事实上,TCP/IP本身并未定义该层的协议,而由参与互连的各网络使用自己的物理层和数据链路层协议,然后与TCP/IP的网络接入层进行连接。地址解析协议(ARP)工作在此层,即OSI参考模型的数据链路层。

 

TCP/IP协议:

TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一个工业标准的协议集,它是为广域网(WANs)设计的。

TCP/IP协议存在于OS中,网络服务通过OS提供,在OS中增加支持TCP/IP的系统调用——Berkeley套接字,如Socket,Connect,Send,Recv等。

UDP(User Data Protocol,用户数据报协议)是与TCP相对应的协议。它是属于TCP/IP协议族中的一种。如图:

TCP/IP协议族包括运输层、网络层、链路层,而socket所在位置如图,socket是应用层与TCP/IP协议族通信的中间软件抽象层。

socket套接字:

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

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

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

 

TCP的建立(三次握手):

TCP协议通过三个报文段完成连接的建立,这个过程称为三次握手(three-way handshake),过程如下图所示。

第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;

第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

一个完整的三次握手也就是: 请求—应答—再次确认。

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认(发送ACK K+1);服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

 

TCP连接的终止(四次握手释放)

建立一个连接需要三次握手,而终止一个连接要经过四次握手,这是由TCP的半关闭(half-close)造成的。

由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送。

(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。

(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A。

(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1。

某个应用进程首先调用close主动关闭连接,这时TCP发送一个FIN M;

另一端接收到FIN M之后,执行被动关闭,对这个FIN进行确认。它的接收也作为文件结束符传递给应用进程,因为FIN的接收意味着应用进程在相应的连接上再也接收不到额外数据;

一段时间之后,接收到文件结束符的应用进程调用close关闭它的socket。这导致它的TCP也发送一个FIN N;

接收到这个FIN的源发送端TCP对它进行确认。

这样每个方向上都有一个FIN和ACK。

 

用tcpdump抓包工具来观察三次握手连接和四次握手释放:

准备一个最简单的tcp server程序并运行,另外准备一个最简单的tcp客户端程序并运行(也可以用telnet 192.168.31.233 4444来代替客户端)。

//easy tcp server s.c

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <string.h>

#include <unistd.h>

int main(){

int sock = socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in addr_s;

addr_s.sin_family = AF_INET;

addr_s.sin_port = htons(4444);

addr_s.sin_addr.s_addr = htonl(INADDR_ANY);

bzero(&addr_s.sin_zero,8);

bind(sock,(struct sockaddr*)&addr_s,sizeof(struct sockaddr));

listen(sock,5);

struct sockaddr_in addr_c;

int len = sizeof(addr_c);

int sock_c = accept(sock,(struct sockaddr*)&addr_c,&len);

printf(“accept ok!\n”);

sleep(1);

close(sock_c);

sleep(100);

return 0;

}

 

//easy tcp client c.c

#include <stdio.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <arpa/inet.h>

#include <string.h>

#include <unistd.h>

int main(){

int sock = socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in addr_s;

addr_s.sin_family = AF_INET;

addr_s.sin_port = htons(4444);

addr_s.sin_addr.s_addr = inet_addr(“192.168.31.233”);

bzero(&addr_s.sin_zero,8);

connect(sock,(struct sockaddr*)&addr_s,sizeof(addr_s));

printf(“connect ok\n”);

sleep(3);

close(sock);

printf(“close sock\n”);

sleep(100);

return 0;

}

 

# gcc -o s s.c 分别在两个终端运行服务端和客户端,并用tcpdump抓包

# ./s 运行服务端(listen 4444并在该端口准备accept)

# tcpdump -S host 192.168.31.233 and 192.168.31.231

tcpdump: verbose output suppressed, use -v or -vv for full protocol decode

listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes

16:47:08.620929 IP 192.168.31.231.46518 > 192.168.31.233.4444: Flags [S], seq 765646754, win 29200, options [mss 1460,sackOK,TS val 72696 ecr 0,nop,wscale 7], length 0

16:47:08.620985 IP 192.168.31.233.4444 > 192.168.31.231.46518: Flags [S.], seq 3696614838, ack 765646755, win 28960, options [mss 1460,sackOK,TS val 383397 ecr 72696,nop,wscale 7], length 0

16:47:08.621091 IP 192.168.31.231.46518 > 192.168.31.233.4444: Flags [.], ack 3696614839, win 229, options [nop,nop,TS val 72696 ecr 383397], length 0

[S] 表示这是一个SYN请求

  • [S.] 表示这是一个SYN+ACK确认包:
  • [.] 表示这是一个ACT确认包, (client)SYN->(server)SYN->(client)ACT 就是3次握手过程
  • [P] 表示这个是一个数据推送,可以是从服务器端向客户端推送,也可以从客户端向服务器端推
  • [F] 表示这是一个FIN包,是关闭连接操作,client/server都有可能发起
  • [R] 表示这是一个RST包,与F包作用相同,但RST表示连接关闭时,仍然有数据未被处理。可以理解为是强制切断连接
  • win 4099 是指滑动窗口大小
  • length 18指数据包的大小

 

关闭连接时的4个包:

17:30:48.901679 IP 192.168.31.233.4444 > 192.168.31.231.46534: Flags [F.], seq 3238309642, ack 4040338479, win 227, options [nop,nop,TS val 1038467 ecr 727534], length 0

17:30:48.904099 IP 192.168.31.231.46534 > 192.168.31.233.4444: Flags [.], ack 3238309643, win 229, options [nop,nop,TS val 727785 ecr 1038467], length 0

17:30:50.901928 IP 192.168.31.231.46534 > 192.168.31.233.4444: Flags [F.], seq 4040338479, ack 3238309643, win 229, options [nop,nop,TS val 728284 ecr 1038467], length 0

17:30:50.901952 IP 192.168.31.233.4444 > 192.168.31.231.46534: Flags [.], ack 4040338480, win 227, options [nop,nop,TS val 1038967 ecr 728284], length 0

 

两个小问题:

1.为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?

这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但并不表示你所有的数据都全部发送给对方了,所以你可能不会马上关闭SOCKET,也就是你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

 

2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?

这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。

 

 

 

(2017-11-08 www.vsppc.com)

学习笔记未经允许不得转载:PPC的C/C++和人工智能学习笔记 » linux开发(6)_网络编程(1)socket基本概念

分享到:更多 ()

评论 333

评论前必须登录!