当前位置:   article > 正文

4.3_guideline 4.3 - design

guideline 4.3 - design
synchronous同步的
TCP(Transmission Control Protocol) 传输控制协议
位码即tcp标志位,有6种标示:
SYN(synchronous建立联机) ACK(acknowledgement 确认) PSH(push传送) FIN(finish结束) RST(reset重置) URG(urgent紧急)
Sequence number(顺序号码) Acknowledge number(确认号码)
************************************************************************************************************8
第一次握手:主机A发送位码为syn=1,随机产生seq number=1234567的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=7654321的包
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,
主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。
完成三次握手,主机A与主机B开始传送数据。
************************************************************************************************************
四次分手:
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止
这个方向的
连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。

(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。
************************************************************************************************************
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。
而这两种状态的区别 是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,
此时该SOCKET即
进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,
都应该马 上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
************************************************************************************************************
2.为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?   报文最长生命 maximum segment lifetime
这是因为虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND
状态到
ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,
因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重
发可能丢失
的ACK报文。
************************************************************************************************************
                  int socket(int domain, int type, int pritocol);
      domain参数标识系统用的那个底层协议族,我们示例程序中用的是AF_INET,表示用于ipv4 。
      type 指定服务类型,SOCK_STREAM是流式服务,SOCK_UGRAM是数据报服务(用于UDP通信中)。
      protocol指在前两个参数确定的情况的 下,再选择一个具体的协议。不过一般这个值把它设置为0,表示默认协议。
      函数返回整形的socket文件描述符,失败返回-1,并设置errno.
************************************************************************************************************
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
       bind将my_addr所指的socket地址分配给未命名sockfd文件描述符,addrlen参数支出该socket地址的长度。
        bind
成功时返回0,失败时返回-1并设置errron,常见errno是EACCES(被绑定的地址受保护),EADDRINUSE(被绑定的地址使用中
)。
************************************************************************************************************
监听:socket被命名后不能马上接受客户连接,需要使用系统调用来建一个监听队列以存放待处理的客户连接。
               int listen(int sockfd, int backlog);
第一个参数指定监听的socke,backlog提示内核监听队列的最大长度。
监听队列的长度如果唱过backlog,服务器将不受理新的客户连接,客户端也将收到 CONNECTREFUSED信息。
               listen成功返回0,失败返回-1并设置errno。
************************************************************************************************************
 int accept(int sockfd, struct sockaddr* addr, socklen_t addrlen);
 
accept成功时返回一个新的连接socket,该socket地址唯一的标识了被接受的这个连接,服务器可以通过读写该socket来与被接
受连接对应
  的客户端通信。accept失败时返回-1并设置errno。
自此,服务器端在while(1)的控制下可以一直处于等待连接状态。
************************************************************************************************************
   ssize_t recv((int sockfd, void *buf, size_t len, int flags);
          ssize_t send(int sockfd, const void *buf, size_t len, int flags);
          
recv读取sockfd上的数据,buf、len参数分别指定读缓冲区的位置和大小,flags为数据收发提供了额外的控制,通常
设为0即可。
    
recv成功返回实际读取到的数据的长度,它可能小于我们期望的长度len。因此可能需要多次调用recv,才能读取到完
整数据。
     recv可能返回0,意味着通信对方已经关闭连接了。recv出错返回-1并设置errno。
        send 往sockfd上写入数据,buf和len参数分贝制定写缓冲区的位置和大小。send成功时返回实际写入的数据长度,
  失败返回-1并设置errno。
************************************************************************************************************ 
 
  int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);
serv_addr参数是服务器监听的socket地址,addrlen参数指定这个地址的长度。connect成功时返回0,一旦成功建立连接,
sockfd就唯一的标识这个连接,客户端可以通过读写sockfd来与服务器通信。connect失败返回-1,并设置errno。
************************************************************************************************************
int  close(int fd);
      关闭一个连接实际上是关闭该连接对应的socket,可通过如下关闭普通文件描述符的系统调用来完成。
   但是close并非立即关闭一个连接,而是将fd的引用计数减1,只有当引用计数fd为0时,才真正的关闭连接。
  
若在多进程中,一次fork系统调用默认将父进程中打开的socket引用计数加1,因此我们在父进程和子进程都需要调用close
才能将连接关闭。
************************************************************************************************************
如果程序是计算密集型的,并发编程并没有优势,反而由于任务的切换使效率降低。但如果程序是I/O密集型的,比如经常读写文件

访问数据库等,情况就不同了。由于I/O操作的速度远没有CPU的计算速度快,所以让程序阻塞于I/O于I/O操作将浪费大量的CPU时间

如果程序有多个执行线程,则当前被I/O操作所阻塞的执行线程可主动放弃CPU(或由操作系统来调度),并将执行权转移到其他线
程。
因此CPU就可以用来做更加有意义的事情,而不是等待I/O操作完成,因此CPU利用率显著提升。
************************************************************************************************************
 int pipe(int
fd[2]);pipe函数参数是一个包含两个int型的数组指针,函数成功返回0,并将一对打开的文件描述符填入其参数指向的数组。
 如果失败,则返回-1,并设置errno。
默认情况下,这一对文件描述符都是阻塞的。如果用read系统调用来读取一个空的管道,read将被阻塞,直到管道内有数据可读;
如果我们用write系统调用往一个满的管道中写入数据,则write将被阻塞,直到管道有足够多的空间可用。
 
如果管道的写端文件描述符fd[1]的引用计数减少至0,即没有任何的进程要往管道写入数据,则针对该管道的读端文件描述符fd[0
]的read操作
 将会返回0,意味着读到了文件结束标记(EOF);反之,如果管道的读端文件描述符fd[0]的引用计数减少至0,
 即没有任何的进程需要从管道中读取数据,则针对该管道的写端文件描述符fd[1]的write操作将失败,并引发SIGPIPE信号。
 管道内部传输的数据是字节流,与TCP字节流概念相同,又有细微的区别。
而管道本身拥有一个容量限制,它规定如果应用程序不将数据从管道读走的话,该管道最多能被写入多少字节的数据。
自Linux2.6.11内核起,管道容量的大小默认65536字节。当然我们也可以使用fcntl函数修改管道容量。
************************************************************************************************************ς
1.C/S模型
TCP/IP协议在设计和实现上并没有客户端和服务器的概念。在通信过程中所有机器都是对等的。
但由于资源(视频、资源、软件等)都被数据提供者所垄断,所以几乎所有的网络应用程序都很自然地采用
C/S(客户端/服务器)模型:所有客户端都采用通过访问服务器来获取所需的资源。
由于客户连接请求是随机到达的异步事件,服务器就要适用某种I/O模型来监听这一事件。服务器使用的是I/O复用技术之一的selec
t系统调用。
当监听到连接器请求后,服务器就调用accept函数接受它,并分配一个逻辑单元为新的连接服务。
逻辑单元可以是新创建的子进程、子线程或者其他。
************************************************************************************************************
2. P2P模型
P2P(Peer to Peer, 点对点)模型比C/S模型更符合网络通信的实际情况。
它摒弃了以服务器为中心的格局,让网络上所有主机重新回归对等的地位。
P2P模型使得每台机器在消耗服务的同时也给别人提供服务,这样资源能够充分、自由地共享。
************************************************************************************************************
众所周知,IO是计算机上最慢的部分,先不看磁盘IO,针对网络编程,自然是针对网络IO。
************************************************************************************************************
not broadcast and not multicastNBNS
hierarchy层级
************************************************************************************************************
SIGTSTP中TSTP的意思是tty stop,即在control
terminal上输入了susp即,输入了ctl-z的suspend(悬停)键。那么SIGTSTP被发送给进程。
将进程暂停是SIGTSTP的默认action,用户可以自定义一其handler,而将进程暂停是SIGSTOP的定死的action,用户不能修改。
此外,二者没什么差别,都使用SIGCONT来讲进程重新激活。
************************************************************************************************************
服务器与客户端一对一
#include <stdio.h>   
2.#include <stdlib.h>   
3.#include <strings.h>   
4.#include <sys/types.h>   
5.#include <sys/socket.h>   
6.#include <memory.h>   
7.#include <unistd.h>   
8.//#include <linux/in.h>   
9.#include <netinet/in.h>   
10.//#include <linux/inet_diag.h>   
11.#include <arpa/inet.h>   
12.   
13.#include <signal.h>   
14.   
15./** 
16.  关于 sockaddr  sockaddr_in  socketaddr_un说明 
17.  http://maomaozaoyue.blog.sohu.com/197538359.html 
18.  */   
19.   
20.#define PORT    11910   //定义通信端口   
21.#define BACKLOG 5       //定义侦听队列长度   
22.#define buflen  1024   
23.   
24.void process_conn_server(int s);   
25.void sig_pipe(int signo);   
26.   
27.int ss,sc;  //ss为服务器socket描述符,sc为某一客户端通信socket描述符   
28.   
29.int main(int argc,char *argv[])   
30.{   
31.   
32.    struct sockaddr_in server_addr; //存储服务器端socket地址结构   
33.    struct sockaddr_in client_addr; //存储客户端 socket地址结构   
34.   
35.    int err;    //返回值   
36.    pid_t pid;  //分叉进行的ID   
37.   
38.    /*****************socket()***************/   
39.    ss = socket(AF_INET,SOCK_STREAM,0); //建立一个序列化的,可靠的,双向连接的的字节流   
40.    if(ss<0)   
41.    {   
42.        printf("server : server socket create error\n");   
43.        return -1;   
44.    }   
45.    //注册信号   
46.    sighandler_t ret;   
47.    ret = signal(SIGTSTP,sig_pipe);   
48.    if(SIG_ERR == ret)   
49.    {   
50.        printf("信号挂接失败\n");   
51.        return -1;   
52.    }   
53.    else   
54.        printf("信号挂接成功\n");   
55.   
56.   
57.    /******************bind()****************/   
58.    //初始化地址结构   
59.    memset(&server_addr,0,sizeof(server_addr));   
60.    server_addr.sin_family = AF_INET;           //协议族   
61.    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   //本地地址   
62.    server_addr.sin_port = htons(PORT);   
63.   
64.    err = bind(ss,(struct sockaddr *)&server_addr,sizeof(sockaddr));   
65.    if(err<0)   
66.    {   
67.        printf("server : bind error\n");   
68.        return -1;   
69.    }   
70.   
71.    /*****************listen()***************/   
72.    err = listen(ss,BACKLOG);   //设置监听的队列大小   
73.    if(err < 0)   
74.    {   
75.        printf("server : listen error\n");   
76.        return -1;   
77.    }   
78.   
79.    /****************accept()***************/   
80.    /** 
81.    为类方便处理,我们使用两个进程分别管理两个处理: 
82.    1,服务器监听新的连接请求;2,以建立连接的C/S实现通信 
83.    这两个任务分别放在两个进程中处理,为了防止失误操作 
84.    在一个进程中关闭 侦听套接字描述符 另一进程中关闭 
85.    客户端连接套接字描述符。注只有当所有套接字全都关闭时 
86.    当前连接才能关闭,fork调用的时候父进程与子进程有相同的 
87.    套接字,总共两套,两套都关闭掉才能关闭这个套接字 
88.    */   
89.   
90.    for(;;)   
91.    {   
92.        socklen_t addrlen = sizeof(client_addr);   
93.        //accept返回客户端套接字描述符   
94.        sc = accept(ss,(struct sockaddr *)&client_addr,&addrlen);  //注,此处为了获取返回值使用 指针做参数   
95.        if(sc < 0)  //出错   
96.        {   
97.            continue;   //结束此次循环   
98.        }   
99.        else   
100.        {   
101.            printf("server : connected\n");   
102.        }   
103.   
104.        //创建一个子线程,用于与客户端通信   
105.        pid = fork();   
106.        //fork 调用说明:子进程返回 0 ;父进程返回子进程 ID   
107.        if(pid == 0)        //子进程,与客户端通信   
108.        {   
109.            close(ss);   
110.            process_conn_server(sc);   
111.        }   
112.        else   
113.        {   
114.            close(sc);   
115.        }   
116.    }   
117.}   
118.   
119./** 
120.  服务器对客户端连接处理过程;先读取从客户端发送来的数据, 
121.  然后将接收到的数据的字节的个数发送到客户端 
122.  */   
123.   
124.//通过套接字 s 与客户端进行通信   
125.void process_conn_server(int s)   
126.{   
127.    ssize_t size = 0;   
128.    char buffer[buflen];  //定义数据缓冲区   
129.    for(;;)   
130.    {   
131.        //等待读   
132.        for(size = 0;size == 0 ;size = read(s,buffer,buflen));   
133.        //输出从客户端接收到的数据   
134.        printf("%s",buffer);   
135.   
136.        //结束处理   
137.        if(strcmp(buffer,"quit") == 0)   
138.        {   
139.            close(s);   //成功返回0,失败返回-1   
140.            return ;   
141.        }   
142.        sprintf(buffer,"%d bytes altogether\n",size);   
143.        write(s,buffer,strlen(buffer)+1);   
144.    }   
145.}   
146.void sig_pipe(int signo)   
147.{   
148.    printf("catch a signal\n");   
149.    if(signo == SIGTSTP)   
150.    {   
151.        printf("接收到 SIGTSTP 信号\n");   
152.        int ret1 = close(ss);   
153.        int ret2 = close(sc);   
154.        int ret = ret1>ret2?ret1:ret2;   
155.        if(ret == 0)   
156.            printf("成功 : 关闭套接字\n");   
157.        else if(ret ==-1 )   
158.            printf("失败 : 未关闭套接字\n");   
159.   
160.        exit(1);   
161.    }   
162.}   


客户端代码:



[cpp] view plain copy
 1.    #include <stdio.h>   
2.    #include <strings.h>   
3.    #include <unistd.h>   
4.    #include <sys/types.h>   
5.    #include <sys/socket.h>   
6.    //#include <linux/in.h>   
7.    #include <stdlib.h>   
8.    #include <memory.h>   
9.    #include <arpa/inet.h>   
10.    #include <netinet/in.h>   
11.       
12.    #include <signal.h> //添加信号处理  防止向已断开的连接通信   
13.       
14.    /** 
15.      信号处理顺序说明:在Linux操作系统中某些状况发生时,系统会向相关进程发送信号, 
16.      信号处理方式是:1,系统首先调用用户在进程中注册的函数,2,然后调用系统的默认 
17.      响应方式,此处我们可以注册自己的信号处理函数,在连接断开时执行 
18.      */   
19.       
20.       
21.    #define PORT    11910   
22.    #define Buflen  1024   
23.       
24.    void process_conn_client(int s);   
25.    void sig_pipe(int signo);    //用户注册的信号函数,接收的是信号值   
26.       
27.    int s;  //全局变量 , 存储套接字描述符   
28.       
29.    int main(int argc,char *argv[])   
30.    {   
31.       
32.        sockaddr_in server_addr;   
33.        int err;   
34.        sighandler_t ret;   
35.        char server_ip[50] = "";   
36.        int port = 0; 
37.        strcpy(server_ip, argv[1]); 
38.        port = atoi(argv[2]); 
39. 
40.        /********************socket()*********************/   
41.        s= socket(AF_INET,SOCK_STREAM,0);   
42.        if(s<0)   
43.        {   
44.            printf("client : create socket error\n");   
45.            return 1;   
46.        }   
47.        printf("client : socket fd = %d\n", s);  
48.        //信号处理函数  SIGINT 是当用户按一个 Ctrl-C 建时发送的信号   
49.        ret = signal(SIGTSTP,sig_pipe);   
50.        if(SIG_ERR == ret)   
51.        {   
52.            printf("信号挂接失败\n");   
53.            return -1;   
54.        }   
55.        else   
56.            printf("信号挂接成功\n") ;   
57.       
58.       
59.        /*******************connect()*********************/   
60.        //设置服务器地址结构,准备连接到服务器   
61.        memset(&server_addr,0,sizeof(server_addr));   
62.        server_addr.sin_family = AF_INET;   
63.        server_addr.sin_port = htons(PORT);   
64.        server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   
65.       
66.        /*将用户数入对额字符串类型的IP格式转化为整型数据*/   
67.        //inet_pton(AF_INET,argv[1],&server_addr.sin_addr.s_addr);   
68.        printf("please input server ip address : \n");   
69.        read(0,server_ip,50);   
70.        //err = inet_pton(AF_INET,server_ip,&server_addr.sin_addr.s_addr);   
71.        server_addr.sin_addr.s_addr = inet_addr(server_ip);   
72.       
73.        err = connect(s,(struct sockaddr *)&server_addr,sizeof(sockaddr));   
74.        if(err == 0)   
75.        {   
76.            printf("client : connect to server\n");   
77.        }   
78.        else   
79.        {   
80.            printf("client : connect error\n");   
81.            return -1;   
82.        }   
83.        //与服务器端进行通信   
84.        process_conn_client(s);   
85.        close(s);   
86.       
87.    }   
88.    void process_conn_client(int s)   
89.    {   
90.       
91.        ssize_t size = 0;   
92.        char buffer[Buflen];   
93.       
94.        for(;;)   
95.        {   
96.            memset(buffer,'\0',Buflen);   
97.            /*从标准输入中读取数据放到缓冲区buffer中*/   
98.            size = read(0,buffer,Buflen);   // 0,被默认的分配到标准输入  1,标准输出  2,error   
99.            if(size >  0)   
100.            {   
101.                //当向服务器发送 “quit” 命令时,服务器首先断开连接   
102.                write(s,buffer,strlen(buffer)+1);   //向服务器端写   
103.       
104.                //等待读取到数据   
105.                for(size = 0 ; size == 0 ; size = read(s,buffer,Buflen) );   
106.       
107.                write(1,buffer,strlen(buffer)+1);   //向标准输出写   
108.            }   
109.        }   
110.    }   
111.       
112.    void sig_pipe(int signo)    //传入套接字描述符   
113.    {   
114.        printf("Catch a signal\n");   
115.        if(signo == SIGTSTP)   
116.        {   
117.       
118.            printf("接收到 SIGTSTP 信号\n");   
119.            int ret = close(s);   
120.            if(ret == 0)   
121.                printf("成功 : 关闭套接字\n");   
122.            else if(ret ==-1 )   
123.                printf("失败 : 未关闭套接字\n");   
124.            exit(1);   
125.        }   
126.    }   


















声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/892157
推荐阅读
相关标签
  

闽ICP备14008679号