自学笔记整理,如有不对,请指正;

目录

一.网络分层:

1.特点:

①.每一层实现不同的功能,对上层的数据做透明传输

②.每一层向上层提供服务,同时使用下层提供的服务

2.各层典型协议

①网络接口与物理层

②网络层

③传输层

④应用层

3.数据传输的封包与拆包

二.TCP/IP网络编程预备知识

1.socket

①socket特点

②socket的类型

2.IP地址

①IP地址是Internet中主机的标识;

②表示形式:

③特殊IP地址:

④IP地址转换函数:

3.端口号

4.字节序

①.概念

②.字节序相关函数

三.TCP编程API

1.socket()函数:创建socket

2.bind()函数:绑定端口

3.listen函数:监听,把主动套接字变被动

4.accept()函数:阻塞等待客户端连接

5.connect()函数:连接服务器

四.TCP多线程服务器代码示例

1.头文件

2.服务器端:

3.客户端:

五.UDP编程

1.发送函数

①send()

②sendto() 

2.接收函数

①recv()

②recvfrom()

3.UDP循环服务器

①客户端

②服务器

一.IO模型和多路复用模型

①.阻塞IO(最常用)

②非阻塞IO(可防止进程阻塞在I/O操作上,需要轮询)

③多路复用I/O(允许同时对多个I/O进行控制)

④信号驱动I/O(一种异步通信模型)

二.网络分析测试工具、IP头、TCP头

1.网络抓包工具wireshark

2.以太网头

3.IP头/TCP头 

4.三次握手,四次释放

1.三次握手

2.四次挥手



网络互联促成了TCP/IP协议的产生,TCP协议分成了两个不同的协议:
用来检测网络传输中差错的传输控制协议TCP(可靠传输)
专门负责对不同网络进行互联的互联网协议IP(不可靠传输)

一.网络分层:

1.特点:

①.每一层实现不同的功能,对上层的数据做透明传输

②.每一层向上层提供服务,同时使用下层提供的服务

下三层属于linux内核部分

网络接口和物理层:屏蔽硬件差异

网络层(IP层):端到端的传输,A机器到B机器

传输层(TCP/UDP):数据包应该交给哪个任务去处理

应用层:各种应用层协议

2.各层典型协议

①网络接口与物理层

>MAC地址:48位全球唯一,网络设备的身份标识

ARP/RARP:

ARP:IP地址------>MAC地址

RARP:MAC地址------->IP地址

IP地址和MAC地址一一对应,通过这两个协议找到对方;

PPP协议:拨号协议(GPS/3G/4G)

②网络层

>IP协议(IPV4和IPV6)

>ICMP:Internet控制管理协议,ping命令属于ICMP

>IGMP:Internet分组管理协议,广播,组播

③传输层

>TCP:传输控制协议提供面向连接的,一对一的可靠数据传输协议(会有开销,占用一定资源)

可靠传输:即数据无误,无丢失,无失序,无重复到达的通信

>UDP:用户数据协议,提供不可靠的,无连接的尽力传输协议(实时性更好)

不可靠连接:不需要建立连接,效率高

>SCTP:TCP的增强版(S:stream),提供面向连接的,多对一或多对多的可靠数据传输协议

④应用层

网页访问协议:HTTP/HTTPS

邮件收发协议:POP3(收)/SMTP(发)/IMAP(接收邮件的一部分)/FTP

Tnet/SSH:远程登录

嵌入式相关

NTP:网络时钟协议

SNMP:简单网络管理协议(实现对网络设备集中式管理)

RTP/RTSP:传输音视频的协议(安防监控)

3.数据传输的封包与拆包

一个TCP协议下的完整数据包: 

app header为应用层的app协议,和data一起被打包为数据包data

Ethernet trailer(CRC):硬件产生和校验4个字节

MTU(Max Transform Unit):最大传输单元,与网络类型有关

以太网MTU=1500

MSS(真正的用户数据大小):最大段的大小,与网络类型,线路,系统的特性相关

以太网MSS=1460

封包过程:

拆包过程:

二.TCP/IP网络编程预备知识

1.socket

①socket特点

>socket是一个应用编程接口,代表着网络编程的一种资源;

>一种特殊的文件描述符(对它执行IO操作函数,如read,write);

>不局限于面向连接的TCP/IP,还有无连接的UDP;

②socket的类型

>流式套接字(SOCK_STREAM):

唯一对应着TCP,提供了一个面向连接的可靠的数据传输;

>数据报套接字(SOCK_DGRAM):

唯一对应着UDP,提供无连接服务;

>原始套接字(SOCK_RAW):(对应多个协议,传输穿透传输层)

可以跨过传输层,向较低层次协议进行直接访问如IP,ICMP;

2.IP地址

①IP地址是Internet中主机的标识;

>Internet中主机想要和别的机器通信必须要有一个IP地址;

>IP地址分为IPV4和IPV6:

IPV4:采用32位的整数来表示;

IPV6:采用了128位的整数来表示;

例如:mobileIPV6:local IP(本地注册IP),roam IP(漫游IP);

>每个数据包必须携带源IP地址和目的IP地址,路由器依靠此信息为数据包选择路由;

②表示形式:

>常用点分形式,如202.38.64.10(16个字节,结尾补0),最后转换成一个32位的无符号整数;

>32位整数形式(四个字节);

③特殊IP地址:

局域网IP:192.XXX.XXX.XXX   , 10.XXX.XXX.XXX

广播IP:XXX.XXX.XXX.255  , 255.255.255.255(全网广播)

组播IP:224.XXX.XXX.XXX~239.XXX.XXX.XXX

④IP地址转换函数:

#include "sys/socket.h"

#include "netinet/in.h"

#include arpa/inet.h"

in_addr_t inet_addr(const char*cp);

cp:点分形式的IP地址,结果是32位整数(内部包含了字节序的转换,默认是网络字节序的模式)

特点:

>仅适用于IPV4;

>出错时,返回-1;

>此函数不能适用于255.255.255.255的转换;(255的储存与-1的补码一样)

int inet_pton(int af,const char *src,void *dst);

特点:

>适用于IPV4和IPV6;

>能正确处理255.255.255.255的转换问题;

参数:

>af:地址协议族(AF_INET或AF_INET6);

>src:是一个指针(填写点分形式的IP地址[主要指IPV4] );

>dst:储存转换结果;

inet_ntop();

3.端口号

为了区分一台主机接收到的数据包应该交给哪个任务来处理,使用端口号区分,端口号是一个16位的数字(1-65535)。

>众所周知端口:1-1023(FTP:21,SSH:22,HTTP:80,HTTPS:469)

>保留端口:1024-5000(不建议使用,系统使用了,可能会发生冲突)

>可以使用端口:5000-65535

注意:TCP和UDP端口是相互独立的,比如都可以用5005;

网络里面的通信是由IP地址+(协议+端口)来决定数据包送给哪台机器的哪个任务来处理;

4.字节序

①.概念

不同类型的CPU的主机访问内存多字节(访问单字节数据,如字符串,则不存在大小端的问题)整数序列有两种方法,称为主机字节序(HBO):

>小端序:低字节储存在起始地址,即低对低,高对高;(X89/ARM)

>大端序:高字节储存在起始地址,即低对高,高对低;(powerpc/mips,ARM作为路由器时)

网络传输时采用大端模式,为了统一有了网络字节序和本地字节序,在发送接收数据时,相互转换;

②.字节序相关函数

主机字节序到网络字节序:

#include arpa/inet.h"

>u_long htonl(u_long hostlong);(host to long)

>u_short htons(u_short short);(host to net work short)

网络字节序到主机字节序:

>u_long ntohl(u_long hostlong);

>u_short ntohs(u_short short);

三.TCP编程API

1.socket()函数:创建socket

 #include "sys/socket.h"

 #include "sys/types.h"

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

出错返回-1,成功返回文件描述符

参数:

<domain:

AF_INET/AF_INET6(IPV4/IPV6);

AF_UNIX/AF_LOCAL(本地通信);

AF_NETLINK

AF_PACKKET(原始套接字)

.....

<type:SOCK_STREAM / SOCK_DGRAM / SOCK_RAW

<protocol:一般填0,原始套接字编程时需要填充

代码示例:

int fd;
struct socket_in sin;
/*1.创建socket */
if((fd=socket(AF_INET,SOCK_STREAM,0))<0)//创建socket
 {
        perror("socket");
        exit(-1);
 }

2.bind()函数:绑定端口

 #include "sys/socket.h"

 #include "sys/types.h"

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

成功返回0,失败返回1

参数:

>sockfd:socket返回的fd;

>addr:struct sockddr的结构体地址(通用结构体,man 7 ip 可查看)

若bind的addr不是具体的一个IP地址,而是INADDR_ANY(宏,值为-1)则代表bind服务器IP地址为本机的任意网卡地址;

>addrlrn:地址长度

  struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */(2字节)
               in_port_t      sin_port;   /* port in network byte order */(2字节)
               struct in_addr sin_addr;   /* internet address */(4字节)
           };

           /* Internet address. */
   struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };

强制转换struct socket_in为struct socket;

代码示例:

/*2.绑定*/
/*2.1填充*/
bzero(&sin,sizeof(sin));//清零
sin.sin_family=AF_INET;
sin.sin_port=htons(SERV_PORT);//网络字字节序的端口
sin.sin_addr.s_addr=inet_addr(SERV_IP_ADDR);//IP地址转换
/*     if(inet_pton(AF_INET,SERV_IP_ADDR,(void *)&sin.sin_addr)!=1)
          {
                  perror("inet_pton");
                  exit(-1);
          }
另一种方式的转换*/ 
/*2.2绑定*/
if(bind(fd,(*struc socket)&sin,sizeof(sin))<0)//绑定
{
          exit(-1);
}

 注意:如果是IPV6的编程,要使用struct sockadd_in6结构体填充(man 7 ipv6)

The sockaddr_in6 structure is bigger than the generic sockaddr.  Programs that assume that all address types can be stored safely in a struct sock‐addr need to be changed to use struct sockaddr_storage for that instead.

3.listen函数:监听,把主动套接字变被动

#include <sys/types.h> 
#include <sys/socket.h>

int listen(int sockfd, int backlog);

成功返回0,失败返回1

参数:

>sockfd:绑定的socket()返回的fd

>backlog:同时允许几路客户端进行正在连接的过程(三次握手),一般填5,ARM最大为8;

4.accept()函数:阻塞等待客户端连接


 #include <sys/types.h>       
 #include <sys/socket.h>

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

成功返回已经建立好连接的新的newfd,与之前的fd相区别,失败返回-1;

参数

>socket()返回的fd;

>addr和addrlen:用于获取客户端的IP地址和端口号;
 

5.connect()函数:连接服务器

#include <sys/types.h>     
#include <sys/socket.h>

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

成功返回0,失败返回-1;
和服务器的bind()函数写法类似;

四.TCP多线程服务器代码示例

1.头文件

#ifndef _CS_H_
#define _CS_H_
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "strings.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include "errno.h"
#include <arpa/inet.h>
#include "pthread.h"
#define BACKLOG 5
#define SERV_PORT 5001
#define BUFSIZE 64
#endif

2.服务器端:

#include "cs.h"
void* pthread_server(void*newfd);
int main()
{
        int fd=-1;
        int newfd=-1;
        struct sockaddr_in sin;
        /*1.创建socket */
        if((fd=socket(AF_INET,SOCK_STREAM,0))<0)//创建socket
        {
                perror("socket");
                exit(-1);
        }
        /*2.绑定*/
        /*2.1填充*/
        bzero(&sin,sizeof(sin));//清零
        sin.sin_family=AF_INET;
        sin.sin_port=htons(SERV_PORT);//网络字字节序的端口
        sin.sin_addr.s_addr=htonl(INADDR_ANY);//IP地址转换,绑定任意IP地址
        /*2.2绑定*/
        if((bind(fd,(struct sockaddr*)&sin,sizeof(sin)))<0)//绑定
        {
                exit(-1);
        }
        /*3.调用listen()把主动套接字变被动*/
        if(listen(fd,BACKLOG)<0)
        {
                perror("listen");
                exit(-1);
        }
        printf("Server staring...\n");
        /*4.阻塞等待客户端的连接请求*/
        struct sockaddr_in cin;
        socklen_t addrlen=sizeof(cin);
        pthread_t pthread;
        while(1)
        {
                if((newfd=accept(fd,(struct sockaddr*)&cin,&addrlen))<0){//阻塞等待连接
                        perror("accept");
                        exit(-1);
                }
                char ipaddr[16];
                if(inet_ntop(AF_INET,(void*)&cin.sin_addr,ipaddr,sizeof(cin))<0)
                {
                        perror("inet_ntop");
                        exit(-1);
                }
                printf("Client IP is:%s\nPort is:%d\n",ipaddr,ntohs(cin.sin_port));//打印客户端信息
                if(pthread_create(&pthread,NULL,pthread_server,(void*)&newfd)<0){//建线程
                perror("pthread_create");
                exit(-1);
                }
        }
        close(fd);
        return 0;

}
void* pthread_server(void*arg)//线程完成读写操作
{
        char buf[BUFSIZE];
        int newfd=*(int*)arg;
        int ret=-1;
        while(1)
        {
                bzero(buf,sizeof(buf));
                do
                {
                        ret=read(newfd,buf,BUFSIZE-1);
                }while(ret<0&&EINTR==errno);//EINTR系统调用被信号中断
                if(ret<0){
                        perror("read");
                        exit(1);
                }
                if(!ret)//对方已关闭
                break;
                printf("Receive data:%s\n",buf);
                if(strncasecmp(buf,"quit",strlen("quit"))==0){
                        printf("Client(fd=%d) is exiting\n",newfd);
                        break;
                }
        }
        close(newfd);
}


3.客户端:

#include "cs.h"
int main(int argc,char**argv)
{
        if(argc<3){
        printf("Usage....\n");
        exit(-1);
        }
        int fd; 
        short PORT=atoi(argv[2]);
        char*ADDR_IP=argv[1];
        struct sockaddr_in sin;
        /*1.创建socket*/
        if((fd=socket(AF_INET,SOCK_STREAM,0))<0){
                perror("socket");
                exit(-1);
        }
        /*2.连接服务器*/
        sin.sin_family=AF_INET;
        sin.sin_port=htons(PORT);
        sin.sin_addr.s_addr=inet_addr(ADDR_IP); 
        if(connect(fd,(struct sockaddr*)&sin,sizeof(sin))<0)
        {
                perror("connect");
                exit(-1);
        }
        /*3.读写数据*/
        char buf[BUFSIZE];
        while(1)
        {
                bzero(buf,BUFSIZE);
                if(fgets(buf,BUFSIZE,stdin)==NULL){
                        continue;
                }
                if(strncasecmp(buf,"quit",strlen("quit"))==0){
                        printf("CLient is exiting\n");
                        break;
                }
                write(fd,buf,strlen(buf));
        }
        /*4.关闭套接字*/
        close(fd);
        return 0;
}

五.UDP编程

UPD传输:无连接的不可靠传输,存在丢包,实时性好,用于实时音视频传输,DNS域名解析包

1.发送函数

①send()

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
成功返回发生的字节数,失败返回-1;

>和write相比,send多一个flags参数,一般填0,此时和write作用一样;

MSG_DONTWAIT:非阻塞操作,当缓冲区满时不阻塞等待;

MSG_OOB:用于发送TCP类型的带外数据(out—of—band);

②sendto() 

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
>相比于recv,多了两个参数,包含了接收方的IP地址、端口号、协议方式、地址长度;

2.接收函数

①recv()

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);
成功返回接收到的字节数,失败返回-1;

>和read相比,send多一个flags参数,一般填0,此时和write作用一样;

MSG_DONTWAIT:非阻塞操作,当缓冲区空时不阻塞等待;

MSG_OOB:用于接收TCP类型的带外数据(out—of—band);

MSG_PEEK:第一次从缓冲区读取的数据后,数据不会消失,下次去读还是原来的数据;

②recvfrom()

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

>相比于recv,多了两个参数,包含了发送方的IP地址、端口号、协议方式、地址长度;

3.UDP循环服务器

①客户端

#include "cs.h"
int main(int argc,char**argv)
{
        int fd=-1;
        if((fd=socket(AF_INET,SOCK_DGRAM,0))<0){//创建socket
                perror("socket");
                exit(-1);
        }
        char buf[BUFSIZE];
        struct sockaddr_in cin;//创建填充结构体
        bzero(&cin,sizeof(cin));
        short port=htons(atoi(argv[2]));
        cin.sin_family=AF_INET;
        cin.sin_port=port;
        cin.sin_addr.s_addr=inet_addr(argv[1]);
        socklen_t addrlen=sizeof(cin);
        while(1){//客户端发送数据
                if(fgets(buf,BUFSIZE-1,stdin)==NULL){
                        perror("fgets");
                        exit(-1);
                }
                sendto(fd,buf,strlen(buf),0,(struct sockaddr*)&cin,addrlen);
                if(strncasecmp(buf,"quit",strlen("quit"))==0)
                {
                        printf("Client is exiting...\n");
                        break;  
                }
        }
        close(fd);
}
           

②服务器

#include "cs.h"
int main()
{
        int fd=-1;
        struct sockaddr_in sin;
    
        if((fd=socket(AF_INET,SOCK_DGRAM,0))<0){//创建socket
                perror("socket");
                exit(-1);
        }
        bzero(&sin,sizeof(sin));
        sin.sin_family=AF_INET;
        sin.sin_port=htons(SERV_PORT);
        sin.sin_addr.s_addr=INADDR_ANY;
        if(bind(fd,(struct sockaddr*)&sin,sizeof(sin))<0){//绑定
                perror("bind");
                exit(-1);
        }
        char buf[BUFSIZE];
        struct sockaddr_in cin;
        socklen_t addrlen;
        while(1){//等待接受数据,并处理
                bzero(buf,BUFSIZE);
                bzero(&cin,sizeof(cin));
                if(recvfrom(fd,buf,BUFSIZE-1,0,(struct sockaddr*)&cin,&addrlen)<0){
                        perror("recvfrom");
                        continue;
                }
                char client_ip[16];
                inet_ntop(AF_INET,(void*)&cin.sin_addr,client_ip,sizeof(cin));
                short port=ntohs(cin.sin_port);

                if(strncasecmp(buf,"quit",strlen("quit"))==0){//处理数据
                        printf("Client:%s  port:%d is exiting...\n",client_ip,port);
                }
                else{
                        printf("Recive data from:%s port:%d:\n%s\n",client_ip,port,buf);
                }
        }
}

   

六.IO模型和多路复用模型

在UNIX/Linux下主要有4中I/O模型:

①.阻塞IO(最常用)

缺省情况下,套接字建立后所处的模式就是阻塞I/O,还有读操作:read,recv,recvfrom;写操作:write,send(sendto不阻塞);其他操作:accept,connect

>读阻塞:以read为例:调用read从套接字上读取数据,当套接字接收缓冲区为空时,read将发生阻塞,直到缓冲区有数据或者有信号打断阻塞,内核就会唤醒该进程,但如果阻塞过程中发生故障,那进程将永远阻塞下去;

>写阻塞:缓冲区大小小于要写入数据量的大小,此时写操作不进行任何拷贝,发生阻塞,缓冲区空间足够时,内核唤醒进程,将数据从用户缓冲区拷贝到相应的缓冲区;UDP套接字上执行的写操作sendto永远不会阻塞,因为这个函数不经过内核缓冲区;

②非阻塞IO(可防止进程阻塞在I/O操作上,需要轮询)

非阻塞I/O下,当I/O操作不能立刻进行时,内核不会让进程阻塞,而是返回一个错误,使用非阻塞套接字时需要有一个应用程序来循环测试是否一个文件描述符可读(叫做polling),这个过程很消耗CPU资源,不普遍使用;

>非阻塞模式的实现:

socket创建的套接字一般为阻塞I/O,要通过函数fcntl设一个套接字的标志位O_NONBLOCK来实现非阻塞;

fcntl()

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );
例如:

int flag;
flag=fcntl(fd,F_GETFL,0);
flag|=O_NONBLOCK;
fcntl(fd,F_GETFL,flag);

ioctl()

#include <sys/ioctl.h>

int ioctl(int fd, unsigned long request, ...);

int b_on=1;
ioctl(fd,FIONBIO,&b_on);

③多路复用I/O(允许同时对多个I/O进行控制)

当应用程序处理多路输入输出流时,阻塞I/O达不到预期效果,非阻塞I/O会占用太多资源,若设置多个进程,又会产生新进程间的通信问题,使程序变得复杂;较好的解决方法是使用I/O多路复用,基本思想是:

>把关心的文件描述符组成一个表fd_set,表的每一位代表一个文件描述符,这张表大小写是4字节的整数倍,具体根据存在的最大fd决定(节约资源);

>然后调用函数select()/poll()函数,监控fd_set中的文件描述符,阻塞等待一个或多个文件描述符有数据,退出select();

>退出select()后依次判断哪个文件描述符有数据;

>依次处理有数据的文件描述符;

fd_set相关函数()

void FD_ZERO(fd_set*fdset);//对集合清零

void FD_SET(int fd,fd_set*fdset);//加入集合

void FD_CLR(int fd,fd_set*fdset);//清除fd

int FD_ISSET(int fd,fd_set*fdset);//判断fd是否在集合中

select()

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
参数:

nfds:最大的文件描述符maxfd+1;

readfds/writefds:读写文件描述符集合;

超时结构体:

  struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

一般填充读集合,写集合为NULL,异常集合一般NULL(带外数据才填),超市检测,阻塞超出时间时退出select,select退出后,集合表示有数据的集合;

也就是说select()之前是关心的文件描述符的集合,select()之后是有数据的文件描述符;

④信号驱动I/O(一种异步通信模型)

七.网络分析测试工具、IP头、TCP头

1.网络抓包工具wireshark

baidu直接下载就行;

linux下:sudo apt-get install wireshark

2.以太网头

以太网头中有目的MAC地址和源MAC地址各占6个字节,每个MAC地址都是全球唯一的;

CRC字段用于对帧内数据进行校验,保证数据传输的正确性,通常是硬件实现的。

3.IP头/TCP头 

https://blog.csdn/m0_37542524/article/details/84594111

4.三次握手,四次释放

1.三次握手

①第一次握手

A的TCP客户进程向B发出建立连接请求报文段,其中SYN(同步位)=1,ACK(确认位)=0,seq(序号)=x。

TCP规定,当报文段的SYN=1且ACK=0时,表明这是一个请求建立连接的;SYN报文段(SYN=1的报文段)不能携带数据,但是要消耗掉一个序号。

在A发送完毕之后,A的TCP客户端进程进入SYN-SENT(同步已发送)状态。

②第二次握手

B在收到连接请求报文段之后,如果同意建立连接,则向A发送确认报文段。其中SYN=1,ACK=1,ack(确认号)=x+1,同时设置自己的初始序号seq=y。

TCP规定,当报文段的SYN=1且ACK=1时,表明这是一个同一建立连接响应报文段;这个报文段也不能携带数据,同样需要消耗掉一个序号。

在B发送完毕之后,B的TCP服务端进程进入SYN-RCVD(同步收到)状态。

③第三次握手

A在收到B的确认报文段之后,还需要向B给出确认报文段。其中ACK=1,seq=x+1,ack=y+1。

TCP规定,这个ACK报文段可以携带数据;如果不携带数据则不消耗序号,即A下一个数据报文段的序号仍然是seq=x+1。

在A发送完毕之后,A的TCP客户端进程进入ESTABLISHED(已建立连接)状态;B在接收到A的确认报文段之后,B的服务端进程也进入ESTABLISHED(已建立连接)状态。

原文链接:https://blog.csdn/guoweimelon/article/details/50878730

2.四次挥手

 

先由客户端向服务器端发送一个FIN,请求关闭数据传输。

当服务器接收到客户端的FIN时,向客户端发送一个ACK,其中ack的值等于FIN+SEQ

然后服务器向客户端发送一个FIN,告诉客户端应用程序关闭。

当客户端收到服务器端的FIN是,回复一个ACK给服务器端。其中ack的值等于FIN+SEQ

原文链接:https://blog.csdn/sanwe3333/article/details/110110006

八.网络知识扩展

1.网络信息检索

相关函数:

#include <netdb.h>
extern int h_errno;

struct hostent *sethostbyname(const char *name);//只适用于IPV4

struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);

name填域名或者IP地址;
域名解析,成功返回一个结构体指针,出错返回空;出错是打印错误:

 void herror(const char *s);

 const char *hstrerror(int err);

 struct hostent {
               char  *h_name;            /* official name of host */
               char **h_aliases;         /* alias list */
               int    h_addrtype;        /* host address type */
               int    h_length;          /* length of address */
               char **h_addr_list;       /* list of addresses */
           }
 #define h_addr h_addr_list[0] /* for backward compatibility *

h_addr_list:An  array of pointers to network addresses for the host (in net‐work byte order), terminated by a null pointer.(指向主机的多个网路字节序的网络地址列表)
void endhostent(void);//不用返回的信息结构体时,释放

2.网络属性设置

①setsockopt

#include <sys/types.h>
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

参数:

>level为指定控制套接字的层次:

1)SOL_SOCKET:通用套接字选项(应用层)

2)IPPRORO_TCP:TCP选项(传输层)

3)IPPROTO_IP:选项(网络层)

>optval:对于getsockopt(),指向返回选项值的缓冲。对于setsockopt(),指向包含新选项值的缓冲。
>optlen:对于getsockopt(),作为入口参数时,选项值的最大长度。作为出口参数时,选项值的实际长度。对于setsockopt(),现选项的长度。

选项名称        说明                  数据类型
========================================================================
            SOL_SOCKET
------------------------------------------------------------------------
SO_BROADCAST     允许发送广播数据            int
SO_DEBUG         允许调试                int
SO_DONTROUTE       不查找路由               int
SO_ERROR         获得套接字错误             int
SO_KEEPALIVE        保持连接                int
SO_LINGER         延迟关闭连接              struct linger
SO_OOBINLINE        带外数据放入正常数据流         int
SO_RCVBUF        接收缓冲区大小             int
SO_SNDBUF        发送缓冲区大小             int
SO_RCVLOWAT        接收缓冲区下限             int
SO_SNDLOWAT        发送缓冲区下限             int
SO_RCVTIMEO       接收超时                struct timeval
SO_SNDTIMEO       发送超时                struct timeval
SO_REUSERADDR        允许重用本地地址和端口         int
SO_TYPE           获得套接字类型             int
SO_BSDCOMPAT     与BSD系统兼容              int
========================================================================
            IPPROTO_IP
------------------------------------------------------------------------
IP_HDRINCL       在数据包中包含IP首部          int
IP_OPTINOS       IP首部选项               int
IP_TOS         服务类型
IP_TTL         生存时间                   int
========================================================================
            IPPRO_TCP
------------------------------------------------------------------------
TCP_MAXSEG       TCP最大数据段的大小           int
TCP_NODELAY       不使用Nagle算法                int
========================================================================

示例:

/*允许广播*/
int b_br=1;
setsockopt(fd,SOL_SOCKET,SO_BROADCAST,&b_br,sizeof(int));
/*设置接收超时*/
struct timeval tout;
tout.tv_sec=5;
tout.tv_usec=0;
setsockopt(fd,SOL_SOCKET,SO_RCVTIMEO,&tout,sizeof(struct timeval));

②getsockopt

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);

3.网络超时检查

在网络通信中,很多操作会使进程阻塞(recv,recvfrom,connect,accept),超时检测可以避免进程在没有数据时无限制的阻塞,当设定的时间到,进程从原操作返回继续运行。

①setsocktopt函数

②select函数

③设置定时器t,sigaction()捕捉SIGALRM

简单的几种网络超时检测_youge_C的博客-CSDN博客_网络超时检测

信号处理:signal和sigaction的区别_fly_air的博客-CSDN博客

动态检测网络是否断开或者异常:

①交互双方每隔一段时间相互发送一定数据,若超出设定次数均未检测到数据,则网络有问题

②设置套接字属性

/*函数定义:*/
void setKeepAlive (int sockfd, int attr_on, socklen_t idle_time, socklen_t interval, socklen_t cnt)
setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, (const char *) dattr_on, sizeof (attr_on)) ;
setsockopt (sockfd, SOL_TCP, TCP_KEEPIDLE, (const char *) idle_time, sizeof (idle_time))
setsockopt (sockfd, SOL_ICP, TCP_KEEPINTVL, (const char *) &interval, sizeof (interval)) :
setsockopt (sockfd, SOL_ICP, TCP_KEEPCNT, (const char *) &ent, sizeof (cnt)):
/*使用:*/
int keepAlive = 1;
//设定KeepAlive
int keepIdle = 5;
1/开始首次KeepAlive探测前的TCP空闭时间
int keepInterval = 5;
//两次KeepAlive探测间的时间间隔
int keepCount = 3;
//判定断开前的KeepAlive探测次数
setkeepAlive (newfd, keepAlive, keepIdle, keepInterval, keepCount):

4.广播

>广播:将数据包同时发送给局域网里的所有主机

>只有UDP才能广播

>广播地址:

-以192.168.1.0网段为例,最大的主机地址为192.168.1.255代表该网段的广播地址

-发送到广播地址的数据包被所有的主机接收

-255.255.255.255在所有网段中都代表广播地址

①广播的发送

>创建UDP套接字

>缺省的套接字不能广播数据包,需要设置套接字属性(setsockopt)

>接收方地址为广播地址,指定端口信息,发送数据包

②广播的接收

>创建UDP套接字

>绑定本机IP地址和端口(绑定的端口必须和发送方指定的端口相同)

>等待接受数据

5.组播

组播:一个人发送,只有加入某个多播组的主机才能接收到数据

组播的IP地址:224.0.0.1-239.255.255.254(中间除掉广播地址)

组播编程都是基于UDP编程

①组播的发送

>创建DUP套接字

>接收方地址为组播地址,指定端口信息

>发送数据包

②组播的接收

>创建UDP套接字

>加入多播组

struct ip_mreqn{
               struct in_addr imr_multiaddr; /* IP multicast group
                                                address */
               struct in_addr imr_address;   /* IP address of local
                                                interface */
           };
(man 7 ip)

struct ip_mreq mreq;
mreq.imr_multiaddr.s_addr=inet_addr(MUTICAST_IP);
mreq.imr_interface.s_addr=htonl(INADDR_ANY);
setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

>bind本机IP地址

>等待接受数据

6.UNIX域套接字

socket可以在创建时使用本地协议PF_UNIX(或PF_LOCAL)用于本地通信。

>socket(AF_LOCAL,SOCK_STREAM,0)

>socket(AF_LOCAL,SOCK_DG_RAM,0)

bind时填充的结构体是sockaddr_un

 struct sockaddr_un {
               sa_family_t sun_family;               /* AF_UNIX */
               char        sun_path[108];            /* pathname */
           };

sun_path:unix域套接字的文件路径名,必须事先不存在,给绝对路径;

bind后,根据TCP/UDP进行相应的编程;

进程间通信:
进程间的数据共享:管道、消息队列、共享内存、unix域套接宇
易用性:
消息队列>unix域套接字 ›管道,共享内存(经常要和信号量一起用)
效率:共享内存 >unix域套接字>管道>消息队列
常用:共享内存、unix域套接字
异步通信:信号
同步和互斥:(做资源保护)信号量

更多推荐

linux网络编程