linux-----网络编程

 

虚拟专用网络:virtual private network,VPN

安全shell:secure shell,SSH

 

1. 计算机网络基础

  1.1 模型

(1)OSI模型

  linux-----网络编程

 (2)TCP/IP模型

  linux-----网络编程

 

  网络接口层:运行网络接口层协议,进行IP数据报接收、发送

  网络IP层:提供无连接的网络层服务,进行路由选择、流量控制、拥塞控制

  传输层:TCP、UDP协议,建立端到端的连接

  应用层:数据处理,进程间通信

 

 

  1.2 有连接、无连接服务

  面向连接:如TCP,transmission control protocol    SCTP,stream control transmission protocol

    建立连接、数据传输、释放连接

    每一个数据分组中都带有该分组数据的源IP和目的IP地址

    面向连接的服务是可靠的

    协议复杂

  无连接的: 如UDP,user datagram protocol

       不需要建立、释放连接

    可靠性差, 尽最大努力交付

    协议简单、通信效率高

     

(3)其他常见协议

 

 

 

 

2.  UDP 用户数据报协议

  往UDP套接字写入消息,消息封装成UDP数据报,再封装成IP数据报,然后发送。

  不保证最终能到达、不保证达到的先后顺序、不保证每个数据报只达到一次

  到达后校验、检测有错误,或中途丢失,源端也不重传

  每个UDP数据报有一个长度(TCP是字节流协议,没有记录边界)

  2.1  用户数据报格式

  首部字段、数据字段  

 (1)首部字段

  linux-----网络编程

  长度:首部长度和数据部分长度

  校验和:保证数据安全

 

  伪首部:12字节,在UDP用户数据报前,用于计算校验和

  linux-----网络编程

 

  源IP:4字节  目的IP:4字节

  0:1字节

  协议:1字节,UDP协议号为17

  数据报长度:2字节 

 

 

 

3.  TCP

  面向连接

  可靠的全双工信道

(1)TCP报文段首部

  linux-----网络编程

 

  序号:本报文段发送的数据中第一个字节的序号(例,报文段A序号为1000,报文段长度100字节,则下一个报文段的序号为1100)

  确认号:期望收到对方的下一个报文段的 数据的第一个字节的序号(例,已经正确收到了序号为2000,长度100的报文段,则2000~2099都已收到,期望下一个是2100)

         4字节,能标记4GB的数据

  数据偏移: 4bit,而数据偏移的单位是4字节,所以能表示最大64字节偏移量

  保留:值为0

  6个bit位:URG:紧急bit,为1表示报文段中有紧急数据,应尽快传送

        ACK:确认bit,建立连接时使用

        PSH:推送bit,为1时发送端会立即将该报文段发送出去,接收端在接收到推送bit为1的报文段时,会尽快送接收缓存中取出来交给应用进程

        RST:复位bit,为1表明TCP连接中出现严重差错,必须释放连接重新建立连接

        SYN:同步bit,建立连接时用来同步序号

        FIN:终止bit,用来释放连接。为1时,表示此报文段发送的数据已经发送完毕,并要求释放连接

  窗口:控制对方发送的数据量,单位字节。用于流量控制、拥塞控制

  校验和:检验首部和数据部分。计算时要在TCP报文段前加12字节的伪首部(UDP协议号17,TCP协议号6)    

  选项:见(5)

  填充:0     

 

(2)窗口

    发送窗口:数据通信过程中发送端一次可以发送数据的最大字节数

    通信过程中,接收端可根据情况动态调整发送端窗口的大小  

  通知窗口:接收端根据其接收TCP报文段的能力而设定的窗口大小

  拥塞窗口:发送端根据网络拥塞情况而设定的窗口的大小

 

  一般发送端发送窗口大小取通知窗口和拥塞窗口中的较小者  

 

 (3)TCP的三次握手

  假设一个客户端A和服务器B

  

  服务器B被动打开,为接收用户进程请求做准备(socke、bind、listen)

  客户端发起主动打开(connect),客户端等的TCP发送连接请求报文段,报文段中首部 SYN 为1,序号为J(后面传送数据时第一个字节的序号就是J+1)

  服务器若同意建立连接,发送一个确认报文段,ACK为1,确认号为J+1,SYN为1,自己选择一个序号K,即序号设为K

  客户端收到服务器的确认报文段,向服务器发送报文段,序号为K+1。连接建立

   linux-----网络编程

 

 

 

(4)TCP四次挥手

  客户端主动关闭(close),发出连接释放请求报文段。 FIN为1,ACK为0,序号设为收到数据最后字节序号加1, SEQ设为M

  服务器收到FIN执行被动关闭,发出确认报文。ACK=1,确认号ack设为M+1(表示已收到客户端的链接释放请求报文,服务器停止接收从客户端发来的数据)

  服务器发送连接释放报文,FIN为1,SEQ为N,ACK为1,ack为M+1

  客户端收到服务器的链接释放报文,发送确认报文。ACK为1,ack为N+1   

  linux-----网络编程

 

 

(5)TCP选项

  

 

 

 

 

--------------------------------------套接字-----------------------------------

1.  套接字结构

  IPv4的:

 1 //IPv4套接字结构
 2 struct in_addr {
 3     in_addr_t s_addr;   //32位 IPv4地址,网络字节序
 4 };
 5 
 6 struct sockaddr_in {
 7     sa_family_t       sin_family; //16位地址族  AF_INET
 8     in_port_t         sin_port;   //16位 TCP或UDP的端口号,网络字节序
 9     struct in_addr    sin_addr;   //32位
10     char              sin_zero[8];//填充'0'
11 };

 

  IPv6的:

 1 struct in6_addr {
 2     uint8_t s6_addr[16];  //IPv6地址
 3 };
 4 struct sockaddr_in6 {
 5     sa_family_t      sin6_family;   //AF_INET6
 6     in_port_t        sin6_port;
 7     uint32_t         sin6_flowinfo;
 8     struct in6_addr  sin6_addr;
 9     uint32_t         sin6_scope_id;
10 }; 

 

  sockaddr_in或sockaddr_in都被转换成通用的结构

  通用的结构:同struct sockaddr_in,也是16字节

struct sockaddr {
    sa_family_t  sa_family;   //
    char         sa_data[14]; //
};

 

  新的套接字地址结构:<netinet/in.h>

1 struct sockaddr_storage {
2     uint8_t    ss_len; //这个结构体的大小
3     sa_family_t  ss_family;   //地址族
4     ....
5     
6 };

 

2. 字节序

  两个进程通信时,先将数据转换成网络字节序,接受到数据后,再转换成本地主机字节序

  linux-----网络编程

 

  hton表示主机字节序转网络字节序

  ntoh表示网络字节序转本地主机字节序

 

 3. 地址形式转换

  linux-----网络编程

(1)

  inet_aton:"192.168.0.1"等类似形式字符串转换为网络字节序的IPv4地址存到struct in_addr里

               有效返回1;无效返回0

  inet_ntoa:网络字节序IPv4地址(struct in_addr里面的)转换成字符串形式

       inet_addr:字符串形式IP地址直接转换成网络字节序的地址并返回该数值  

 (2)适合IPv4和IPv6的

  linux-----网络编程

 

     linux-----网络编程

 

   inet_pton:af是AF_INET或AF_INET6

  可用来转换IPv4、IPv6地址

  使用:

1 struct sockaddr_in addr;
2 inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));
3 
4 struct sockaddr_in6 addr6;
5 inet_ntop(AF_INET6,&addr6.sin6_addr,str,sizeof(str));

 

 

 

4. 获取主机信息

  /etc/hosts    /etc/services

(1)主机信息结构体

1 struct hostent {
2     char *h_name;       //主机名
3     char **h_aliases;   //主机别名
4     int  h_addrtype;    //地址类型
5     int  h_length;      //地址长度,单位字节 IPv4是4字节,IPv6是16字节
6     char **h_addr_list; //地址
7     ...
8 };

(2)

  linux-----网络编程

  linux-----网络编程

   得到一个struct hostent指针,指向一块静态内存,每次调用这个函数该缓冲区都会被覆盖

   endhostent会关闭/etc/hosts文件

 

5. 查询服务与对应的端口号

  linux-----网络编程

 

  

1 struct servent {
2     char *s_name;       //服务名
3     char **s_aliases;   //服务别名
4     int  s_port;        //端口号
5     char *s_proto;      //协议名字
6     ...
7 };

 

 

 

6. 域名与IP

  linux-----网络编程

 

  linux-----网络编程

 

   linux-----网络编程

 1 struct adddrinfo {
 2     int             ai_flags;   
 3     int             ai_family;
 4     int             ai_socktype;
 5     int             ai_protocol;
 6     socklen_t       ai_addrlen;
 7     struct sockaddr *ai_addr;
 8     char            *ai_canonname;
 9     struct addrinfo *ai_next;
10 };

 

 

 

 

----------------------------------------------------------------套接字编程-----------------------------------------------------------------------------

  TCP示意图:

  linux-----网络编程

 

 

1.  创建、销毁套接字描述符

  linux-----网络编程

 

  domain: 

    AF_INET:IPv4协议

    AF_INET6:IPv6协议

    AF_UNIX:非网络环境的进程通信,Unix域协议

  type:

    SOCK_STREAM:有序,面向连接字节流。默认协议TCP

    SOCK_DGRAM:长度固定,无连接报文。 默认协议UDP

    SOCK_SEQPACKET:长度固定,面向连接的报文。默认协议SCTP

    SOCK_RAM:原始套接字。绕过协议直接访问网络层。   

 

2.  套接字绑定地址信息

  linux-----网络编程

 

  服务器套接字绑定IP地址、端口号等

  addr:实际使用struct sockaddr_in或struct sockaddr_in6(强制转换为struct sockaddr)

    IP地址问题:如果只想本机客户端访问本机的服务器,地址选择 "127.0.0.1"  先用aton、pton转换,再转成网络字节序htonl

          如果只想局域网内访问,可使用内网地址如 "192.168.1.12"

          如果想被公网访问,则使用 "0.0.0.0"或宏 INADDR_ANY、IN6ADDR_ANY

    端口:若参数取0,则由内核为套接字选择临时端口号 可通过getsockname获取

 

   获取本机套接字上关联的地址信息:

  linux-----网络编程

 

   

  套接字已和对端连接,通过getpeername获取对方的地址信息:

  linux-----网络编程

 

 

     

3. 开始监听

  TCP服务器调用,作用:

    ①socket创建一个套接字时,它默认是一个主动的套接字(一个将发起connect的客户套接字),调用listen后转换成被动套接字,指示内核应接收指向该套接字的连接请求。套接字从CLOSED状态

  变为LISTEN状态。

    ②listen函数的第二个参数规定了内核应为该套接字排队的最大连接数

  内核为任一给定的监听套接字维护两个队列:

    ①未完成连接队列:每个这样的SYN分节对应一项,已由客户发出并到达服务器,服务器正在等待完成TCP三路握手过程,这些套接字处于SYN_RCVD状态

    ②已完成连接队列:每个已完成三路握手的客户对应一项,这些套接字处于ESTABLISHED状态

  linux-----网络编程

 

 

4. 开始接受请求

  开始监听之和,调用accept开始一个接受请求,已完成队列的对头将返回给进程。如果已完成队列为空,进程睡眠(当套接字设为默认的阻塞方式时)

  linux-----网络编程

 

  成功返回描述符;失败-1

  addr:返回已连接的对端进程(客户端的)协议地址   

  accept成功时,返回要给内核创建的新描述符,与相应的客户TCP连接。监听套接字在服务器的整个运行期间有效,而已连接套接字在完成对应客户的服务后关闭。

   

 5. 客户端发起连接

  linux-----网络编程

 

  addr:里面有服务器的IP和端口号

  对于TCP套接字,客户端调用connect将发起TCP的三路握手过程,连接建立或出错才返回。 

 

6. 数据发送和接收

(1)数据发送

  linux-----网络编程

 

   sendto常用语无连接通信,如UDP

  sendmsg:可用于多重缓冲区发送  

1 struct msghdr {
2     void      *msg_name;      //可选地址
3     socklen_t msg_namelen;    //地址大小
4     struct    iovec *msg_iov; //I/O缓冲区数组
5     size_t    msg_iovlen;     //数组元素个数
6     void      *msg_control;   //辅助数据
7     size_t    msg_controllen; //
8     int       msg_flags;
9 };

(2)接收数据

  linux-----网络编程

 

   recvfrom常用于无连接通信,如UDP,可用来获取发送方地址端口等信息

7. 简单示例

  服务器:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 
 9 int main()
10 {
11     //1.创建socket
12     //IPv4、TCP
13     int listenfd = socket(AF_INET,SOCK_STREAM,0);
14     if(listenfd == -1) {
15         printf("socket error\n");
16         exit(1);
17     }
18     
19     //2.初始化服务器地址、端口
20     struct sockaddr_in bindaddr;
21     bindaddr.sin_family = AF_INET;
22     bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //IPv4地址
23     /*
24     char ipaddr[16] = "192.168.0.110";
25     bindaddr.sin_addr.s_addr = inet_addr(ipaddr);
26     */
27     bindaddr.sin_port = htons(4000);               //指定端口号
28     if(bind(listenfd,(struct sockaddr *)&bindaddr,sizeof(bindaddr)) == -1) {
29         printf("bind error\n");
30         close(listenfd);
31         exit(1);
32     }
33     //3.启动监听
34     if(listen(listenfd,2)==-1) {
35         printf("listen error\n");
36         exit(1);
37     }
38     printf("listening...\n");
39     //4.接收连接请求
40     while(1) {
41         int clientfd;
42         struct sockaddr_in clientaddr;
43         socklen_t addrlen = sizeof(struct sockaddr_in);
44         clientfd = accept(listenfd,(struct sockaddr *)&clientaddr,&addrlen);
45         if(clientfd == -1) {
46             continue;
47         }
48         char recvBuf[40] = {0};
49         int ret = recv(clientfd,recvBuf,20,0);
50         if(ret>0) {
51             //打印收到的信息
52             printf("Get:%s\n",recvBuf);
53             //给客户端回信息,表示我已收到
54             strcat(recvBuf,".From server");
55             ret = send(clientfd,recvBuf,strlen(recvBuf),0);
56         }
57         /*
58         pid_t pid;
59         if((pid=fork())==0) {
60             //子进程处理连接
61             close(listenfd);
62             char recvBuf[40] = {0};
63             接收发送数据
64             close(clientfd);
65             exit(0);
66         }
67         */
68         close(clientfd);
69         
70     }
71     close(listenfd);
72     return 0; 
73 }

 

  客户端: 

 

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 #include <arpa/inet.h>
 9 
10 int main()
11 {
12     //1.创建socket
13     //IPv4、TCP
14     int connfd = socket(AF_INET,SOCK_STREAM,0);
15     if(connfd == -1) {
16         printf("socket error\n");
17         exit(1);
18     }
19     //2.向服务器发起连接
20     struct sockaddr_in serveraddr;
21     serveraddr.sin_family = AF_INET;
22     serveraddr.sin_addr.s_addr = inet_addr("192.168.0.110");
23     serveraddr.sin_port = htons(4000);
24     if(connect(connfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))==-1) {
25         printf("connect error\n");
26         close(connfd);
27         exit(1);
28     }
29     //3.向服务器发送数据
30     char sendBuf[40] = "Hello,this is client";
31     int ret = send(connfd,sendBuf,strlen(sendBuf),0);
32     if (ret != strlen(sendBuf)) {
33         printf("send error\n");
34         close(connfd);
35         exit(1);
36     }
37     //4.从服务器接收数据,接收完后就退出
38     memset(sendBuf,0,sizeof(sendBuf));
39     ret = recv(connfd,sendBuf,40,0);
40     if(ret > 0) {
41         printf("Recv data from server:%s\n",sendBuf);
42     }
43     close(connfd);
44     return 0; 
45 }

 

 

 ----------------------------------------套接字编程-----------------------------------------------------

  UDP示意图:

  linux-----网络编程

 

   

  服务器没有listen、accept,客户端少了bind和connect

 

1.  如果给客户端UDP套接字调用connect

  对于调用了connect的已连接UDP套接字:

    ①不能给输出操作指定目的IP和端口,则只能用send、write或将sendto的相关参数设为NULL。写到已连接UDP套接字上的任何内容都将自动发送到connect指定的IP端口上去

    ②可用read、recv、recvmsg获取信息,没必要用recvfrom(假设客户套接字A和服务器套接字B建立了UDP连接,则从客户套接字C发送到B的数据报不会被B接收,现在的B只能和已建立连接的

    A交换数据报)(是只能与A所在的IP地址交换数据报)

    ③已连接UDP套接字引发的异步错误会返回给它们所在的进程,未连接UDP套接字不会接收任何异步错误

2. 简单无连接UDP示例

  服务器:

 1 //UDP服务器
 2 #include <stdio.h>
 3 #include <stdlib.h>
 4 #include <string.h>
 5 #include <unistd.h>
 6 #include <sys/types.h>
 7 #include <sys/socket.h>
 8 #include <netinet/in.h>
 9 #include <arpa/inet.h>
10 
11 int main()
12 {
13     //1.创建套接字
14     int listenfd = socket(AF_INET,SOCK_DGRAM,0);
15     if(listenfd == -1) {
16         printf("socket error\n");
17         exit(1);
18     }
19     //2.初始化服务器地址和端口
20     struct sockaddr_in bindaddr;
21     bindaddr.sin_family = AF_INET;
22     bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //IPv4地址
23     bindaddr.sin_port = htons(4000);    
24     if(bind(listenfd,(struct sockaddr *)&bindaddr,sizeof(bindaddr)) == -1) {
25         printf("bind error\n");
26         close(listenfd);
27         exit(1);
28     }
29     //3.处理请求
30     struct sockaddr_in clientaddr;
31     socklen_t addrlen = sizeof(struct sockaddr_in);
32     char recvBuf[40] = {0};
33     while(1) {
34         memset(recvBuf,0,sizeof(recvBuf));
35         int n = recvfrom(listenfd,recvBuf,40,0,(struct sockaddr *)&clientaddr,&addrlen);
36         if(n>0) {
37             char clientip[16] = {0};
38             inet_ntop(AF_INET,&clientaddr.sin_addr,clientip,16);
39             printf("Get data from:%s:%d\n",clientip,ntohs(clientaddr.sin_port));
40             printf("Content is:%s\n",recvBuf);
41             strcat(recvBuf,".From server.");
42             n = sendto(listenfd,recvBuf,strlen(recvBuf),0,(struct sockaddr *)&clientaddr,addrlen);
43         }
44     }
45     close(listenfd);
46     return 0;
47 }

 

  客户端:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <sys/types.h>
 6 #include <sys/socket.h>
 7 #include <netinet/in.h>
 8 #include <arpa/inet.h>
 9 
10 int main()
11 {
12     //1.创建socket
13     //IPv4、UDP
14     int connfd = socket(AF_INET,SOCK_DGRAM,0);
15     if(connfd == -1) {
16         printf("socket error\n");
17         exit(1);
18     }
19     //2.向服务器发送数据
20     char sendBuf[40] = "Hello,this is client";
21     struct sockaddr_in serveraddr;
22     serveraddr.sin_family = AF_INET;
23     serveraddr.sin_addr.s_addr = inet_addr("192.168.0.110");
24     serveraddr.sin_port = htons(4000);
25     int n = sendto(connfd,sendBuf,strlen(sendBuf),0,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
26     if (n != strlen(sendBuf)) {
27         printf("send error\n");
28         close(connfd);
29         exit(1);
30     }
31     //从服务器接收数据
32     struct sockaddr_in serveraddr2;
33     socklen_t addrlen = sizeof(struct sockaddr_in);
34     memset(sendBuf,0,sizeof(sendBuf));
35     n = recvfrom(connfd,sendBuf,40,0,(struct sockaddr *)&serveraddr2,&addrlen);
36     if(n > 0) {
37         printf("Recv data from server:%s\n",sendBuf);
38     }
39     close(connfd);
40     return 0;
41 }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   

  

上一篇:day1


下一篇:java计算两个日期相差的天数