您的位置:首页 > 理论基础 > 计算机网络

《Unix网络编程》卷1:套接字联网API(第3版):简介、传输层、套接字编程

2017-08-05 23:47 525 查看
全书共31章+附录。


计划安排
:吃透这本书,一天三章+源码,并实测代码做当天笔记,CSDN见。
时间安排:计划时间1.5个月 == 6个周末 == 12天。

2017.08.05    第01-03章:TCP/IP简介、传输层、套接字编程简介
2017.08.06    第04-06章:基本TCP编程、TCP客户端/服务器程序、I/O复用
2017.08.12    第07-09章:套接字选项、基本UDP编程、基本SCTP编程
2017.08.13    第10-12章:SCTP客户端/服务器程序例子、名字与地址互换、IPv4和IPv6互操作性
2017.08.19    第13-15章:守护进程和inetd超级服务器、高级I/O、Unix域协议
2017.08.20    第16-18章:非阻塞I/O、ioctl操作、路由套接字
2017.08.26    第19-21章:密钥管理套接字、广播、多播
2017.08.27    第22-24章:高级UDP编程、高级SCTP编程、带外数据
2017.09.02    第25-27章:信号驱动I/O、线程、IP选项
2017.09.03    第28-30章:原始套接字、数据链路访问、客户端/服务器程序设计范式
2017.09.09    第31章-附录:流。附录:IPv4/6协议、调试技术
2017.09.10    整理、总结:思维导图。

>>第1章丶简介

网络应用系统主要构成有两部分:客户端(client)和服务器(server)。

举例来说:web服务器程序时一个长时间运行的守护程序,web客户与服务器之间使用TCP通信,TCP转而使用IP通信,IP通过以太网驱动程序的数据链路层通信。



客户端和服务器通常是用户进程,而TCP和IP协议通常是内核中"协议栈"的一部分。

LAN:局域网(内网)

WAN:广域网(外网)



路由器是广域网的架构设备。当下最大的广域网是因特网internet。

/* 一个简单的时间获取客户程序server */
#include <time.h>
#include "unp.h"

#define MAXLINE 4096
#define LISTENQ 1024
//#define SA struct sockaddr
typedef struct sockaddr SA;

int
main(int argc, char **argv)
{
int					listenfd, connfd;
struct sockaddr_in	servaddr;
char				buff[MAXLINE];
time_t				ticks;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family      = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port        = htons(1300);	/* daytime server */

bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

listen(listenfd, LISTENQ);

for ( ; ; ) {
connfd = accept(listenfd, (SA *) NULL, NULL);

ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
write(connfd, buff, strlen(buff));

close(connfd);
}
}

/* 一个简单的时间获取客户程序client */
#include	"unp.h"

#define MAXLINE 1024
//#define SA struct sockaddr
typedef struct sockaddr SA;

int main(int argc, char **argv)
{
int					sockfd, n;
char				recvline[MAXLINE + 1];
struct sockaddr_in	servaddr;

if (argc != 2)
printf ("usage: a.out <IPaddress>\n");

if ( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
printf ("socket error\n");

bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port   = htons(1300);	/* daytime server */
if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0)
printf ("inet_pton error for %s\n", argv[1]);

if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) < 0)
printf ("connect error\n");

while ( (n = read(sockfd, recvline, MAXLINE)) > 0) {
recvline
= 0;	/* null terminate */
if (fputs(recvline, stdout) == EOF)
printf ("fputs error\n");
}
if (n < 0)
printf ("read error\n");

exit(0);
}

/* unp.h */
#ifndef __UNP_H__
#define __UNP_H__

#include	<sys/types.h>	/* basic system data types */
#include	<sys/socket.h>	/* basic socket definitions */
#include	<sys/time.h>	/* timeval{} for select() */
#include	<time.h>		/* timespec{} for pselect() */
#include	<netinet/in.h>	/* sockaddr_in{} and other Internet defns */
#include	<arpa/inet.h>	/* inet(3) functions */
#include	<errno.h>
#include	<fcntl.h>		/* for nonblocking */
#include	<netdb.h>
#include	<signal.h>
#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<sys/stat.h>	/* for S_xxx file mode constants */
#include	<sys/uio.h>		/* for iovec{} and readv/writev */
#include	<unistd.h>
#include	<sys/wait.h>
#include	<sys/un.h>		/* for Unix domain sockets */

#endif //__UNP_H__



/* 上述例子中原书做了比较安全的接口实现,如 Socket() 伪代码演示:*/
/* include Socket */
int Socket(int family, int type, int protocol)
{
int		n;

if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return(n);
}
/* err_sys() */
void err_sys(const char *fmt, ...)
{
va_list		ap;

va_start(ap, fmt);
err_doit(1, fmt, ap);
va_end(ap);
exit(1);
}
/* err_doit() */
static void err_doit(int errnoflag, const char *fmt, va_list ap)
{
int		errno_save;
char	buf[MAXLINE];

errno_save = errno;		/* value caller might want printed */
vsprintf(buf, fmt, ap);
if (errnoflag)
sprintf(buf+strlen(buf), ": %s", strerror(errno_save));
strcat(buf, "\n");
fflush(stdout);		/* in case stdout and stderr are the same */
fputs(buf, stderr);
fflush(stderr);		/* SunOS 4.1.* doesn't grok NULL argument */
return;
}


【Tips】调用sprintf无法检查目的缓冲区是否溢出,相反,snprintf要求其第二个参数指定目的缓冲区的大小,因此可以确保该缓冲区不溢出。get√

许多网络入侵是由黑客通过发送数据,导致服务器对sprintf的调用使其缓冲区溢出而发生的,必须小心使用的函数还有gets/strcat/strcpy,通常应分别改为调用fgets/strncat/strncpy,更好的替代函数还有strlcat/strlcpy可以确保结果是正确终止的字符串。

OSI模型 open systems interconnection

全称:计算机通信开放系统互连模型。



物理层/数据链路层:主要是设备驱动和网络硬件,通常我们不必关心。

网络层:由IPv4和IPv6这两个协议处理。详细在附录A中。

传输层:即本书所讲的套接字编程接口,从应用层(上3层)进入传输层的接口。

应用层/会话层/表示层:统称为应用层,如web客户端(浏览器)、telnet客户端、web服务器、FTP服务器等。

网络细节的两个基本命令:netstat /
ifconfig

netstat

(1)netstat -ni // 提供网络接口信息,-n输出数值地址而不是反向解析为名字

$ netstat -ni

Kernel Interface table

Iface   MTU Met   RX-OK RX-ERR RX-DRP RX-OVR    TX-OK TX-ERR TX-DRP TX-OVR Flg

eth0       1500 0     15459      0      0 0         10444      0      0      0 BMRU

lo        16436 0       138      0      0 0           138      0      0      0 LRU

lo 环回接口

eth0 以太网接口

(2)netstat -nr // 展示路由表信息,另一种确定接口的方法

$ netstat -nr

内核 IP 路由表

Destination     Gateway         Genmask         Flags   MSS Window  irtt Iface

0.0.0.0         192.168.31.1    0.0.0.0         UG        0 0          0 eth0

169.254.0.0     0.0.0.0         255.255.0.0     U         0 0          0 eth0

192.168.31.0    0.0.0.0         255.255.255.0   U         0 0          0 eth0

(3)ifconfig eth0 // 获得eth0以太网接口的详细信息

$ ifconfig eth0

eth0      Link encap:以太网  硬件地址 00:0c:29:55:a0:99

          inet 地址:192.168.31.205  广播:192.168.31.255  掩码:255.255.255.0

          inet6 地址: fe80::20c:29ff:fe55:a099/64 Scope:Link

          UP BROADCAST RUNNING MULTICAST  MTU:1500  跃点数:1

          接收数据包:15624 错误:0 丢弃:0 过载:0 帧数:0

          发送数据包:10571 错误:0 丢弃:0 过载:0 载波:0

          碰撞:0 发送队列长度:1000

          接收字节:1468669 (1.4 MB)  发送字节:1070042 (1.0 MB)

          中断:19 基本地址:0x2000
// MULTICAST 标志通常指明该接口所在主机支持多播。

(4)ping // 测试ip地址是否联通当前以太网络

$ ping -b 192.168.31.255

PING 192.168.31.255 (192.168.31.255) 56(84) bytes of data.

64 bytes from 192.168.31.255: icmp_req=1 ttl=64 time=0.253 ms

64 bytes from 192.168.31.255: icmp_req=2 ttl=64 time=0.022 ms

64 bytes from 192.168.31.255: icmp_req=3 ttl=64 time=0.029 ms

...

64位体系结构的趋势

原因之一是:在每个进程内部可以由此使用更长的编址长度(即64位指针),从而可以寻址更大的内存空间(超过2^32字节)。

>>第2章丶传输层TCP-UDP-SCTP

TCP:传输控制协议,面相连接,全双工字节流。流套接字。关心:确认、超时、重传等细节。

UDP:用户数据报协议,无连接协议。数据报套接字。不保证最终达到目的地。

ICMP:网际控制消息协议,处理路由器和主机之间流通的错误和控制消息。

ARP:地址解析协议,把IPv4地址映射成一个硬件地址。

RARP:反地址解析协议,把一个硬件地址映射成一个IPv4地址。

SCTP(内容略)

TIME_WAIT状态有两个存在的理由:

(1)可靠地实现TCP全双工连接的终止;

(2)允许老的重复分节在网络中消逝。

端口号port:

传输层协议都使用16位整数的端口号来区分不同的进程。
0~1023(自控保留) | 1024~49151(已登记) |49152~65535(动态私有)

通讯对socket pair:

每个TCP分节中都有16bit的端口号和32bit的IPv4地址。

>>第3章丶套接字编程简介

【套接字地址结构】

IPv4套接字地址结构:sockaddr_in
#include <netinet/in.h>

struct in_addr {

    in_addr_t       s_addr;         /* 32bit IPv4 address. */

};

struct sockaddr_in {

    uint8_t         sin_len;        /* length of structure (16) */

    sa_family_t     sin_family;     /* AF_INET */

    in_port_t       sin_port;       /* 16bit TCP/UDP port number */

    struct in_addr  sin_addr;       /* 32bit IPv4 address */

    char            sin_zero[8];    /* unused */

};

通用套接字地址结构:sockaddr
#include <sys/socket.h>

struct sockaddr {

    uint8_t         sa_len;         /* length of structure */

    sa_family_t     sa_family;      /* address family: AF_XXXX value */

    char            sa_data[14];    /* protocol-specific address */

};

IPv6套接字地址结构:sockaddr_in6
#include <netinet/in.h>

struct in6_addr {

    uint8_t         s6_addr[16];    /* 128bit IPv6 address */

};

#define SIN6_LEN

struct sockaddr_in6 {

    uint8_t         sin6_len;       /* length of structure (28) */

    sa_family_t     sin6_family;    /* AF_INET6 */

    in_port_t       sin6_port;      /* transport layer port */

    uint32_t        sin6_flowinfo;  /* flow information, undefined */

    struct in6_addr sin6_addr;      /* IPv6 address */

    uint32_t        sin6_scope_id;  /* set of interfaces for a scope */

};

新的通用套接字地址结构:sockaddr_storage
#include <netinet/in.h>

struct sockaddr_strorage {

    uint8_t         ss_len;         /* length of structure */

    sa_family_t     ss_family;      /* address family: AF_XXXX value */

};



/* 输出主机字节序程序 */
#include	"unp.h"

int main(int argc, char **argv)
{
union {
short  s;
char   c[sizeof(short)];
} un;

un.s = 0x0102;
printf("This host byteorder is: ");
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);
}


字节序转换接口函数:大小端字节序转换
#include <netinet/in.h>

uint16_t htons (uint16_t host16bitvalue);

uint32_t htonl (uint32_t host32bitvalue);  // 此2个函数均返回:网络字节序的值

uint16_t ntohs (uint16_t net16bitvalue);

uint32_t ntohl (uint32_t net32bitvalue);  // 此2个函数均返回:主机字节序的值

接口特点:h代表host,n代表network,s代表short,l代表long。

字节操纵接口函数:
#include <string.h>

void bzero (void *dest, size_t nbytes); // 将目标字节串中指定数目的字节置0

void bcopy (const void *src, void *dest, size_t nbytes);

int bcmp (const void *ptr1, const void *ptr2, size_t nbutes);

#include <string.h>  // ANSI C函数

void *memset (void *dest, int c, size_t len); // 将目标自揭穿指定数目字节置c

void *memcpy (void *dest, const void *src, size_t nbytes);

int memcmp (const void *ptr1, const void *ptr2, size_t nbytes);

接口特点:b代表字节,mem代表内存。

地址转换函数:
#include <arpa/inet.h>

int inet_aton (const char *strptr, struct in_addr *addrptr); // 返回值:字符串有效为1,否则为0

in_addr_t inet_addr (const char *strptr); // 字符串有效则为32位二进制网络字节序的IPv4地址,否则为INADDR_NONE

char *inet_ntoa (struct in_addr inaddr); // 指向一个点分十进制数字符串的指针

/* 随IPv6出现的新函数×2 */

int inet_pton (int family, const char *strptr, void *addrptr);

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

【Tips】函数以结构为参数是罕见的,更常见的是以指向结构变量的指针为参数。

/* 预防万一,不让实现返回一个不足的字节计数值,封装read和write代码: */
ssize_t	readn(int fd, void *vptr, size_t n)
{
size_t	nleft;
ssize_t	nread;
char	*ptr;

ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR)
nread = 0;		/* and call read() again */
else
return(-1);
} else if (nread == 0)
break;				/* EOF */

nleft -= nread;
ptr   += nread;
}
return(n - nleft);		/* return >= 0 */
}

ssize_t	writen(int fd, const void *vptr, size_t n)
{
size_t		nleft;
ssize_t		nwritten;
const char	*ptr;

ptr = vptr;
nleft = n;
while (nleft > 0) {
if ( (nwritten = write(fd, ptr, nleft)) <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0;		/* and call write() again */
else
return(-1);			/* error */
}

nleft -= nwritten;
ptr   += nwritten;
}
return(n);
}


2017.08.05

01-03章节完成...
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  网络编程 unix
相关文章推荐