unix网络编程笔记(一)

第三章笔记

1. 套接字地址:

1.1 分类:

套接字地址分两种,一种是通用套接字地址类型struct sockaddr,它只是为了在套接字函数中可以传入任意套接字地址而定义的结构体,功能类似于void*,而出现定义套接字函数时还没有出现void*,所以才定义了该结构体。sockaddr 使用unsigned short int sa_family 来表明具体地址类型。另一种是具体协议的套接字地址类型,如果需要具体的套接字地址类型,需要使用sockaddr_in(IPv4)sockaddr_in6(IPv6)类型。

1.2 通用套接字地址结构sockaddr

typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON(sa_prefix) \
   sa_family_t sa_prefix##family
#define __SOCKADDR_COMMON_SIZE  (sizeof (unsigned short int))
//定义位置:/usr/include/bits/sockaddr.h

struct sockaddr
{
    __SOCKADDR_COMMON(sa);
    char sa_data[14];
};   //定义位置:写代码需#include的头文件:/usr/include/sys/socket.h -> 实际真正定义sockaddr的头文件:/usr/include/bits/socket.h

所以sockaddr的实际定义为:

struct sockaddr
{
    unsigned short int sa_family;
    unsigned char  sa_data[14];
}

(1)其中sa_family表示协议族类型,具体协议族值可以通过man socket查看第一个参数的值,该值就表示协议族类型。

(2)当作为一个参数传递进任何套接字(sockaddr)函数时,套接字地址结构总是以引用形式(也就是以指定该结构的指针)来传递。比如int bind(int,struct sockaddr*,socklen_t); 这就要求对这些函数的任何调用都必须要将指向特定协议的套接字地址结构的指针进行类型强制转换,变成指向某个通用套接字地址结构的指针。 比如:struct sockaddr_in serv; bind(sockfd,(struct sockaddr*) &serv,sizeof(serv));

(3)除了要传递套接字的地址,该结构的长度也要作为一个参数来传递。

当套接字是从进程到内核传递时,结构长度只需传一个整数值即可。这样的函数有3个:bind connect sendto。需要传递长度的原因是:不同的套接字的长度是不同的。比如sockaddr_in和sockaddr_in5的长度就不同。只有给内核传递了长度,内核才知道到底需从进程复制多少数据进来。

当套接字是从内核传递到进程时,结构长度需要传递一个指针。这样的函数有4个:accept recvfrom getsockname getpeername。需要传递长度指针的原因是:

当函数被调用时,结构大小是一个值,它告诉内核该结构的大小,这样内核在写结构时不至于越界;当函数返回时,结构大小又是一个结构,它告诉进程内核在该结构中究竟存储了多少信息。如果套接字地址结构是固定长度的,那么从内核返回的值总是那个固定长度,如sockaddr_in。如果是可变长度的套接字地址结构,返回值可能小于该结构的长度,如sockaddr_un(unix域套接字地址)

1.3 IPV4套接字地址结构sockaddr_in

typedef uint16_t in_port_t
typedef uint32_t in_addr_t;
struct in_addr
{
   in_addr_t s_addr;
};

struct sockaddr_in
{
     __SOCKADDR_COMMON (sin_);
     in_port_t sin_port;             /* Port number.  */
     struct in_addr sin_addr;        /* Internet address.  */

     /* Pad to size of `struct sockaddr‘.  */
     unsigned char sin_zero[sizeof (struct sockaddr) -
     __SOCKADDR_COMMON_SIZE -
     sizeof (in_port_t) -
     sizeof (struct in_addr)];
};   //定义位置:/usr/include/netinet/in.h

所以sockaddr_in的实际定义为:

struct sockaddr_in
{
    unsigned short int sin_family;  //协议族类型
    uint16_t sin_port;              //端口号(可以是TCP或UDP端口号)
    uint32_t sin_addr;              //IPv4地址
    unsigned char sin_zero[sizeof(struct sockaddr) - sizeof(unsigned short int) - sizeof(uint16_t) - sizeof(uint32_t)];  //也就是unsigned char sin_zero[8];
}

(1)对于IPV4来说,sin_family的协议族类型应该是AF_INET

(2) 按照惯例,对套接字结构体赋值前,我们应该总是把整个结构置0,而不是单单把sin_zero字段置0

(3) IPv4的IP地址为32位

4. IPv6套接字地址结构sockaddr_in6

typedef uint16_t in_port_t
   struct sockaddr_in6
   {
     __SOCKADDR_COMMON (sin6_);
     in_port_t sin6_port;    /* Transport layer port # */
     uint32_t sin6_flowinfo; /* IPv6 flow information */
     struct in6_addr sin6_addr;  /* IPv6 address */
     uint32_t sin6_scope_id; /* IPv6 scope-id */
   };  //定义位置:/usr/include/netinet/in.h

IPv6的协议族类型是AF_INET6

IPv6的IP地址为128位

1.5 IPv4和IPv6的套接字地址结构图

1.6 新的通用套接字地址结构

sockaddr_storage,因为现在没用到过这个结构,先pass

2. 字节序

(1)小端:存储具有多字节的类型数据时(比如16位整数),将低序字节存储在指向该数据的起始地址处。

(2)大端:存储具有多字节的类型数据时(比如16位整数),将高序字节存储在指向该数据的起始地址处。

(3)主机字节序:我们把某个给定系统所有的字节序成为主机字节序。

(4)代码:确定主机字节序的程序为:intro/byteorder.c

(5)网际协议使用大端字节序来传递数据。

(6)尽管可以在数据传输时由内核将套接字地址的ip和端口号由主机字节序转换成网络字节序,但由于历史的原因和POSIX规范的规定,套接字地址结构中的某些字段必须按照网络字节序进行维护。所以当需要把套接字地址结构传递给内核时,需要首先把IP地址和端口号由主机字节序转换成网络字节序。从内核获取的套接字类型(比如accept)也是网络字节序,如果要打印,首先先转换成主机字节序

(7)接口函数:

IP地址主机字节序转网络字节序的函数:htonl 网络字节序转主机字节序的函数:ntohl

端口号主机字节序转网络字节序的函数:htons 网络字节序转主机字节序的函数:ntohs

其中:h代表host n代表network s代表short(16位数值) l代表long(现视为32位数值)

3. 字节操作函数

为了操作诸如IP地址这样的字段,这些字段可能包含值为0的字节,但并不是C字符串,需要使用字节操作函数

一组是几乎支持套接字函数的系统都会提供的函数:bzero bcopy bcmp

一组是ANSI C标准提供的函数:memset memcpy memmove memcmp

两组表达的含义相同,使用时可以任意选择。

注意:当拷贝字节串时,如果源字节串与目标字节串重叠时,bcopy能够正确处理,而memcpy不能,需要使用memmove。

memcmp:当两个字节串不相等时,是大于0还是小于0取决于第一个不等的字节,并且比较时是假设两个不等的字节均为无符号字符(unsigned char)的前提下完成的。

4.将IP地址由字符串格式和数值格式进行相互转换的函数

(1)只适用于IPv4的函数:

inet_aton: IP地址由字符串格式转换成数值格式(将四个点分十进制字符串转换成四个字节的数值,比如192.168.1.2->C0 A8 1 2,其中C0 A8分别时192 168的十六进制表示

inet_ntoa: IP地址由数值格式转换成字符串格式(将四个字节的数值转换成四个点分十进制字符串,比如C0 A8 1 2->192.168.1.2,其中C0 A8分别时192 168的十六进制表示)

注意:inet_ntoa:由该函数的返回值所指向的字符串驻留在静态内存中。这意味着该函数是不可重入的。因为静态数据是全局数据,如果是堆栈数据则没有,因为即使相同函数,堆栈数据也是互不影响的,

(2)对IPv4和IPv6都适用的函数:

inet_pton: IP地址由字符串格式转换成数值格式

inet_ntop: IP地址由数值格式转换成字符串格式

可以这样记忆:p代表表达(presentation),表示字符串格式;n代表数值(numeric),表示数值格式

inet_ntop需要指定存储IP字符串的长度。为有助于指定这个长度,在

netinet/in.h 头文件中定义了两个宏,INET_ADDRSTRLEN(16)和INET6_ADDRSTRLEN(46)。可以使用这两个宏定义存储IP字符串的数组。

(3)因为inet_pton inet_ntop对IPv4和IPv6都适用,建议使用这组函数

(4)通用的inet_ntop

虽然inet_ntop对于IPv4和IPv6都适用,但是它们是协议相关的,即将IPv4 和 IPv6的地址由字符串转换成数值,需要不同的代码:

struct sockaddr_in addr;
inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));
struct sockaddr_in6 addr6;
inet_ntop(AF_INET6,&addr.sin6_addr,str,sizeof(str));

如果需要,可以实现一个通用的函数sock_ntop,它以指向某个套接字地址结构的指针为参数,查看该结构的内部sa_family,然后调用适当的函数返回该地址的表达格式。

其中可使用通过套接字地址结构sockaddr做参数,并使用该参数的sa_family判断具体是哪个协议。代码位置:lib/sock_ntop.c

5. readn writen readline

字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,这不是出错的状态。这个现象的原因在于内核中用于套接字的缓冲区可能已达到了极限。此时需要调用者再次调用read或write函数,以输入或输出剩余的字节。

为了不让实现返回一个不足的字节计数值,我们总是改为调用readn writen代替read write

代码见lib/readn.c lib.writen.c lib/readline.c

虽然基于文本行的网络协议相当多,比如SMTP HTTP FTP,然而我们的建议是依照缓冲区而不是文本行的要求来考虑编程。编写从缓冲区中读取数据的代码,当期待一个文本行时,就查看缓冲区中是否含有那一行。

实现readline时即使使用内部缓冲区实现,也可能存在问题。比如select等系统函数仍然不知道readline使用的内部缓冲区,因此编写不严谨的程序很可能发现自己在select上等待的数据早已收到并存放在readline的缓冲区中了。

6. 总结:

(1)通用套接字地址结果为sockaddr,具体套接字结构IPv4为sockaddr_in,IPv6为sockadd_16。其中sockaddr只是为了在套接字函数中可以传入任意套接字地址而定义的结构体,功能类似于void*。

(2)所有的套接字地址结构都有一个共同的变量sa_family,它用来指明具体的地址族的,它是socket接口的第一个参数,具体指可以用man socket查看。sockaddr_in 的地址族类型为AF_INET,并用32位存储IPv4地址,sockaddr_in6的地址族类型为AF_INET6,并用128位存储IPv6地址。而Tcp Udp的端口号都用16位存储

(3)由于历史的原因和POSIX规范的规定,套接字地址结构中的某些字段必须按照网络字节序进行维护。所以当需要把套接字地址结构传递给内核时,需要首先把IP地址和端口号由主机字节序转换成网络字节序。从内核获取的套接字类型(比如accept)也是网络字节序,如果要打印,首先先转换成主机字节序

IP地址和端口号 在主机字节序和网络字节序之间互相转换的四个函数为:htonl ntohl htons ntohs (其中h代表host主机,n代表network,l代表32位数值 s代表16位数值)

(4)将IP地址由字符串格式和数值格式进行相互转换的函数,建议使用inet_ptoninet_ntop,这两个函数对于IPv4和IPv6都适用。(其中p代表presentation表达,n代表numeric数值

(5)字节流套接字上调用read或write输入或输出的字节数可能比请求的数量少,这不是出错的状态。但为了不让实现返回一个不足的字节计数值,我们总是改为调用readn writen代替read write

时间: 2024-11-06 10:00:32

unix网络编程笔记(一)的相关文章

UNIX网络编程笔记(1)—传输层协议

开始学习网络编程的经典<UNIX网络编程>(第3版)作为研究生阶段的副本练习吧,厚厚一本书,希望能坚持看下去,坚持做些笔记. 1.TCP/IP协议概述 IPv4 网际协议版本4(Internet Protocol version 4),32位地址,为TCP.UDP.SCTP.ICMP和IGMP提供分组递送服务. IPv6 网际协议版本6(Internet Protocol version 6).128位地址,为TCP.UDP.SCTP和ICMPv6提供分组递送服务. TCP 传输控制协议(Tr

UNIX网络编程笔记(4)—TCP客户/服务器程序示例

TCP客户/服务器程序示例 这一章信息量开始大起来了,粗略来看它实现了简单的TCP客户/服务器程序,里面也有一些费解的细节. 1.概述 完整的TCP客户/服务器程序示例.这个简单的例子将执行如下步骤的一个回射服务器(这里的回射服务器就是服务简单的把客户端发送的消息返回给客户): 1)客户从标准输入读入一行文本,并写给服务器 2)服务器从网络输入读入这行文本,并回射给客户 3)客户从网络输入读入这行回射文本,并显示在标准输出上 这样实际上就构成了一个全双工的TCP连接. 本章就围绕了这个简单的TC

UNIX网络编程笔记(2)—套接字编程简介

套接字编程概述 说到网络编程一定都离不开套接字,以前用起来的时候大多靠记下来它的用法,这一次希望能理解一些更底层的东西,当然这些都是网络编程的基础- (1)套接字地址结构 大多说套接字函数都需要一个指向套接字地址结构的指针作为参数,每个协议族都定义它自己的套接字地址结构,这些结构都以sockadd_开头. IPV4套接字地址结构 IPv4套接字地址结构通常称为"网际套接字地址结构",以sockaddr_in命名,并定义在 /* Internet address. */ typedef

UNIX 网络编程笔记-CH3:套接字编程简介

IPv4套接字地址结构 struct in_addr { in_addr_t s_addr; }; struct sockaddr_in { uint8_t sin_len; /* length of structure (16) bytes */ sa_family_t sin_family; /* AF_INET */ in_port_t sin_port; /* 16-bit TCP/UDP port, network byte order(big-endian) */ struct in

unix网络编程笔记(二)

第四章笔记 1. 基本Tcp客户端/服务器程序的套接字函数 2. socket函数: int socket(int family,int type,int protocol); (1)socket有三个函数,除了tcp udp外还支持许多协议. (2)对于tcp协议:三个参数分别为AF_INET/AF_INET6.SOCK_STREAM.0 (3)对于udp协议:三个参数分别为AF_INET/AF_INET6.SOCK_DGRAM.0 (4)其他协议和参数含义,先pass 3. connect函

UNIX网络编程笔记(3)—基本TCP套接字编程

基本TCP套接字编程 主要介绍一个完整的TCP客户/服务器程序需要的基本套接字函数. 1.概述 在整个TCP客户/服务程序中,用到的函数就那么几个,其整体框图如下: 2.socket函数 为了执行网络I/O,一个进程必须要做的事情就是调用socket函数.其函数声明如下: #include <sys/socket.h> int socket(int family ,int type, int protocol); 其中: family:指定协议族 type:指定套接字类型 protocol:指

UNIX网络编程笔记(5)—I/O复用select/poll

I/O复用:select和poll函数 1. 概述 考虑一种情况,当客户端阻塞于fgets调用时,服务器进程被杀死:此时服务器TCP虽然正确地给客户TCP发送了一个FIN,但是由于客户进程阻塞于标准输入的过程,直到从套接字读时为止.这样的进程就需要一种机制,使得内核一旦发现进程指定的一个或多个I/O条件就绪,就通知进程.这个能力就叫做I/O复用.由select和poll函数支持的. I/O复用典型使用在下列网络应用场合: (1)客户处理多个描述符. (2) 客户通知处理多个套接字. (3) 服务

UNIX网络编程笔记(6)—UDP网络编程

基本UDP套接字编程 1. 概述 TCP和UDP的本质区别就在于:UDP是无连接不可靠的数据报协议,TCP是面向连接的可靠字节流.因此使用TCP和UDP编写的应用程序存在一些差异.使用UDP编写的一些常见的应用程序有:DNS(域名解析系统).NFS(网络文件系统)和SNMP(简单网络管理协议). 2. sendto和recvfrom函数 类似与标准的read和write函数: #include <sys/socket.h> ssize_t recvfrom (int sockfd,void *

UNIX 网络编程笔记-CH2:TCP、UDP概貌

好久不读不用又忘得差不多了,还是感叹Richard Stevens真是太刁,25年前第一版. "Tcp state diagram fixed new" by Scil100. Licensed under CC BY-SA 3.0 via Wikimedia Commons - http://commons.wikimedia.org/wiki/File:Tcp_state_diagram_fixed_new.svg#/media/File:Tcp_state_diagram_fix