2017-03-24 11:38:03 mashuiping 阅读数 168

前言

本章开始介绍API。其中有很多个参数实现的功能差不多,我们除了学习他们的功能之外,要还特别关注形如“有了read为什么还要有readn、readline呢?”之类的问题。下面一一归纳。

关注点

  • 套接字地址结构
  • 值-结果参数
  • 字节排序函数(大小端存储问题)
  • 字节操纵函数
  • inet_pton inet_ntop函数
  • sock_ntop函数
  • readn writen和readline函数

套接字地址结构(IPV4地址结构)

struct in_addr{
  in_addr_t s_addr;
};
struct sockaddr_in {
  uint8_t sin_len;
  sa_family_t sin_family;
  in_port_t sin_port;
  struct in_addr sin_addr;
  char sin_zero[8];
};

值-结果参数

值:进程到内核
结果:内核到进程
(1)进程到内核传递套接字的地址结构的函数有3个:bind、connect、sendto。如

struct sockaddr_in serv;
/*fill in serv{} */
connect(sockfd, (SA*) &serv, sizeof(serv));

(2)从内核到进程传递套接字地址结构的函数有4个:accept、recvfrom、getsockname和getpeername。通常返回套接字地址结构的长度。

struct sockaddr_un cli; /*Unix domain*/
socklen_t len;
len = sizeof(cli);
getpeername(unixfd, (SA*) &cli, &len);
/* len may have changed */

本书中还会碰到的其他值-结果参数:

  • select函数中间的3个参数
  • getsockopt函数的长度参数
  • 使用recvmsg函数时,msghdr结构中的msg_namelen和msg_controllen字段
  • ifconf结构中的ifc_len字段
  • sysctl函数两个长度参数中的第一个

字节排序函数(大小端存储问题)

这里不讨论什么是大小端、如何用union判断机器的大小端,而是讨论相应函数的使用。

#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);

网络字节序为大端
而由于历史原因计算机可能大端也可能小端(小端存储居多)
那么需要把主机序转为网络序。
如:

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(13);

存储13一个字节就足够所以用htons
而IP地址通常是4个字节所以用htonl

字节操纵函数

#include<string.h>
void bzero(void *dest, size_t nbytes);
void bcopy(const void *src, void *dest, size_t nbytes);
int bcmp(const void *ptrl, const void *ptr2, size_t nbytes);

bzero把目标字节串中指定数目的字节置为0.
bcopy将指定数目的字节从源字节串移到目标字节串
bcmp比较两个任意的字节串,若相同返回0,不同返回非0

#include <string.h>
void *memset(void *dest, int c, size_t len);
void *memcpy(void *dest, const void *src, size_t nbytes);
int memcmp(const void *ptr1, const void *ptr2, size_t nbytes);

memset把目标字符串指定数目的字节置为c
memcpy类似bcopy,不过两个指针参数的顺序相反。当源字节串与目标字节串重叠时,bcopy能够正确处理,但是memcpy的操作结果却不可知。这种情形下必须该用ANSI C的memmove函数。
memcpy比较两个任意的字节串,若相同返回0,否则返回一个非0值。

inet_pton和inet_ntop函数

这个两个函数的作用是实现ip地址点分十进制格式和二进制格式的相互转换。

#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr);
//返回:1(成功) 0(不是有效格式) -1(出错)
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
//返回:结果指针(成功) NULL(失败)

sock_ntop和相关函数

readn、writen和readline函数

2017-03-24 11:38:45 mashuiping 阅读数 221

前言

本章讲解一个完整的TCP客户/服务器程序所需要的基本套接字函数、通过fork子进程处理新连接的并发服务器、客户与服务器交互的典型时刻。

关注点

  • socket函数
  • connect函数
  • bind函数
  • listen函数
  • accept函数
  • fork和exec函数
  • 并发服务器
  • close函数
  • getsockname和getpeername函数

socket函数

#include <sys/socket.h>
int socket(int family, int type, int protocol);
//调用成功返回一个小的正值

family:协议族,如AF_INET IPV4协议族
type:类型,如SOCK_STREAM字节流协议
protocol:协议 IPPROTO_TCP TCP协议
type和protocol存在某种程度的关联性,这和以往每个参数都是独立的不同,应该存在某种原因。待后续解答。

connect函数

#include <sys/socket.h>
int connect(int sockfd, const sockaddr * servaddr, socklen_t addrlen);
//成功返回0,出错为-1

出错的情况
1. TCP客户没有收到SYN分节的响应,如往本地子网上一个不存在的IP发送SYN
2. 硬错误:收到RST
产生RST的三个条件:目的地为某端口的SYN到达,然而端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收到一个根本不存在的连接上的分节
3. 软错误
发送SYN分节引发路由器“destination unreachable”ICMP错误。

bind函数

#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
//成功返回0,出错返回-1

常见错误“address already in use”

listen函数

#include <sys/socket.h>
int listen(int sockfd, int backlog);
成功返回0,出错返回-1

监听套接字维护两个队列:
未完成连接队列(SYN_RCVD)和已完成连接队列(ESTABLISHED)。
backlog要求这两个队列之和不超过它。
关于backlog这个有意思的话题,可以参见

accept函数

#include <sys/socket.h>
int accept(int sockfd, struct sockaddr * cliaddr, socklen_t *addrlen);
//成功返回非负描述符号,出错返回-1

accept拥有两个值-结果参数,cliaddr和addrlen可以返回peer端信息,如果不关心,可以置NULL。

fork和exec函数

#include <unistd.h>
pid_t fork(void);
//成功返回两次,子进程看到返回0,父进程看到返回子进程ID,出错返回-1

fork可以创建父进程的副本,同时在副本(子进程中)调用exec可以执行另外的应用程序。例如shell之类的程序就是典型用法。

并发服务器

并发服务器轮廓

pid_t pid;
int listenfd, connfd;
listenfd = Socket(...);
Bind(listenfd, ...);
for(;;){
  connfd = Accept(listenfd,...);
  if((pid = Fork())== 0){
    Close(listenfd);
    doit(connfd);
    Clost(connfd);
    exit(0);
  }
  Close(connfd);
}

图:

close函数

#include <unistd.h>
int close(int sockfd);

close采用引用计数的规则,当计数不为0的时候,并不引发TCP的四分组连接终止序列。

getsockname和getpeername函数

这两个函数返回某个套接字关联的本地地址协议(getsockname),或者返回某个套接字关联的外地协议地址(getpeername);

#include <sys/socket.h>
int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);

当没有bind的时候,用getsockname可以获得本地IP地址和本地端口号。
当进程是通过exec出来的,如果它想获得对端的IP地址和本地端口号,那么采用getpeername。(例子:inetd超级守护进程)

2019-10-06 18:13:13 ProgramVAE 阅读数 29

1 下载源码并且配置环境

   对于想学习网络编程的同学来说,《UNIX网络编程》这书肯定是不二选择。所谓实践是检验真理的唯一标志,特别是对于编程来讲,再多的理论经验也比不过code一次。《UNIX网络编程》这本书提供连源码下载,第三版本的源码我是在 GitHub上面下载的。

但是源码归源码,下载了如果不会使用那也是瞎扯。下面简单介绍一下在本节主要介绍UNIX网络编程卷(第三版)在Ubuntu19.04的配置问题,并运行一个简单时间获取客户程序。
1.首先下载源文件,链接如下

2.将下载好的压缩文件unpv13e.tar.gz解压,解压后文件为unpv13e.

3.解压后进入源码目录,打开README文件:
 在这里插入图片描述
4.按照提示首先在命令行执行 ./configure,没有问题。

5.执行 cd lib 进入lib文件夹,执行make命令,没有问题。

6.执行 cd …/libfree进入libfree文件夹,执行make命令,会遇到以下问题:
在这里插入图片描述
错误解决办法是:进入libfree文件夹,打开inet_ntop.c,在第60行将size_t size改为socklen_t size。
在这里插入图片描述
7.再执行第6步,问题解决。

8.在命令行执行 cd …/intro/,进入intro文件夹,

执行 make daytimetcpcli,生成可执行文件后,

执行 ./daytimetcpcli 127.0.0.1 会出现以下问题:

Connection refused
运行时间获取程序,需要现在ubuntu里安装时间服务

9.执行以下命令

sudo apt-get install xinetd
sudo vi /etc/xinetd.d/daytime
将图中的两个disable的值改为no:
在这里插入图片描述
执行 service xinetd restart
10.重新执行./daytimetcpcli 127.0.0.1,结果如图
在这里插入图片描述

2 章节末习题

第三题:修改client socket(AF_INET, SOCK_STREAM, 0) AF_INET成9999.

结果:报socket error: Address family not supported by protocol family 。
经查询:AF_INET本身代表了protocol family的一个特殊标记值,如果修改成其他值,相当于一个新的protocol family,那么之后传入的address family当然不会支持了。

第四题:client增加一个计数器并打印。

int counter = 0;

while ( (n = read(sockfd, recvline, MAXLINE)) > 0) 
{
    counter++;
    recvline[n] = 0;    /* null terminate */
    if (fputs(recvline, stdout) == EOF)
        err_sys("fputs error");
}

printf("%d\n",counter);

第五题:修改server端代码,将原本完整的内容,单字节发送,并查看client端实际执行的read次数。

for (int i = 0; i < strlen(buff); ++i)
{
    Write(connfd, &(buff[i]), 1);
}

Close(connfd);

实际执行client端,每次执行read的counter都在变化。
在这里插入图片描述

2018-12-09 16:23:34 weixin_34764432 阅读数 64

UNIX网络编程卷1:套接字联网API 详解笔记
第一部分:简介和TCP/IP
1.Socket 套接字
sockfd = socket(AF_INET(网际流), SOCK_STREAM, 0);

2.*bind(int fd, const struct sockaddr sa, socklen_t salen)
套接字绑定函数

3.监听套接字
socket、bind、listen三个函数依次执行,是TCP服务器准备监听描述符的标准步骤。
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bind(sockfd, (SA* )servaddr, sizeof(servaddr));
listen(sockfd, LISTENQ)

4.接受客户连接,发送应答
connfd = accept(sockfd, (SA*)NULL, NULL);
accept是进程进入睡眠,TCP握手协议完成之后,accpet接收到应答,返回值称为已连接描述符,
用于与已经连接上的客户端进行通信。

5.OSI模型
应用层
表示层 应用层
会话层
传输层 TCP UDP
网络层 IPv4、IPv6
数据链路层 设备驱动程序和硬件
物理层

6.UDP 是简单不可靠的传输协议。

7.TCP是复杂而又可靠的传输协议。

8.STCP是可靠的传输协议,同时还提供消息边界、传输级别多宿支持以及将头端阻塞减小到最少的一种方法。

协议:
IPv4 32位地址,给TCP、UDP、STCP、ICMP、IGMP提供分组递送服务
IPv6 128位地址,给TCP、UCP、STCP、ICMPv6 提供分组递送服务
TCP 传输控制协议 给用户提供全双工字节流,TCP套接字是一种流套接字,可用IPv4或IPv6实现
UDP 用户数据报协议 UDP套接字是一种数据包套接字,不保证数据准确到达,可用IPv4或IPv6实现
SCTP 流控制传输协议 提供可靠的全双工关联的面向连接的协议,STCP是多宿的,每个关联的两端涉及一组IP地址和端口号。
ICMP 网际消息控制协议 主要处理路由器与主机之间的错误和控制消息。ping 和 traceroute使用这个协议
IGMP 网际组管理协议 IGMP用于多播
ARP 把一个IPv4地址转换成一个硬件地址
RARP 把一个硬件地址转成IPv4地址
ICMPv6 集成了ICMPv4 ARP 以及 IGMP的功能
BPF BSD分组过滤器 用于提供对数据链路的访问能力
DLPI 数据链路提供者接口 用于提供对数据链路的访问能力

9.TCP和UDP的特性:
UDP
UDP缺乏可靠性,需要对应用程序添加不同的功能,对端的确认、本端的超时与重传等;UDP提供无连接的服务,表示一个客户端可以向多个服务端发送同一包数据。

TCP
TCP可靠性:1.TCP建立服务器与客户端之间的连接;2.TCP有消息应答机制;3.TCP拥有一个计算客户端与服务器之间传输一次需要的时间;3.TCP会对多次分包传输的数据加上序列号,重新排序;4.TCP 提供流量控制,保证客户端传给服务器的数据永远不会超过服务器剩余的空间;TCP是全双工协议。

流控制传输协议(SCTP)
STCP与TCP、UDP类似。与TCP不同的是STCP是面向消息的协议,与UDP一样,将发送端写入的每条记录的长度随数据一道传递给接收端应用。STCP可以在连接的端点之间提供多个流,并且相互不会影响。STCP支持多宿性,保持可以支持多个IP地址。

10.TCP选项
MSS选项:最大分节大小
窗口规模选项:最大窗口规模可以达到1GB
时间戳选项:防止由失而复现造成的数据损坏

11.服务器发送数据耗时长时,则先是发送确认,后是应答。

12.MSL是任何IP数据报能够在英特网中存活的最长时间。最长分节时间。传统是30秒。

13.TIME_WAIT状态 持续时间大约是MSL的两倍
1.可靠的实现TCP全双工连接的终止
2.允许老的分解在网络中消逝(防止老的重复分组被认为是新的分组的化身)

14.SCTP的连接建立与终止
四路握手
(1)服务器被动打开,通过调用socket、bind、listen。
(2)客户通过调用connect或者发送一个隐式打开该关联的消息进行主动打开。
(3)服务器回复一个INIT ACK
(4)客户端发送一个COOKIE ECHO回给服务器
(5)服务器回复一个ACK证明发送的是正确的cookie

15.SCTP的分组单位信息为块
SCTP选项:
1.动态地址扩展,允许协作的SCTP端点从已有的某个关联中动态增删IP地址;
2.不完全可靠性扩展,允许协作的SCTP端点在应用进程的指导下进行数据限制,去掉过于陈旧的消息。

16.端口号
TCP/IP协议分配给FTP服务器的端口号是21
UDP分配给TFTP(简化邮件传送协议)的端口号是69

17.众所周知的端口号为0~1023,由IANA分配和控制,可以将一个端口号同时分配给TCP、UDP、SCTP同一给定的服务。

18.已登记的端口号为102449151,不受IANA控制,但是会登记到IANA的端口使用表中,相同的端口也可分配给TCP、UDP给定的服务。如60006063分配给这两种协议的X Window服务器。

49152~65535是动态的或私用的端口,是临时端口,不受IANA管辖。

19.套接字对
TCP套接字对是一个定义连接的两个端点的四元组:本地IP地址、本地的TCP端口号、外地IP地址、外地TCP端口号。套接字对唯一标识一个网络上的每个TCP连接,但是对于一个端点多宿的情况下,一个端点就需要多个四元组标识。
标识每个端点的两个值(IP地址和端口号)通常称为一个套接字。

IPv4数据包最大为65535字节,包括IPv4首部。
IPv6数据包最大为65575字节,包括IPv6首部,40位。
MTU 最大传输单元,IPv4规定的最小链路MTU为68字节,IPv6规定的最小链路的MTU为1280字节。
IP数据报超出相应链路的MTU时,IPv4和IPv6都将进行分片,这些片段在最终到达目的地之前不会进行重组。IPv4主机和路由器(转发)都会对自身发出的数据报进行分片;而IPv6只有主机会对发出的数据报进行分片,而路由器不会对其转发的数据报进行分片。
IPv4首部的DF位专门用于设置分片,IPv6每个数据报都隐含一个数据位,所以IPv6传输数据报是要么用最小的传,要么必须支持。
IPv4和IPv6都有最小缓冲区重组大小,IPv4为256字节,IPv6为1500字节。
TCP有个MSS(最大分节大小),通知对方重组缓冲区大小的实际值,避免分片;MSS通常设置为MTU减IP和TCP首部的固定长度。以太网中IPv4为1460,IPv6为1440(TCP首部20字节,IPv4首部20字节,IPv6首部40字节)。TCP的MSS选项中,MSS是一个16为的字段,IPv4最大值为65495,IPv6的最大值为65515。
SCTP基于对端所有地址发现的最小路径MTU保持一个分片点。

后续还会更新。

2019-05-08 19:50:36 lvhonglei1987 阅读数 2589

请点击下载pdf文件:

UNIX网络编程卷1:套接字联网API(第3版).pdf

更多书籍请点击 技术电子书总目录

UNIX网络编程卷2源码编译错误

博文 来自: zhangxiao93

UNIX网络编程:卷2 - 读书笔记

博文 来自: huanzai2
没有更多推荐了,返回首页