Unix网络编程笔记

Unix网络编程笔记


书本:《Unix Network Programing》


  • Unix网络编程笔记
    • 第一章 基础
    • 第二章 TCP UDP
    • 第三章
      • 1、IPv4 Socket Address Structure(IPv4套接字地址结构)
      • 2、Generic Address Structure(通用套接字地址)
      • 3、IPv6 Socket Address Structure(IPv6套接字地址)
      • 4、Value-Result Arguments(值-结果参数)
      • 5、Byte Ordering代码
      • 6、inet_pton() inet_ntop()比较
    • 第四章 套接字函数
      • 1、socket函数
      • 2、connect函数
      • 3、bind函数
      • 4、listen函数
      • 5、accept函数
      • 6、fork()函数
      • 7、close()函数
    • 第五章
      • 1、echo程序的服务端和客户端的代码
      • 2、wait() waitpid()
      • 3、编写SIGCHLD信号处理函数
      • 4、防止慢系统调用accept()在捕获信号和处理信号时返回EINTR错误中断,对accept()函数调用做如下更改
      • 5、TCP服务器常见错误:accept返回前连接中止
      • 6、TCP服务器常见错误:服务器进程终止
      • 7、TCP服务器常见错误:服务器主机崩溃
      • 8、TCP服务器常见错误:服务器主机崩溃后重启
      • 9、TCP服务器常见错误:服务器主机关机
    • 第六章 I/O复用
      • 1、五种I/O模型
      • 2、select函数
      • 3、描述符就绪条件
      • 4、select函数使用描述符集(通常是一个整数数组)的每一位来对应一个描述符。
      • 5、select实践代码:见书
      • 6、shutdown函数
      • 6、使用shutdown而不使用close的情况
    • 第七章
      • 1、getsockopt getsockopt
      • 2、通用套接字选项
      • 3、IPV4套接字选项:
    • 第八章
      • 1、recvfrom sendto函数
      • 2、程序示例:
    • 第十一章
      • 1、gethostbyname()
      • 2、gethostbyaddr()
      • 3、getservbyname() getservbyport()
      • 4、getaddrinfo()
    • 第十八章 路由套接字
      • 1、路由套接字上支持3种类型的操作
      • 2、地址结构:
      • 3、sockfd = Socket(AF_ROUTE, SOCK_RAW, 0);
      • 4、在路由域中支持的唯一一种套接字是原始套接字。
    • 第十九章 密钥套接字
      • 1、密钥管理套接字支持3种类型操作:
      • 2、s=socket(PF_KEY,SOCK_RAW,PF_KEY_V2);
      • 3、支持的唯一套接字是原始套接字
    • 第二十六章:线程
      • 1、同一进程内的所有线程共享:
      • 2、每个线程有各自的:
      • 3、pthread_create()
      • 4、pthread_join()
      • 5、pthread_self()
      • 6、pthread_exit()
      • 7、互斥锁
    • 第二十八章:原始套接字
      • 1、原始套接口提供以下三种TCP及UDP套接口一般不提供的功能。
      • 2、原始套接字不存在端口号的概念
      • 3、基本用法
      • 4、原始套接字的输出遵循以下规则:
      • 5、内核把收到的IP数据报传递到原始套接字要遵循规则:
      • 6、ping原理
      • 8、traceroute原理

第一章 基础

OSI(open system interconnection)七层模型
物理层 数据链路层 网络层 传输层 会话层 表示层 应用层


第二章 TCP UDP

User Datagram Protocol(UDP):用户数据报协议

Transmission Control Protocol (TCP):传输控制协议

Created with Raphaël 2.1.2 TCP三次握手图示 client client server server SYN J SYN K, ACK J+1 ACK K+1
  • client发起连接使用到的函数:socket connect
  • server自己调用socket bind listen函数,并阻塞在accept函数等待连接
  • client接收到ACK J+1 connect返回
  • server接收到ACK K+1 accept返回

Created with Raphaël 2.1.2 TCP四次挥手图示 client client server server FIN M ACK M+1 FIN N ACK N+1
  • client由close主动关闭,向server发送FIN M
  • server由close主动关闭,向client发送FIN N

第三章

1、IPv4 Socket Address Structure(IPv4套接字地址结构)

 #includein.h>
    struct in_addr{
        in_addr_t s_addr;
    };

    struct sockaddr_in{
        uint8_t         sin_len; /*16*/
        sa_family_t     sin_family;/*AF_INET*/
        in_port_t       sin_port;
        struct in_addr  sin_addr;/*32位IPV4地址*/
        char            sin_zero[8];/*没用*/
    };
  • POSIX数据类型
  • uint8_t 无符号8位整数
  • sa_family_t 套接字地址结构的地址族

2、Generic Address Structure(通用套接字地址)

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

用于强制类型转换,例如:
bind(sockfd,(struct sockaddr *)&server_addr,sizeof(server_addr))

3、IPv6 Socket Address Structure(IPv6套接字地址)

struct in6_addr {
   uint8_t s6_addr[16]; /*128bit*/

#define SIN6_LEN / * required for compile-time tests * /
struct sockaddr_in6 {
   uint8_t sin6_len; 
   sa_family_t sin6_family; /*AF_INET6*/
   in_port_t sin6_port; 
   uint32_t sin6_flowinfo;  
   struct in6_addr sin6_addr; 
   uint32_t sin6_scope_id;
};
};

4、Value-Result Arguments(值-结果参数)

主要指的是函数参数传递的是一个套接字地址结构长度的指针,当进程被调用,该参数告诉内核地址结构的大小,就是一个值,防止内核写入地址结构数据越界,当函数返回,内核将它写的数据大小作为结果写入参数,告诉进程写了多少,就是一个结果。下面代码的&len就是一个 值-结果 参数

socklen_t len = sizeof(client_addr); 
connfd = accept(listenfd,(SA*)&client_addr,&len);

进程到内核传递套接字地址结构,无值-结果参数的函数 bind connect sendto
有值-结果参数的函数 accep recvfrom getsockname getpeername

5、Byte Ordering代码

int main(int argc, char ** argv)
 {
   union {
     short s;
     char c[sizeof(short)];
   } un;
   un.s = 0x0102;
   printf("%s: ", CPU_VENDOR_OS);
   if (sizeof(short) == 2) {
   if (un.c[0] == 1 && un.c[1] == 2)
        printf("big-endian\n");
   else if (un.c[0] == 2 && un.c[1] == 1)
        printf("little-endian\n");
   else
         printf("unknown\n");
   } else
         printf("sizeof(short) = %d\n", sizeof(short));
         exit(0);
}

6、inet_pton() inet_ntop()比较

#include
int inet_pton(int family, const char *strptr, void *addrptr);
const char* inet_ntop(int family, const void * addrptr, char *strptr, size_t);

比如:

inet_pton(AF_INET,IP,&server_addr.sin_addr);
inet_ntop(AF_INET,&client_addr.sin_addr,buffer,sizeof(buffer));

第四章 套接字函数

1、socket函数

#include 
int socket(int family, int type, int protocol);

若成功返回非负描述符,错误返回-1
比如:sockfd = socket(AF_INET,SOCK_STREAM,0);

参数 具体
family AF_INET(IPV4), AF_INET6(IPv6),AF_ROUTE,PF_KEY
type SOCK_STREAM, SOCK_DGRAM, SOCK_RAW
protocol IPPROTO_CP(TCP), IPPROTO_UDP(UDP), IPPROTO_SCTP

2、connect函数

#include 
int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);

若成功返回0,错误返回-1
比如:connect(sockfd,(SA*)&server_addr,sizeof(server_addr));connect失败之后,都必须关闭当前套接字,重新调用新的socket,例子中的sockfd如果没有用bind绑定IP和端口,内核自动确定源IP,并选择一个临时端口.

3、bind函数

#include 
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);

若成功返回0,错误返回-1
比如:bind(listenfd,(SA*)&server_addr,sizeof(server_addr));
如果不调用bind函数,调用connect或者listen函数,内核会随机分配端口,客户端没事,若是服务端,如果端口是随机的,客户端连接会比较困难,因为不知道应该连接那个端口。
常见的返回错误:EADDRINUSE(“Address already in use”)

4、listen函数

#include 
int listen(int sockfd, int backlog);

若成功返回0,错误返回-1
比如:listen(listenfd,100);

  • backlog为两个队列之和最大值:
  • 未完成连接队列,处于SYN_RECV状态;
  • 已完成连接队列,处于ESTABLISHED状态;

5、accept函数

#include 
int accept(int sockfd, struct sockaddr *cliaddr, socket_t *addrlen);

若成功返回0,错误返回-1
比如:

struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
connfd = accept(listenfd,(SA*)&client_addr,&len);

如果调用正确,返回内核自动生成的已连接socket描述符,错误返回-1
如果对于连接客户的身份不感兴趣,后面两个指针可以置空;
如果想查看用户身份,调用inet_ntop()函数将32位IPV4地址转换为点分十进制IPV4地址,调用ntohs()查看端口

const char* inet_ntop(int family, const void* addrptr, char *strptr, size_t len);

6、fork()函数

#include
pid_t fork(void);

该函数返回两次,在父进程中返回子进程的进程ID,在子进程中返回值为0;

例子:

 for(;;){
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        connfd = accept(listenfd,(SA*)&client_addr,&len);
        if((childpid=fork())==0){
            Close(listenfd);
            service(connfd);
            Close(connfd);
            exit(0);
        }
        Close(connfd);
}

图中的if语句,就是判定在子进程中,执行if后面的语句,执行完之后关闭客户套接字,退出,
如果没有返回0,那么就是父进程,直接关闭connfd。

为什么父进程关闭connfd,子进程还能够与客户端进行通信?系统中对于每个套接字都有一个引用计数,
当fork()函数返回后,connfd的引用计数变为2,父进程close(connfd)只是将引用计数变为1,而只有当引用计数变为0的时候,该套接字资源才会被释放。

7、close()函数

#include
int close(int sockfd);

若成功返回0,错误返回-1
调用该函数,只是将引用计数器-1,并一定会导致关闭与客户端的TCP连接,
若想关闭连接,调用shutdown()函数。

第五章

1、echo程序的服务端和客户端的代码

看书吧

2、wait() waitpid()

#include
pid_t wait(int *statloc);

成功返回进程ID,出错返回0-1
statloc 指针返回进程终止状态:正常终止、由信号杀死、作业控制停止
调用wait()的进程没有已终止的子进程,但有子进程在执行,wait()将阻塞至第一个子进程终止

pid_t waitpid(pid_t pid, int *statloc, int options);

成功返回进程ID,出错返回0-1
statloc指针和wait()函数用法一致
pid 指定等待进程ID,-1表示等待第一个
options常用WNOHANG,让内核不要阻塞等待进程的终止

3、编写SIGCHLD信号处理函数

Signal(SIGCHLD,sig_chld);//捕获SIGCHLD信号

void sig_chld(int signo){
    pid_t pid;
    int stat;
    while((pid=waitpid(-1,&stat,WNOHANG))>0)
        printf("child %d terminated\n",pid);
    return;
}

不能使用wait()函数,因为无法阻止wait()函数阻塞

4、防止慢系统调用accept()在捕获信号和处理信号时返回EINTR错误中断,对accept()函数调用做如下更改

struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
if((connfd = accept(listenfd,(SA*)&client_addr,&len))<0){
    if(errno==EINTR)
        continue;
    else
        err_sys("accept erro");
}

5、TCP服务器常见错误:accept返回前连接中止

服务器到达ESTABLISHED状态后,accept()函数还没返回,接收到客户端发来的RST信号,重新调用accept函数

6、TCP服务器常见错误:服务器进程终止

服务器行为

(a)被杀死的子进程的所有打开描述符被关闭,并且发送一个FIN给客户端。
(b)SIGCHLD信号发给该子进程的父进程,并得到正确处理。
(c)收到客户端发来的信息,返回RST

客户端行为

FIN到达后接收并返回ACK,由于阻塞与Fgets函数,故不知道服务端已经关闭服务,当再输入数据发给服务端,由于套接字已经关闭,故发送RST,当客户端发送完数据后,阻塞与readline,此时接收到FINreadline返回0,故提示错误信息“server terminated permaturely

如果RSTreadline调用之前到达(TCP信号没有队列,故不会先接收FIN),readline返回复位连接错误。

出现这种问题的关键所在是:

客户端不能第一时间处理服务端关闭主要因为它得处理两个源:套接字和用户输入,而且只能处理一个不能同时处理

写一个已经接收FIN的套接字不成问题,写一个已经接收RST的套接字会引发错误,引发SIGPIPE信号,终止进程

7、TCP服务器常见错误:服务器主机崩溃

服务器主机崩溃后,不会向客户端发送任何数据。根据TCP的重传机制,客户端会坚持一段时间向服务器重发数据,由于服务器主机崩溃,
对这些数据无法响应,因此客户端在坚持一段时间后会放弃重传,并且进程会返回一个ETIMEOUT错误。,如果是中间路由器判定服务器不可达,
则会返回EHOSTUNREACHENETUNREACH错误。

客户端要想检测出服务器主机崩溃可以采用两个办法:

(a)向服务器主动发送数据,直到返回ETIMEOUT或者EHOSTUNREACH或者ENETUNREACH错误
(b)如果不想主动发送数据也能检测出服务器主机崩溃,可以通过设置SO_KEEPALIVE套接字选项。

8、TCP服务器常见错误:服务器主机崩溃后重启

服务器主机崩溃后重启,它的TCP丢失了TCP崩溃前的所有连接信息,因此会对收到的客户端数据响应一个RST
如果客户端阻塞于read调用,会导致该调用返回ECONNRESET错误。
如果不想主动发送数据也能检测出服务器主机崩溃,可以通过设置SO_KEEPALIVE套接字选项。

9、TCP服务器常见错误:服务器主机关机

服务器主机如果被人为关机,UNIX系统的init进程会向所有进程发送SIGTERM信号,然后等待一段时间(520秒),
再向所有仍在运行的进程发送SIGKILL信号。从进程角度,当接收到SIGTERM信号后,会有一段时间来处理SIGTERM信号,并且终止自己,服务器与客户端连接进程终止后,就会发生上述服务器子进程终止的情况。
如果超过了这段时间,进程还在运行就会被SIGKILL信号强制终止。

第六章 I/O复用

1、五种I/O模型

阻塞式(blocking I/O)
非阻塞式(nonblocking I/O)
I/O复用(I/O multiplexing) (select and poll)
信号驱动I/O(signal-driven I/O)(SIGIO)
异步I/O(asynchronousI/O)(the POSIX aio_functions)

2、select函数

#include 
#include 
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);

返回:若有就绪的描述符则返回它的数目,若超时则返回0,出错返回-1

3、描述符就绪条件

满足下列四个条件时,一个套接字准备好读:

(1)该套接字接收缓冲区的数据字节数大于等于套接字接收缓冲区低水位标记的当前大小

(2)该连接的读半部关闭(也就是接收了FIN的TCP连接)

(3)该套接字是一个监听套接字并且已完成的连接数不为0

(4)其上有一个套接字错误待处理

满足下列四个条件时,一个套接字准备好写:

(1)该套接字发送缓冲区的数据字节数大于等于套接字发送缓冲区低水位标记的当前大小

(2)该连接的写半部关闭

(3)使用非阻塞式的connect的套接字已建立连接,或者connect已经以失败告终

(4)其上有一个套接字错误待处理

4、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);
int FD_ISSET(int fd, fd_set *fdset);

5、select实践代码:见书

6、shutdown函数

终止网络连接的通常方法是调用close函数。不过close有两个限制,可以使用shutdown函数来避免。

(1)close把描述符的引用计数减1,仅在计数变为0时才关闭套接字。使用shutdown不管引用计数就可以激发TCP的正常连接终止序列
(2)close终止读和写两个方向上的传送。既然TCP连接是全双工的,有时候我们需要通知对端我们已经完成了数据发送任务,但是可能对方还有信息要发送给我们。

#include 
int shutdown(int sockfd, int howto);

返回:若成功返回0,出错返回-1

该函数的行为依赖于howto参数的值:

SHUT_RD:关闭连接的读这一半——套接字不再有数据接收,且接收缓冲区的数据将被丢弃。
SHUT_WR:关闭连接的写这一半——对于TCP套接字,则称为半关闭。当前留在发送缓冲区的数据将被发送掉,接着发送终止序列。
SHUT_REDWR:等效于分别调用上述两个shutdown函数。

6、使用shutdown而不使用close的情况

在批量方式下,当标准输入完成后,我们用shutdown关闭套接字的写,而允许套接字读可能任在路上的应答数据,而不用close关闭套接字,避免数据丢失。

第七章

1、getsockopt getsockopt

#include
int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t *optlen);
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t *optlen);

2、通用套接字选项

第二个参数:SOL_SOCKET
示例代码:

int on;
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));//ping程序节选

SO_KEEPALIVE:保持存活
SO_LINGER:close立即返回,若缓冲区有数据,系统将数据发送给对端

3、IPV4套接字选项:

第二个参数:IPPROTO_IP
示例代码:

int on
setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on));

IP_HDRINCL:构造自己的IP首部
IP_TOS:允许我们为TCP,UDP,SCTP套接口设置IP头部的服务类型字段
IP_TTL:设置或获取系统用在某个套接口发送的单播分组的缺省TTL值

第八章

1、recvfrom sendto函数

#include
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct socket *from, socklen_t *addrlen);
ssize_t sendto(int sockfd, void *buff, size_t nbytes, int flags, struct socket *to, socklen_t addrlen);

成功返回读或者写的字节数,错误返回-1

UDP无连接的数据报协议,没有连接,两个函数用法类似read write accept connect 的综合

2、程序示例:

服务端:

sockfd = socket(AF_INET,SOCK_DGRAM,0);//SOCK_DGRAM数据报
struct sockaddr_in server_addr,client_addr;
bzero(&server_addr,sizeof(server_addr));
bzero(&client_addr,sizeof(client_addr));
socklen_t len=sizeof(client_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = htonl(IP);
bind(sockfd,(SA*)&server_addr,sizeof(server_addr));
recvfrom(sockfd,buff,BUFF_SIZE,&client_addr,&len);

客户端:

sockfd = socket(AF_INET,SOCK_DGRAM,0);//SOCK_DGRAM数据报
struct sockaddr_in server_addr;
bzero(&server_addr,sizeof(server_addr));
socklen_t len=sizeof(server_addr);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(IP);
sendto(sockfd,buff,BUFF_SIZE,&server_addr,len);

UDP的传输核心主要就在recvfrom和sendto两个函数上,它们是TCP程序中read write connect accept函数的综合,基本的图示如下:

Created with Raphaël 2.1.2 client client server server client(sendto) server(recvfrom) serve(sendto) client(recvfrom)

第十一章

1、gethostbyname()

#include
struct hostent *gethostbyname(const char *hostname);

struct hostent{
    char  *h_name;       //正式主机名
    char **aliases;      //别名
    int    h_addrtype;   //地址类型AF_INET
    int    h_length;     //长度 4
    char **h_addr_list;  //所有IPV4地址
};

2、gethostbyaddr()

#include
struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);
struct hostent *p = gethostbyaddr(&server_addr.sin_addr,sizeof(server_addr.sin_addr),AF_INET);

3、getservbyname() getservbyport()

#include
struct servent *getservbyname(const char *servname, const char *protoname);
struct servent *getservbyport(int port, const char *protoname);

struct servent{
    char  *s_name;
    char **s_aliases;
    int    s_port;//port number,net work byte order
    char  *s_proto; 
};
//使用
struct servent *p=getservbyname("ftp","tcp");
struct servent *p=getservbyport(htons(21),"tcp");

4、getaddrinfo()

#include 
int getaddrinfo (const char * hostname, const char * service, const struct addrinfo * hints, struct addrinfo ** result);

struct addrinfo {
int ai_flags; /* AI_PASSIVE, AI_CANONNAME */
int ai_family; /* AF_xxx */
int ai_socktype; /* SOCK_xxx */
int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
socklen_t ai_addrlen; /* length of ai_addr */
char *ai_canonname;/* ptr to canonical name for host */
struct sockaddr * ai_addr;/* ptr to socket address structure */
struct addrinfo * ai_next;/* ptr to next structure in linked list */
};

第十八章 路由套接字

1、路由套接字上支持3种类型的操作

1). 进程能通过写路由套接字向内核发消息。

2). 进程能通过路由套接字从内核读消息。

3). 进程可以用sysctl函数得到路由表或列出所有已配置的接口。

2、地址结构:

struct sockaddr_dl {
  uint8_t      sdl_len;
  sa_family_t  sdl_family; /* AF_LINK */ 
  uint16_t     sdl_index; /* system assigned index, if > 0 */ 
  uint8_t      sdl_type; /* IFT_ETHER, etc. from  */ 
  uint8_t      sdl_nlen; /* name length, starting in sdl_data[0] */           
  uint8_t      sdl_alen; /* link-layer address length */ 
  uint8_t      sdl_slen; /* link-layer selector length */ 
  char sdl_data[12]; /* minimum work area, can be larger; 
                      contains i/f name and link-layer address */ 
};

3、sockfd = Socket(AF_ROUTE, SOCK_RAW, 0);

4、在路由域中支持的唯一一种套接字是原始套接字。

第十九章 密钥套接字

1、密钥管理套接字支持3种类型操作:

  • 向内核和打开的密钥管理套接字发送消息。可以从密钥管理守护进程请求密钥
  • 可以从密钥管理套接字读入,可以请求某个密钥管理守护进程为依照策略需受保护的一个新的TCP会话安装一个SA.
  • 发送一个dump消息

2、s=socket(PF_KEY,SOCK_RAW,PF_KEY_V2);

3、支持的唯一套接字是原始套接字

第二十六章:线程

1、同一进程内的所有线程共享:

1) 全局变量
2) 进程指令
3) 大多数数据
4) 打开的文件(即描述符)
5) 信号处理函数和信号处置
6) 当前工作目录
7)用户ID和组ID

2、每个线程有各自的:

1) 线程ID
2) 寄存器集合,包括程序计数器和栈指针
3) 栈(用于存放局部变量和返回地址)
4) Errno
5) 信号掩码
6)优先级

3、pthread_create()

#include 
int pthread_create(pthread_t *tid, const pthread_attr_t *attr, void *(*func(void*)), void *arg);

比如:

pthread_t tid;
pthread_create(&tid,0,service_thread,&fd);

void* service_thread(void* p){
    int fd = *(int*)p;
    printf("pthread = %d\n",fd);
}

4、pthread_join()

int pthread_join(pthread_t tid, void ** status);

类似于waitpid()

5、pthread_self()

pthread_t pthread_self(void);

6、pthread_exit()

oid pthread_exit (void * status);

7、互斥锁

第二十八章:原始套接字

基于字节流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)不可以访问传输层协议,只是对应用层的报文进行操作,
传输层的数据报格式都是由系统提供的协议栈实现,用户只需要填充相应的应用层报文,由系统完成底层报文首部的填充并发送。
原始套接字(SOCK_RAW)可以访问位于基层的传输层协议,原始套接字没有端口号。

1、原始套接口提供以下三种TCP及UDP套接口一般不提供的功能。

1)有了原始套接字,进程可以读写ICMPv4,IGMPv4,ICMPv6分组。例如:Ping程序,就使用原始套接字发送ICMP回射请求,并接受ICMP回射应答。

2)有了原始套接字,进程可以读写内核不处理其协议字段的IPv4数据报。

3)有了原始套接字,进程还可以利用IP_HDRINCL套接字选项可以构造自己的IPv4首部。

2、原始套接字不存在端口号的概念

3、基本用法

sockfd = socket(AF_INET, SOCK_RAW, protocol);

protocol参数值为形如IPPROTO_xxx的常值,由

4、原始套接字的输出遵循以下规则:

1).普通输出通过调用sendto或sendmsg并指定目的IP地址来完成。如果套接字已经连接,那么也可以调用write,writev或send

2).如果IP_HDRINCL选项未开启,那么由进程让内核发送的数据的起始地址指的IP首部之后的第一个字节。因为内核将构造IP首部并把它置于来自进程的数据之前,内核把IPv4首部的协议字段设置成来自socket调用的第三个参数。

3). 如果IP_HDRINCL铜戒指选项已开启,那么由进程让内核写的数据起始地址指IP首部的第一个字节。

4). 内核会对超出外出接口MTU的原始分组执行分片。

5、内核把收到的IP数据报传递到原始套接字要遵循规则:

1). 接收到的TCP分组和UDP分组决不会传递给任何原始套接字

2). 大多数ICMP分组在内核处理完其中的ICMP消息后传递到给原始套接字

3). 所有IGMP分组在内核完成处理其中的IGMP消息之后传递到原始套接口字

4). 内核不认识其协议字段的IP数据报传递到原始套接字

5). 如果某个数据报以片段形式到达,则该分组将在所有片段到达并重组后才传给原始套接字。

6、ping原理

当源主机向目标主机发送了 ICMP 回显请求数据报后,它期待着目标主机的回答。 目标主机在收到一个 ICMP回显请求数据报后,它会交换源、目的主机的地址,然后将收到的ICMP回显请求数据报中的数据部分原封不动地封装在自己的 ICMP回显应答数据报中,然后发回给发送ICMP回显请求的一方。如果校验正确,发送者便认为目标主机的回显服务正常,也即物理连接畅通。

ping命令只使用众多 ICMP 报文中的两种:”请求(ICMP_ECHO)”和”回应(ICMP_ECHOREPLY)”

8、traceroute原理

源向目的主机发送一连串的IP数据报,数据报中封装的是无法交付的UDP数据报。第一个数据报P1的生存时间TTL为1,当P1达到路径上的第一个路由器R1时,R1先收下他紧接着将TTL的值减1。此时TTL为0了,于是R1丢弃P1,并向源主机发送一个ICMP时间超过差错报告报文(“time excceded in transit”)

源主机接着发送第二个数据报P2,并将TTL设为2。P2先到达路由器R1.R1收下P2后将P2的TTL减一后再转发给R2,此时R2收到P2,先收起来然后将TTL减一变为0于是丢弃P2,并向源主机发送一个ICMP时间超过差错报告报文。这样一直继续下去。当最后一个报文正好到目的主机时。TTL为1,主机不转发数据报也不讲TTL减1,但因数据报中存放的是UDP无法交付。因此发送一个ICMP终点不可达差错报告报文(“port unreachable”)。

你可能感兴趣的:(Unix网络编程)