预备知识
Socket
Socket 简介
- 是一个编程接口
- 是一种特殊的文件描述符(everything in Unix is a file)
- 并不仅限于 TCP/IP 协议
- 面向连接(Transmission Control Protocol - TCP/IP)
- 无连接(User Datagram Protocol -UDP 和 Inter-network Packet Exchange - IP)
Socket 类型
- 流式套接字(
SOCK_STREAM
)- 提供了一个面向连接、可靠的数据传输服务,数据无差错、无重复的发送且按发送顺序接收。内设置流量控制,避免数据流淹没慢的接收方。数据被看作是字节流,无长度限制。
- ==> TCP
- 数据报套接字(
SOCK_DGRAM
)- 提供无连接服务。数据包以独立数据包的形式被发送,不提供无差错保证,数据可能丢失或重复,顺序发送,可能乱序接收。
- ==> UDP
- 原始套接字(
SOCK_RAW
)- 可以对较低层次协议如IP、ICMP直接访问。
IP 地址
IP 地址是 Internet 中主机的唯一标识
- Internet 中的主机要与别的机器通信必须具有一个 IP 地址
- IP 地址为 32 位(IPv4)或者 128 位(IPv6)
- 每个数据包都必须携带目的 IP 地址和源 IP 地址,路由器依靠此信息为数据包选择路由
IPV4ip 地址的表示形式:点分十进制,例子:192.168.1.123
IP地址分类(最后都会转换为一个 32 位的无符号整数)
- A 类
0000 0000 - 0111 1111
0.x.x.x - 127.x.x.x
- B 类
1000 0000 - 1011 1111
128.x.x.x - 191.x.x.x
- C 类
1100 0000 - 1101 1111
192.x.x.x - 223.x.x.x
- D 类
1110 0000 - 1110 1111
224.x.x.x - 239.x.x.x
表示组播地址 - E 类
1111 0000 - 1111 1111
240.x.x.x - 255.x.x.x
属于保留测试 127.x.x.x
表示主机地址192.168.x.x
表示局域网 ip 地址192.168.1.x
为例192.168.1.0
表示网段、网络地址192.168.1.1
表示网关192.168.1.255
表示广播地址
子网掩码 表示主机的最大连接数
- A类
255.0.0.0
2~24 - B类
255.255.0.0
2~16 - C类
255.255.255.0
2~8
IP 地址的转换
#include <arpa/inet.h>
inet_addr()
- 将点分十进制 IP 地址转化为网络字节序的整型数据
in_addr_t inet_addr(const char *cp);
inet_ntoa()
- 将网络字节序的整型数据转化为点分十进制 IP 地址
char *inet_ntoa(struct in_addr in);
- 例子:
inet_addr("192.168.1.123");
端口号
端口号(vi /etc/services
查看已经占用的端口号)
- 为了区分一台主机接收到的数据包应该转交给哪个进程来进行处理,使用端口号来区别
- TCP 端口号与 UDP 端口号独立
- 端口号一般由 IANA (Internet Assigned Numbers Authority) 管理
- 众所周知端口:11023(1255之间为众所周知端口,256~1023端口通常由 UNIX 系统占用)
- 已登记端口:1024~49151
- 动态或私有端口:49152~65535
- 一般使用 6666 7777 8888 9999 10000 10001
- 套接字和端口
- 端到端通信数据包投递过程
- 一个比喻
字节序
不同类型 CPU 的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
- 小端序(little-endian) - 低序字节存储在低地址
- 将低字节存储在起始地址,称为“Little-Endian”字节序,Intel、AMD 等采用的是这种方式;
- 大端序(big-endian)- 高序字节存储在低地址
- 将高字节存储在起始地址,称为“Big-Endian”字节序,由 ARM、Motorola 等所采用
如何测试主机字节序
- 方法一:使用指针
int main(){
int a = 0x12345678;
char *pa = (char *)&a;
printf("a = %#x\n", a);
printf("*pa = %#x\n", *pa);
if(*pa == (a | 0xff))
puts("Small end storage\n");
else
puts("Big end storage\n");}
- 方法二:使用
file
命令
$ file a.out
a.out: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, BuildID[sha1]=ef89abc11bb84eb2fe2fdc11d51ccc6a8063cba1, not stripped
- 方法三:使用共用体
#include <stdio.h>
union HBO{
int a;
char b;
};
int main(){
union HBO myHBO;
myHBO.a = 0x12345678;
printf("%#x\n",myHBO.a);
printf("%#x\n", myHBO.b);
if(myHBO.b == (myHBO.a & 0xff))
puts("Least Significant Byte");
else
puts("Most Significant Byte");}
网络中传输的数据必须按网络字节序,即大端字节序
字节序转换函数
#include <arpa/inet.h>
- 将主机字节序转化为网络字节序
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
- 将网络字节序转化为主机字节序
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
- 例子:
htons(9999);
系统调用
TCP 服务器、客户端
TCP 服务器端流程
- 创建套接字
socket( )
- 填充服务器网络信息结构体
sockaddr_in
- 将套接字与服务器的网络信息结构体绑定
bind()
- 将套接字设置为被动监听模式
listen()
- 阻塞等待客户端的连接请求
accept()
- 进行通信
recv()
/send()
TCP 客户端流程
- 创建套接字
socket()
- 填充服务器网络信息结构体
sockaddr_in
- 发送客户端的连接请求
connect()
- 进行通信
send()
/recv()
网络编程相关 API
Socket
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol)
功能:创建一个套接字,返回一个文件描述符
参数:
domain
:通信域,协议族AF_UNIX
本地通信AF_INET
ipv4 网络协议AF_INET6
ipv6 网络协议AF_PACKET
底层的协议
type
:类型SOCK_STREAM
流式套接字 tcpSOCK_DGRAM
数据报套接字 UDPSOCK_RAW
底层的通信
protocol
:一般为 0
返回值:
- 成功:文件描述符
- 失败:
-1
例子:
int sockfd;
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
perror("fail to socket");
//return -1;
exit(1);
}
地址相关的数据结构
通用的(一般不用)
struct sockaddr {
sa_family_t sa_family; // 2 个字节
char sa_data[14]; // 14 个字节
}
一般使用的:sockaddr_in
#include <netinet/in.h>
struct sockaddr_in{
__SOCKADDR_COMMON (sin_);
#define __SOCKADDR_COMMON(sa_prefix) sa_family_t sa_prefix##family
- 在函数宏里面,
##<