端口号/IP地址/MAC地址
16位端口号用来标识一台主机上的唯一进程
32位IP地址用来标识网络环境中的一个主机
48位MAC地址指的是网卡的硬件地址
端口号+IP地址:在网络环境中标识唯一进程
网络模型
1.ISO:应用层,表示层,会话层,传输层,网络层,数据链路层,物理层
2.TCP/IP模型结构:
1.应用层(给用户提供协议):FTP(文件传输), HTTP(超文本传输)
2.传输层(传输数据):TCP(传输控制协议), UDP(用户数据报协议)
3.网络层(建立虚拟路径):IP协议(确定路由) ARP(地址解析协议)
4.网络接口层/链路层(将数据分成帧并传送):以太网帧协议
TCP协议
TCP(传输控制协议)是一个复杂,可靠,且面向连接的传输层字节流协议.
特点
1.提供数据的可靠递送,故障的可靠通知(超时和重传)
2.动态估算数据往返时间(RTT)以确定等待一个确认需要多长时间
3.通过给每字节关联序列号对数据进行排序
4.提供滑动窗口(通告窗口)来实现流量控制,告知对端在任何时刻下它一次能从对端接收多少字节的数据以防止因缓冲区溢出导致数据丢失
5.全双工,也可转换成单双工
IP协议
版本:IPv4/IPv6
TTL(time to live):设置数据包在路由节点中跳转上限
协议:0x06为TCP,0x11为UDP
ARP协议
根据IP地址获取MAC地址
以太网首部的硬件地址为FF:FF:FF:FF:FF:FF表示广播
硬件类型指链路层网络类型,1为以太网
帧类型为0806表示ARP请求
协议类型指要转换的地址类型,0x0800为IP地址
op字段为1表示ARP请求,2表示ARP应答
以太网帧协议
根据MAC地址完成数据包传输
数据封装
没有进行封装的数据不能在网络中传输
字节序转换
根据多字节字段在内存中存储顺序不同分为大小端字节序,通常把给定系统所用的字节序称为”主机字节序“.
因为两种格式都有系统使用,所以网络协议需要指定一个”网络字节序“来制定一个标准(大端字节序)使得多字节字段各个字节的传送顺序达成一致.
#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);
IP地址转换
一般我们看到的”192.168.1.1“的IP地址为点分十进制字符串,而在网络通信中需要的是二进制数值
下面两个函数对于IPv4地址和IPv6地址都适用
#include <arpa/inet.h>
//点分十进制字符串转网络字节序二进制数值
int inet_pton(int family, const char *strptr, void *addrptr);
//相反
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
TCP通信时序
三次握手
四次挥手
基本TCP socket编程
套接字内部由内核借助两个(读/写)缓冲区实现。
1.基本模式
2.地址结构
struct sockaddr {
unsigned short sa_family;
char sa_data[14]
};
struct sockaddr_in {
unsigned short sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
struct in_addr {
unsigned long s_addr;
};
eg:
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
int a;
inet_ntop(AF_INET, "127.0.0.1",(void *)&a);
addr.sin_addr.s_addr = a;
addr.sin_addr.s_addr = htonl(INADDR_ANY); 取出系统中任意有效的二进制IP地址
3.相关函数
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
创建套接字
domain(协议族): AF_INET/AF_INET6/AF_UNIX
type(套接字类型): SOCK_STREAM/SOCK_DGRAM
protocol(协议类型): 0
return a sockfd : success
-1 : error
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
给指定套接字绑定地址结构
return 0 : success
-1 : error
int listen(int sockfd, int backlog);
将该套接字转为监听套接字,监听连接请求,设置最大连接数量
return 0 : success
-1 : error
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
阻塞等待连接请求
得到请求后建立新的套接字用于通信
sockfd: server_sockfd(监听套接字)
addr: client_addr
addrlen: sizeof(client_addr)
return a new sockfd(已连接套接字) to communicate : success
-1 : error
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
发送连接请求
sockfd: client_sockfd
addr: server_addr
addrlen: sizeof(server_addr)
return 0 : success
-1 : error
#include <unistd.h>
int close(int sockfd);
关闭连接
return 0 : success
-1 : error
ssize_t read(int fd, void *buf, size_t nbytes)
ssize_t write(int fd, const void *buf, size_t n)
return 实际读/写的字节数:success
-1:error
4.简单应用:实现输入转小写
服务器端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
struct sockaddr_in addr, caddr;
char buf[BUFSIZ];
int ret, i;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = htonl(INADDR_ANY);
int s_sock_fd = socket(AF_INET, SOCK_STREAM,0);
bind(s_sock_fd, (struct sockaddr* )&addr, sizeof(addr));
listen(s_sock_fd, 20);
socklen_t caddr_len = sizeof(caddr_len);
int c_sock_fd = accept(s_sock_fd, (struct sockaddr*)&caddr,&caddr_len);
while(1){
ret = read(c_sock_fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
for(i = 0; i < sizeof(buf); i++)
buf[i] = toupper(buf[i]);
write(c_sock_fd, buf, ret);
}
close(s_sock_fd);
close(c_sock_fd);
return 0;
}
可通过 nc 命令测试服务器端
nc 127.0.0.1 端口号
客户端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <arpa/inet.h>
int main()
{
struct sockaddr_in caddr, saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);
int c_sock_fd = socket(AF_INET, SOCK_STREAM, 0);
int ret = connect(c_sock_fd, (struct sockaddr* )&saddr, sizeof(saddr));
int ccc = 10;
char buf[BUFSIZ];
while(ccc--)
{
write(c_sock_fd, "hello", 5);
ret = read(c_sock_fd, buf, sizeof(buf));
write(STDOUT_FILENO, buf, ret);
}
return 0;
}