网络编程之理论篇

网络通讯做为互联网的技术支持,已被普遍应用在软件开发中,不管是Web,服务端,客户端仍是桌面应用,都是必须掌握的一门技术。web

什么是网络编程?

在软件开发层面实现远程数据交换的编程技术。
复制代码

网络编程的主要场景

  • 基于Http/Https的web,移动端的开发;
  • 基于TCP/UDP的IM,桌面应用的开发;
  • 自定义协议的开发。

要熟悉网络编程,首先须要学习网络协议的相关知识。面试

网络协议

什么网络协议呢?网络协议是为网络中进行数据交换定义的规则,以实现按此规范进行传输数据,就可在整个互联网中进行数据交换的目标。因此说网络协议是网络通讯的基础。编程

网络协议中其中最著名就是TCP/IP协议族。TCP/IP协议族一般被认为是一个四层的协议系统。从上到下依次为:服务器

  • 应用层:包括全部和应用程序协同工做,利用基础网络交换应用程序专用的数据协议,如Telnet, FTP, Http等;
  • 运输层:负责为应用层提供端到端的通讯支持,包含TCP和UDP协议;
  • 网络层:负责数据包在网络中的传输,包含IP, ICMP和IGMP协议;
  • 链路层:用以处理与物理设备的交互细节,对应于操做系统中的设备驱动程序;

从TCP/IP协议族各层次的职责来看,网路数据的传递是从上到下依次传递。以Http协议为为例,具体的数据传输过程如图所示:网络

数据传输过程

固然,这只是数据的流向,实际上网络数据在传输过程当中须要封装与分解,具体的过程如图所示(图片来源于百度图片):socket

简单来讲,数据从本机上传到网络以前,会在TCP/IP协议族的每一层添加协议首部,到达目标主机后,目标主机再进行分解,从而获取须要的数据。学习

在众多的网络协议中,使用最普遍的应该就是TCP协议(传输控制协议)了,它是一种面向链接,可靠的,基于字节流的传输层协议。其运行过程分为三个阶段:创建链接、交换数据、断开链接。spa

TCP链接的创建与终止

在使用TCP协议交换数据前,需先创建一条链接,在不须要发送数据的时候,须要断开链接,以释放资源。操作系统

创建链接

TCP协议创建链接时须要三次握手,其过程可以使用如下场景来描述:code

面试官:说一下TCP创建链接时三次握手的过程。
小明: 三次握手?
面试官: 嗯。
小明:握手完成了。
面试官:what?
复制代码

没错,从小明开始问三次握手到说握手完成就是三次握手的过程,具体的过程以下:

  1. 客户端发向服务端发送一个SYN段,以告知客户端要链接服务端的指定端口;
  2. 服务端收到客户端的报文后,返回一个对客户端报文进行确认的ack段(SYN+1)和一个表示服务端报文的SYN段;
  3. 客户端收到服务端发送的报文后,对服务端发送一个ack(服务端的SYN+1),至此,客户端和服务端就创建了一条链接。

为何须要3次握手而不是2次呢?

由于客户端收到服务端的应答后,知道链接已创建成功,可是服务端并不知道本身发送的确认报文客户端是否收到,
因此须要客户端对服务端的报文进行确认。
复制代码

服务端必定能收到客户端发送的确认报文么?

不必定,若是收不到,那么链接就不会创建,因此,3次握手只是理论上确保创建链接的次数。那可否经过4次握手呢?
不行,再握下去就是鸡生蛋,蛋生鸡的问题了。
复制代码

断开链接

TCP断开链接时须要四次握手,为何须要4次呢?这是因为TCP半关闭的性质形成的。所谓半关闭,就是能够发送数据,却不能接收数据或只能接收数据,不能发送数据。

四次握手的过程:(因为主动断开链接可发送在客户端,也可发送在服务端,因此下面以A,B来区分两端)

  1. A端向B端发送一个FIN, 告知A端即将断开链接;
  2. B端收到A端发送的FIN后发送一个ack对其进行确认;
  3. 随后B端向再A端发送一个FIN,以告知B端也即将断开链接;
  4. A端时候到B端的FIN报文后,对其发送一个ack以示确认。

B端在对A端进行确认的时候为何不一样时发一个FIN呢? 能够同时发。分开发是为了考虑B端在对A端确认后,可能还会给A继续发送数据的状况。

TCP的状态变迁过程

下面以一张图来描述TCP的状态变迁过程(图片来自百度图片)

图片中的全部状态对应于TCP的创建和链接过程,下面简单介绍一下这几种状态:

LISTEN: 服务端状态,表示服务端正在等待客户端的链接请求,处于监听状态;

SYN收到:服务端状态,服务端已收到客户端的链接请求,并对客户端的请求发送了ACK确认(第二次握手完成);

SYN_SENT: 客户端状态,客户端发送SYN或数据后的状态(第一次握手完成);

ESTABLISHED: 客户端对服务端的SYN进行确认后处于ESTABLISHED,服务端收到客户端发送的ACK后也会处于
             ESTABLISHED状态(三次握手完成后的状态);

FIN_WAIT_1: 主动关闭的一端的状态,发送FIN后的状态(断开链接时的第一次握手完成);

FIN_WAIT_2: 主动关闭的一端的状态,收到另外一端的ACK确认后的状态(断开链接时的第二次握手完成);

CLOSING:主动关闭的一端的状态,收到另外一端的FIN,并对其进行确认后的状态(客户端和服务端同时关闭的状况);

TIME_WAIT: 主动关闭的一端的状态,,收到另外一端的FIN或(FIN和ACK)后,对其进行确认后的状态
          (断开链接时最后一次握手完成);

CLOSE_WAIT: 被动关闭的一端的状态,收到另外一端发送的FIN并对其进行确认后的状态(断开链接时第二次握手完成);

LAST_ACK: 被动关闭的一端的状态,发送FIN后的状态(断开链接时第三次握手完成);

CLOSED:链接完全断开;
复制代码

TCP/IP协议族诞生以后,各个平台(Window, Unix)就按照此协议规范在系统层面为开发网络程序提供了统一的接口——Socket。经过这个面向传输层协议的系统接口,咱们可经过TCP/UDP协议快速实现网络数据的交换,同时也可用来实现应用层协议,如HTTP, SSL等。

Socket(套接字)

Socket是操做系统为上层应用实现网络数据交换提供的接口,咱们可经过如下场景来理解:

当你给别人打电话的时候首先要确认打给谁,其次确认打哪一个号码,经过这两个条件就可准确的联系到对方。那么在网络中传输数据也是一样的道理,在网络中定位主机是经过IP来实现的,一个IP表明了一台主机,可是每台主机有不少个端口号,因此要准确地与某个应用进行数据交换,除了IP地址外,还须要一个端口号。有了这两个条件,就可经过Socket实现数据交换。因而可知,Socket其实就至关于一部手机,两部手机之间创建一条通路便可实现通话。

Socket编程的步骤

客户端

  1. 建立scoket;
  2. 链接服务器;
  3. 发送、接收数据;
  4. 关闭socket链接

代码实现(Linux C编程):

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h> 

int main(int argc, char *argv[])
{
    int sockfd = 0, n = 0;
    char recvBuff[1024];
    struct sockaddr_in serv_addr; 

    if(argc != 2)
    {
        printf("\n Usage: %s <ip of server> \n",argv[0]);
        return 1;
    } 

    memset(recvBuff, '0',sizeof(recvBuff));
    // 建立socket
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    } 

    // 设置IP和端口
    memset(&serv_addr, '0', sizeof(serv_addr)); 
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000); 

    if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
    {
        printf("\n inet_pton error occured\n");
        return 1;
    } 
    // 链接到指定的IP和端口 -> 链接成功后即三次握手完成
    if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
    {
       printf("\n Error : Connect Failed \n");
       return 1;
    } 
    // 读数据
    while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
    {
        recvBuff[n] = 0;
        if(fputs(recvBuff, stdout) == EOF)
        {
            printf("\n Error : Fputs error\n");
        }
    } 

    if(n < 0)
    {
        printf("\n Read error \n");
    } 
    close(scokfd);
    
    return 0;
}
复制代码

服务端

  1. 建立socket;
  2. 绑定IP和端口;
  3. 监听客户端的链接;
  4. 接收客户端的链接;
  5. 发送、接收数据
  6. 关闭socket链接;

代码实现:(Linux C编程)

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h> 

int main(int argc, char *argv[])
{
    int listenfd = 0, connfd = 0;
    struct sockaddr_in serv_addr; 

    char sendBuff[1025];
    time_t ticks; 
    
    // 建立socket
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serv_addr, '0', sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff)); 
    
    // 绑定IP地址和端口
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000); 
    bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); 
    
    // 监听客户端口的链接请求 -> 对应于状态图中的LISTEN状态
    listen(listenfd, 10); 
    while(1)
    {
        // 接收客户端的请求 -> 与客户端三次握手完成
        connfd = accept(listenfd, (struct sockaddr*)NULL, NULL); 
        ticks = time(NULL);
        snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
        
        // 向客户端发送数据
        write(connfd, sendBuff, strlen(sendBuff)); 

        // 关闭socket
        close(connfd);
        sleep(1);
     }
}
复制代码

代码出处 https://www.thegeekstuff.com/2011/12/c-socket-programming/

经过以上代码,咱们对socket有了一个简单的认识,同时也了解了数据交换的基本流程。后面会对基于TCP协议的HTTP协议进行一个详细的介绍。

参考资料

TCP/IP协议族

传输控制协议

《TCP/IP详解 卷一》

相关文章
相关标签/搜索