TCP/IP协议族的体系结构:
TCP/IP协议是Internet事实上的工业标准
一共有四层
TCP/IP协议通信模型:
TCP/IP协议下的数据包:
Ethernet header(14Bytes)---IP header(20Bytes)---TCP header(20Bytes)---data(MAX:1460Bytes)---Ethernet trailer(4Bytes)
数据的封装与传递过程:
TCP/IP结构:
TCP协议特点:
TCP(即传输控制协议):是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
适用情况:
1.适合于对传输质量要求较高,以及传输大量数据的通信。
2.在需要可靠数据传输的场合,通常使用TCP协议
3.MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
UDP协议的特点:
UDP(User Datagram Protocol)用户数据报协议,是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。
适用情况:
1.发送小尺寸数据(如对DNS服务器进行IP地址查询时)
2.在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)
3.适合于广播/组播式通信中。
4.MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议
5.流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
TCP/IP网络编程预备知识:
Socket
IP地址
端口号
字节序
Socket 简介:
是一个编程接口
是一种特殊的文件描述符 (everything in Unix is a file)
并不仅限于TCP/IP协议
面向连接 (Transmission Control Protocol - TCP/IP)
无连接 (User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IPX)
Socket类型:
流式套接字(SOCK_STREAM)---提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。(TCP)
数据报套接字(SOCK_DGRAM)---提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。(UDP)
原始套接字(SOCK_RAW)---可以对较低层次协议如IP、ICMP直接访问。
Socket的位置:
IP地址:
IP地址是Internet中主机的标识
Internet中的主机要与别的机器通信必须具有一个IP地址
IP地址为32位(IPv4)或者128位(IPv6)
每个数据包都必须携带目的IP地址和源IP地址,路由器依靠此信息为数据包选择路由
表示形式:常用点分形式,如202.38.64.10,最后都会转换为一个32位的无符号整数。
IP地址分类
子网掩码
端口号:
为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
TCP端口号与UDP端口号独立
端口号一般由IANA (Internet Assigned Numbers Authority) 管理
众所周知端口:1~1023(1~255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
注册端口:1024~49150
动态或私有端口:49151~65535
字节序:
不同类型CPU的主机中,内存存储多字节整数序列(因为字符型只有一个字节,故不影响字符型)有两种方法,称为主机字节序(HBO):
小端序(little-endian) - 低序字节存储在低地址
---将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD等采用的是这种方式;
大端序(big-endian)- 高序字节存储在低地址
---将高字节存储在起始地址,称为“Big-Endian”字节序,由ARM、Motorola等所采用
网络中传输的数据必须按网络字节序,即大端字节序
在大部分PC机上,当应用进程将整数送入socket前,需要转化成网络字节序;当应用进程从socket取出整数后,要转化成小端字节序
网络字节序(NBO - Network Byte Order)
---使用统一的字节顺序,避免兼容性问题
主机字节序(HBO - Host Byte Order)
---不同的机器HBO是不一样的,这与CPU的设计有关
---Motorola 68K系列、ARM系列,HBO与NBO是一致的
---Intel X86系列,HBO与NBO不一致
大端(Big-Endian):字节的高位在内存中放在存储单元的起始位置
小端(Little-Endian):与大端相反
字节序转换函数:
把给定系统所采用的字节序称为主机字节序。为了避免不同类别主机之间在数据交换时由于对于字节序的不同而导致的差错,引入了网络字节序。
主机字节序到网络字节序
u_long htonl (u_long hostlong);
u_short htons (u_short short);
网络字节序到主机字节序
u_long ntohl (u_long hostlong);
u_short ntohs (u_short short);
IP地址的转换函数(详细讲解见下文):
#include <arpa/inet.h>
int inet_aton(const char *strptr, struct in_addr *addrptr); //将strptr所指的字符串转换成32位的网络字节序二进制值
int_addr_t inet_addr(const char *strptr); //功能同上,返回转换后的地址。
char *inet_ntoa(stuct in_addr inaddr); //将32位网络字节序二进制地址转换成点分十进制的字符串。
int inet_pton(int af, const char *src, void *dst); //将IPV4/IPV6的地址转换成binary格式
TCP/IP网络编程:
预备知识
系统调用
TCP编程/UDP编程 API
网络封包格式和IP,TCP头
TCP握手过程
I/O模型和服务器模型
网络调试和协议分析—wireshark和tcpdump 使用
网络编程相关API:
网络编程常用函数(函数具体用法见下文)
socket() 创建套接字
bind() 绑定本机地址和端口
connect() 建立连接
listen() 设置监听端口
accept() 接受TCP连接
recv(), read(), recvfrom() 数据接收
send(), write(), sendto() 数据发送
close(), shutdown() 关闭套接字
网络编程常用函数---socket:
int socket (int domain, int type, int protocol); //创建套接字
domain 是地址族
---AF_INET // internet 协议
---AF_UNIX // unix internal协议
---AF_NS // Xerox NS协议
---AF_IMPLINK // Interface Message协议
type // 套接字类型
---SOCK_STREAM // 流式套接字(TCP)
---SOCK_DGRAM // 数据报套接字(UDP)
---SOCK_RAW // 原始套接字
protocol 参数通常置为0
地址相关的数据结构:
通用地址结构
struct sockaddr
{
u_short sa_family; // 地址族, AF_xxx
char sa_data[14]; // 14字节协议地址
};
Internet协议地址结构
struct sockaddr_in
{
u_short sin_family; // 地址族, AF_INET,2 bytes
u_short sin_port; // 端口,2 bytes
struct in_addr sin_addr; // IPV4地址,4 bytes
char sin_zero[8]; // 8 bytes unused,作为填充
};
IPv4地址结构
// internet address
struct in_addr
{
in_addr_t s_addr; // u32 network address
};
网络编程常用函数---bind:
int bind (int sockfd, struct sockaddr* addr, int addrLen); //绑定本机地址和端口
sockfd 由socket() 调用返回
addr 是指向 sockaddr_in 结构的指针,包含本机IP 地址和端口号
---struct sockaddr_in
u_short sin_family // protocol family
u_short sin_port // port number
struct in_addr sin_addr //IP address (32-bits)
addrLen : sizeof (struct sockaddr_in)
地址结构的一般用法:
1.定义一个struct sockaddr_in类型的变量并清空
struct sockaddr_in myaddr;
memset(&myaddr, 0, sizeof(myaddr));
//bzero(&myaddr,sizeof(myaddr));
2.填充地址信息
myaddr.sin_family = PF_INET;
myaddr.sin_port = htons(8888);
myaddr.sin_addr.s_addr = inet_addr(“192.168.1.100”);
//sin.sin_addr.s_addr = htonl (INADDR_ANY); //让服务器程序能绑定在任意的IP上
3.将该变量强制转换为struct sockaddr类型在函数中使用
bind(listenfd, (struct sockaddr*)(&myaddr), sizeof(myaddr));
地址转换函数:
unsigned long inet_addr(char *address);
address是以’\0’结尾的点分IPv4字符串。该函数返回32位的地址。如果字符串包含的不是合法的IP地址,则函数返回-1。例如:
struct in_addr addr;
addr.s_addr = inet_addr(" 192.168.1.100 ");
char* inet_ntoa(struct in_addr address);
address是IPv4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL
网络编程常用函数---listen :
int listen (int sockfd, int backlog); //设置监听端口
sockfd:监听连接的套接字
backlog 指定了正在等待连接的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。 DoS(拒绝服务)攻击即利用了这个原理,非法的连接占用了全部的连接数,造成正常的连接请求被拒绝。
返回值: 0 或 -1
注:完成listen()调用后,socket变成了监听socket(listening socket)。
网络编程常用函数---accept :
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); //接受TCP连接
返回值:已建立好连接的套接字或-1
sockfd : 监听套接字
addr : 对方地址
addrlen:addr长度地址
注:listen()和accept()是TCP服务器端使用的函数
网络编程常用函数---connect:
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
返回值:0 或 -1
sockfd : socket返回的文件描述符
serv_addr : 服务器端的地址信息
addrlen : serv_addr的长度
注:connect()是客户端使用的系统调用。
网络编程常用函数---send:
#include <sys/socket.h>
ssize_t send(int socket, const void *buffer, size_t length, int flags); //数据发送
返回值:
---成功:实际发送的字节数
---失败:-1, 并设置errno
buffer : 发送缓冲区首地址
length : 发送的字节数
flags : 发送方式(通常为0)
网络编程常用函数---recv:
#include <sys/socket.h>
ssize_t recv(int socket, void *buffer, size_t length, int flags); //接收数据
返回值:
---成功:实际接收的字节数
---失败:-1, 并设置errno
buffer : 接收缓冲区首地址
length : 接收的字节数
flags : 接收方式(通常为0)
网络编程常用函数---read()/write():
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
read()和write()经常会代替recv()和send(),通常情况下,看程序员的偏好
使用read()/write()和recv()/send()时最好统一
读操作:
bzero(buf, BUFSIZ);
do {
ret = read(newfd, buf, BUFSIZ-1);
}while(ret < 0 && EINTR == errno); //由于信号中断返回,没有任何数据可用
if (ret < 0) {
perror("read");
exit(1);
}
if(!ret) { //对方已经关闭
break;
}
写操作:
bzero(buf, BUFSIZ);
if( fgets(buf, BUFSIZ-1, stdin) ==NULL) {
continue;
}
do {
ret = write(fd, buf, strlen(buf));
}while(ret < 0 && EINTR == errno); //由于信号中断返回,没有任何数据可用
网络编程常用函数---sendto()/recvfrom():
ssize_t sendto(int socket, void *message, size_t length, int flags, struct sockaddr *dest_addr, socklen_t dest_len);
ssize_t recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len);
这两个函数一般在使用UDP协议时使用
套接字的关闭:
int close(int sockfd); //关闭双向通讯
int shutdown(int sockfd, int howto);
---TCP连接是双向的(是可读写的),当我们使用close时,会把读写通道都关闭,有时侯我们希望只关闭一个方向,这个时候我们可以使用shutdown。
---针对不同的howto,系统回采取不同的关闭方式。
howto = 0 关闭读通道,但是可以继续往套接字写数据。
howto = 1 和上面相反,关闭写通道。只能从套接字读取数据。
howto = 2 关闭读写通道,和close()一样
TCP编程流程图:
TCP循环服务器模型:
TCP循环服务器模型:
socket(...);
bind(...);
listen(...);
while(1){
accept(...);
process(...);
close(...);
}
TCP服务器一般很少用
TCP多进程并发服务器:
TCP多进程并发服务器:
socket(...);
bind(...);
listen(...);
while(1){
accpet(...);
if(fork(...) == 0) {
process(...);
close(...);
exit(...);
}
close(...);
}
TCP并发服务器的思想是每一个客户机的请求并不由服务器直接处理,而是由服务器创建一个子进程来处理。
TCP多线程服务器:
TCP多线程服务器:
socket(...);
bind(...);
listen(...);
while(1){
accpet(...);
if((pthread_create(...))!==-1) {
process(...);
close(...);
exit(...);
}
close(...);
}
多线程服务器是对多进程的服务器的改进
UDP编程流程图:
UDP循环服务器模型:
UDP循环服务器模型:
socket(...);
bind(...);
while(1){
recvfrom(...);
process(...);
sendto(...);
}