网络编程(PPT版)
第一章
### 1.普通网络编程程序的框架结构
头文件
定义变量
命令行参数
创建TCP套接口
变量赋值,指定服务器的IP地址和端口
建立与服务器的连接
读服务器的应答并输出到屏幕上
终止程序
2.包裹函数*
作用:包裹函数可以调用实际函数,检查函数返回值,发生错误时终止程序
int
Socket(int family, int type, int protocol)
{
int n;
if ( (n = socket(family, type, protocol)) < 0)
err_sys("socket error");
return (n);
}//举例
//仿写一个
fun(a,b,c)
int
Fun(Eletype a,Eletype b,Eletype c){
int n;
if((n==fun(a,b,c))<0)
err_sys("error!");
return (n);
}
3.Unix errno值
全局变量errno在Unix函数发生错误时会被置为一个指示错误类型的正数,函数本身通常返回-1;只在函数发生错误时设置;所有错误都是常值,在头文件(sys/errno.h)中定义,值0不表示任何错
第二章
1.TCP传输协议
首先, TCP 提供客户与服务器的连接(connection)。一个TCP客户建立与一个给定服务器的连接,跨越连接与服务器交换数据,然后终止连接。
其次,TCP提供可靠性。
第三,TCP通过给所发送数据的每一个字节关联一个序列号进行排序。
第四, TCP提供流量控制。
最后,TCP 连接是全双工的。
2.存在time_wait状态的理由
实现终止TCP全双工连接的可靠性
允许老的重复分节在网络中消逝
端口号http://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
套接口对是连接两个端点的四元组:本地IP地址、本地TCP端口号、远程IP地址、远程TCP端口号
套接口是标识每个端点的两个值(IP和port)
第三章
1.IPV4套接口地址结构
struct in_addr{
in_addr_t s_addr; /* 32位IPv4地址,网络字节序*/
};
struct sockaddr_in{
uint8_t sin_len; /*结构体变量长度*/
sa_family_t sin_family; /*AF_INET*/
in_port _t sin_port; /* 16位端口号*/
struct in_addr sin_addr;
char sin_zero[8]; /*保留*/
};
从应用程序开发人员的观点看,通用的套接口地址结构的唯一用途是给指向特定于协议的地址结构的指针转换类型。
2.值-结果参数
1)从进程到内核传递套接口地址结构有3个函数:
bind、connect和sendto
其中一个参数是指向套接口地址结构的指针,另一个参数是结构的大小
2)从内核到进程传递套接口地址结构有4个函数:
accept、recvfrom、getsockname和getpeername
这4 函数的两个参数是:指向套接口地址结构的指针和指向表示结构大小的整数的指针。
为何将结构大小由整数改为指向整数的指针呢?
当函数被调用时,结构大小是一个值(此值告诉内核该结构的大小,使内核在写此结构时不至于越界),当函数返回时,结构大小又是一个结果(它告诉进程,内核在此结构中确切存储了多少信息),这种参数类型叫值—结果参数。
3.字节排序函数
网际协议在处理多字节整数时,使用大端字节序
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue);
以上两个函数均返回:网络字节序值。
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue);
以上两个函数均返回:主机字节序值。
4.字节操纵函数
#include <strings.h>
void bzero(void *dest,size_t nbytes);
void bcopy(const void * src,void * dest,size_t nbytes);
int bcmp(const void * ptr1,const void * ptr2,size_t nbytes);
返回 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);
返回: 0——相同
>0——ptr1所指字节大于ptr2所指字节
<0——ptr1所指字节小于ptr2所指字节
//功能如其名
5.地址转换函数
#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位二进制的网络字节序地址;若有错,则返回 INADDR_NONE (一般为32位均为1的值)
char * inet_ntoa(struct in_addr inaddr);
返回:指向点分十进制数串的指针
INADDR_NONE (一般为32位均为1的值)
#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——出错
(inet_pton函数实现)
struct sockaddr_in addr;
inet_ntop(AF_INET,&addr.sin_addr,str,sizeof(str));
#include "unp.h"
char * sock_ntop(const struct sockaddr *sockaddr,socklen_t addrlen);
返回:非空指针——成功,NULL——出错
sockaddr指向一个长度为addrlen的套接口地址结构。本函数用它自己的静态缓冲区来保存结果,一个指向此缓冲区的指针即为返回值。
(sock_ntop函数实现)
6.readn,writen,readline函数
#include "unp.h"
ssize_t readn(int filedes,void * buff,size_t nbytes);
ssize_t writen(int filedes,const void * buff,size_t nbytes);
ssize_t readline(int filedes,void * buff,size_t maxlen);
均返回:读写字节数,-1——出错
readn,writen,readline函数实现
7.isfdtype函数
测试一个描述字是不是某给定类型。
#include <sys/stat.h>
int isfdtype(int fd,int fdtype);
第四章
### 1.socket函数
#include <sys/socket.h>
int socket(int family, int type, int protocol);
返回:非负描述字——成功,-1——出错
2.connect函数
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr * servaddr, socklen_t addrlen);
返回:0——成功,-1——出错
客户在调用函数connect前不必非得调用函数bind函数,因为如果必要的话,内核会选择源IP地址和一个临时的端口。
有三个条件可以产生RST:SYN到达某端口但此端口上没有正在监听的服务器;TCP想取消一个已有连接;TCP接收了一个根本不存在的连接上的分节。
3.bind函数
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr * myaddr, socklen_t addrlen);
返回:0——成功,-1——出错
### 4.listen函数
#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回:0——成功,-1——出错
backlog的值应设为多大呢?
一个方法是假设一个缺省值,但允许设置命令行选项或环境变量来覆盖该缺省值。当指定的值比内核所支持的值要大时,也不受影响,因为内核能把所给的值改为它所支持的最大值且不返回错误。
void Listen(int fd, int backlog)
{
char *ptr;
if ( (ptr = getenv("LISTENQ")) != NULL)
backlog = atoi(ptr);
if (listen(fd, backlog) < 0)
err_sys(“listen error”);
}
5.accept函数
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr * cliaddr, socklen_t *addrlen);
返回:非负描述字——OK,-1——出错
6.并发服务器
#include <unistd.h>
pid_t fork (void);
返回:在子进程中为0,在父进程中为子进程ID , -1——出错
7.close函数
#include <unistd.h>
int close(int sockfd);
返回:0——OK, -1——出错
其功能是?
TCP 套接口的close其缺省功能是将套接口做上“已关闭”标记,并立即返回到进程。这个套接口描述字不能再为进程所用:它不能用作为函数read或write 的参数,但TCP将试着发送已排队待发的任何数据,然后按正常的TCP 连接终止序列进行操作。
8.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);//远程协议地址
两者均返回:0——OK,-1——出错
9.send,recv函数
#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
返回:读写字节数,-1——出错
第五章
### 1.TCP回射服务器/客户服务器
服务器程序:
Socket,Bind,Listen,Accept,Fork,str_echo,Close
str_echo函数:
Readline,Writen
客户程序:
Socket,Inet_pton,Connect,str_cli
str_cli函数:
Fgets,Writen,Readline,Fputs
2.正常启动与终止的过程
./tcpserv01 &
服务器 socket,bind,listen 阻accept
客户 socket,connect
客户 str_cli 阻fgets
服务器 fork,str_echo->readline 阻read
服务器父 阻accept
客户 EOF(ctrl+d),fgets,str_cli
main->exit
FIN
exit
TIME_WAIT
SIGCHLD
3.信号的用法
一个进程发往另一个进程(或本身)
由内核发往本身
4.处理SIGCHLD信号
if一个进程终止,且该进程有子进程处于僵尸状态,则所有僵尸进程的父进程ID均置为1(init进程),init进程将作为这些进程的继父并负责清除他们。
调用Listen 之后,增加函数调用:
Signal (SIGCHLD , sig_chld ) ;
EOF FIN
子进程 readline
父进程 阻accept sig_chld->wait
内核使accept返回一个EINTR错误
### 5.wait,waitpid函数*
#include <sys/wait.h>
pid_t wait(int * statloc);
pid_t waitpid(pid_t pid,int * statloc,int options);
返回:进程ID——成功(waitpid返回有可能为0);-1——出错
参数常用WNOHANG,它通知内核在没有已终止子进程时不要阻塞。
缺省行为指默认行为
6.程序运行异常
- accept返回前连接夭折
- 服务器进程终止
- SIGPIPE信号
- 服务器主机崩溃
- 服务器主机崩溃后重启
- 服务器主机关机
7.数据格式
方法一 在客户与服务器之间传递文本串
方法二 在客户与服务器端传递二进制结构
结构体:memcpy()函数;snprintf函数;序列化方法
TCP端口号 IP地址
R (TASK_RUNNING),可执行状态;
S (TASK_INTERRUPTIBLE),可中断的睡眠状态;
D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态;
T (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态;
Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,进程成为僵尸进程。
拓展
第六章
1.IO复用?
如果一个或多个I/O条件满足(例如,输入已准备好被读,或者描述字可以承接更多的输出)时,我们就被通知到。这个能力被称为I/O复用
一个输入操作一般有两个阶段
1)等待数据准备好
2)从内核到进程拷贝数据
2.select函数(I/O复用)
这个函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1,fd_set * readset,fd_set * writeset,fd_set * exceptset,const struct timeval * timeout);
返回:准备好描述字的总数目。0——超时,-1——出错
struct timeval {
long tv_sec;
long tv_usec; //微秒,1微秒等于百万分之一秒
};
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);
如果我们对某个条件不感兴趣,函数select的三个中间参数readset、 writeset 和exceptset中相应参数就可设为空指针。
3.shutdown与close函数
1) close将描述字的访问计数减1,仅在此计数为0时才关闭套接口。用shutdown我们可以激发TCP的正常连接终止序列,而不管访问计数。
2)close终止了数据传送的两个方向:读和写。shutdown可以只关闭读或写,也可以两个都关闭。
#include <sys/socket.h>
int shutdown(int sockfd , int howto);
返回: 0——成功,-1——出错
4.简述DDOS
如果一个恶意客户连接到服务器上,**发送一个字节的数据(而不是一行数据)后就睡眠。**服务器将调用readline,它从客户上读到单个字节的数据,然后就阻塞于下一个read调用以等待此客户的其他数据。
5.pselect函数
#include <sys/select.h>
#include <signal.h>
#include <time.h>
int pselect(int maxfdp1, fd_set * readset, fd_set * writeset, fd_set * exceptset, const struct timespec * timeout, const sygset_t * sigmask);
struct timespec
{
time_t tv_sec;
long tv_nsec; // 1纳秒=10-9秒
};
函数pselect增加了第六个参数:指向信号掩码的指针。**这允许程序禁止递交某些信号,**测试由这些当前禁止的信号的信号处理程序所设置的全局变量,然后调用pselect,告诉它临时重置信号掩码。
6.poll函数
#include <poll.h>
int poll(struct pollfd * fdarray, unsigned long nfds, int timeout);
返回:准备好描述字的个数,0——超时,-1——出错
struct pollfd
{
int fd;
short events;//状态
short revents;//异常类型
};
7.epoll函数
#include <sys/epoll.h>
int epoll_create(int size);
//创建内核事件表。
epoll与poll,select差异
首先,epoll使用一组函数完成任务,而不是单个函数;
其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//操作epoll的内核事件表。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//epoll系列系统调用的主要接口,它在一段时间内等待一组文件描述符上的事件。
//成功:返回就绪的文件描述符的个数,-1出错并设置errno
LT和ET模式
第七章
getsockopt,setsocketopt函数
#include <sys/socket.h>
int getsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen);
int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t *optlen);
返回:0——OK,-1——出错
第八章
1.recvfrom,sendto函数
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void * buff, size_t nbytes, int flags, struct sockaddr * from, socklen_t * addrlen);
ssize_t sendto(int sockfd, const void * buff, size_t nbytes, int flags, const struct sockaddr * to, socklen_t addrlen);
两者均返回:读写字节——成功,-1——出错
2.UDP回射程序
用自己的话说,类似TCP,writen变成sendto,readline变成recvfrom
3.程序运行异常
- 数据报的丢失
- 验证接收到的响应
- 服务器进程未运行
4.UDP的connect函数
给UDP套接口调用connect,与TCP连接毫不相同:**没有三路握手过程。内核只是记录对方的IP地址和端口号,**它们包含在传递给connect的套接口地址结构中,并立即返回给调用进程。
5.再次调用的目的
给一个UDP套接口多次调用connect。对于已连接UDP套接口,进程可给那个套接口再次调用connect以达到下面两个目的之一:
1)指定新的IP地址和端口号;
2)断开套接口。 (设置套接口地址结构的地址族(为AF_UNSPEC)
第九章
1.小点
gethostbyname和gethostbyaddr在主机名字与IP地址间进行转换;
getservbyname和getservbyport在服务名字和端口号间进行转换。
2.gethostbyname函数
#include <netdb.h>
struct hostent * gethostbyname(const char *hostname);
返回:非空指针——成功,空指针——出错,同时设置h_errno
struct hostent
{
char * h_name;//主机名\0
char **h_aliases;//别名\0
int h_addrtype;//AF_INET
int h_length;//4
char **h_addr_list;//IP
};
#define h_addr h_addr_list[0]
3.gethostbyname2函数
#include <netdb.h>
struct hostent * gethostbyname2(const char *hostname, int family);
返回:非空指针——成功,空指针——出错,同时设置h_errno
4.gethostbyaddr函数
#include <netdb.h>
struct hostent * gethostbyaddr(const char *addr, size_t len, int family);
返回:非空指针——成功,空指针——出错,同时设置h_errno
5.uname,gethostname函数
函数uname返回当前主机的名字。它不是解析器库中的一部分,经常与函数gethostbyname一起用来确定本地主机的IP地址。
#include <sys/utsname.h>
int uname(struct utsname * name);
返回:非负值——成功,-1——出错
函数gethostname也返回当前主机的名字。
#include <unistd.h>
int gethostname(char * name,size_t namelen);
返回:0——成功,-1——出错
6.getservbyname,getservbyport函数
#include <netdb.h>
struct servent * getservbyname(const char *servname, const char * protoname);
返回:非空指针——成功,空指针——出错
struct servent
{
char * s_name;
char ** s_aliases;
int s_port;
char * s_proto;
};
函数getservbyport在给定端口号和可选协议后查找相应的服务。