OSI七层网络模型:
1.物理层:通过硬件设备将模拟信号转换为数字信号,于是有了0/1的数据流,叫做bit流
2.数据链路层:可以发比特流但是没有格式就会乱七八糟,于是就有了”帧”。采用了一种”帧”的数据块进行传输,为了确保数据通信的准确,实现数 据有效的差错控制,加入了检错等功能
3.网络层:前两层都是在于可以发数据,以及发的数据是否正确,然而如果连着两台电脑还行,多台电脑而又只想让其中一台可以通信,则需要路由 。选择性的发,那每台电脑就得有自己的身份,于是出现了IP协议等。
4.传输层:比特流传输的过程不可能会一直顺畅,偶尔出现中断很正常,如果人为制定出单位,分成一个个的信息段,从中又衍生了报文,结合上面几层,我们就可以有目标的发送正确数据给某台计算机了,传输层有两个重要的协议:TCP和UDP。TCP效率低但是发送包会校验是否完整,UDP效率高但是不管别人能否完整收到。
5.会话层:计算机收到了发送的数据,但是有那么多进程,具体哪个进程需要用到这个数据,则把他输送到那个进程。例如:如果80端口要用,所以系统内数据通信,将接收端口数据送至需求端口。
6.表示层:现在正确接收到了需要的数据,但是因为数据在传输过程中可能基于安全性,或者是算法上的压缩,还有就是网络类型不同。那就得有一个沟通的桥梁来整理整理,还原出原本应该有的表示,类似于一个拆快递的过程
7.应用层:是其他层对用户的已经封装好的接口,提供多种服务,用户只需操作应用层就可以得到服务内容,这样封装可以让更多的人能使用它。包含的主要协议:FTP(文件传送协议)、Telnet(远程登录协议)、DNS(域名解析协议)、SMTP(邮件传送协议),POP3协议(邮局协议),HTTP协议(Hyper Text Transfer Protocol)
socket编程:
TCP/UDP协议是工作在传输层的。
1.struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
2.typedef uint32_t in_addr_t; 网络内部用来表示IP地址的类型
3.两个很重要的结构体:struct sockaddr 和 struct sockaddr_in
4.struct sockaddr,这个结构体是网络编程接口中用来表示一个IP地址的,注意这个IP地址是不区分IPv4和IPv6的(或者说是兼容IPv4和IPv6的)
这个结构体是linux的网络编程接口中用来表示IP地址的标准结构体,bind、connect等函数中都需要这个结构体,这个结构体是兼容IPV4和IPV6的。在实际编程中这个结构体会被一个struct sockaddr_in或者一个struct sockaddr_in6所填充
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr
{
/地址族,就是一些协议类型的集合/
unsigned short sa_family;
/14 字节的协议地址,包含该 socket 的 IP 地址和端口号/
char sa_data[14];
};
struct sockaddr_in
{
short int sin_family; /地址族/
unsigned short int sin_port; /端口号/
struct in_addr sin_addr; /IP 地址/
unsigned char sin_zero[8];
};
5.sa_family 常见值如下:
AF_INET: IPv4 协议
AF_INET6: IPv6 协议
AF_LOCAL: UNIX 域协议
4.sudo tcpdump -iany -v tcp port 6003 tcp相关的调试工具
总结一下:
1.server端:
(1).创建socket
(2).bind
(3).listen
(4).accept: 这里会产生一个新的发送socket的fd
2.client端:
(1).socket
(2).connect
#include
#include
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
int listen(int sockfd, int backlog);
struct sockaddr
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
TCP编程实战
1.socket: 创建一个套接字
头文件:
#include
#include
原型:
int socket(int domain, int type, int protocol);
第一个参数: domain 协议族(地址族)
AF_INET: IPv4网络通信
AF_INET6: IPv6网络通信
AF_PACKET: 链路层通信
AF_UNIX / AF_LOCAl: 本地通信
第二个参数:type soket的类型
SOCK_STREAM:字节流套接字, 流式套接字提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的正确性和顺序性
SOCK_DGRAM:数据包套接字,数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用的数据报协议是 UDP
SOCK_RAW:原始套接字,原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发
SOCK_SEQPACKET:有序分组套接字
第三个参数:protocol 传输协议,
IPPROTO_TCP: TCP传输协议
IPPTOTO_UDP: UDP传输协议
IPPROTO_SCTP: SCTP传输协议
IPPROTO_TIPCTCP: TIPC传输协议
使用实例:socket(AF_INET, SOCK_STREAM, 0); 最后的参数protocol默认给0的话,内核会把它修改为:IPPROTO_TCP
2.bind: 将套接字绑定一个IP地址和端口号
int bind(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
第一个参数:socket_fd
第二个参数:struct sockaddr 结构体指针,我们一般填充的是struct sockaddr_in,然后再强转为struct sockaddr类型
第三个参数:第二个参数的大小,填充 sizeof(struct sockaddr)
3.listen: netstat -na,可以查看网络相关的状态
假设由socket函数创建的套接字为主动套接字,那么listen函数就是将主动套接字转为被动套接字,当调用connect的时候将使用client端的主动套接字向对端(服务器端)发起
连接请求,当服务器端有被动套接字时并满足必要的条件,就将客户端的主动套接字和服务器端的被动套接字进行连接。
第一个参数:socket_fd
(下面的完成连接,未完成连接都是指的是套接字)
第二个参数:未完成连接队列和已完成连接队列的上限
未完成连接队列:服务器还未完成3次握手全过程的一个队列
已完成连接队列:服务器已经完成3次握手的队列,accept函数就是从这个队列中去返回下一个套接字(就是从这个队列中取最近完成连接的套接字)
4.accept: 这个函数会阻塞进程,直到服务器的被动套接字被客户端的主动套接字连,这个函数就会返回
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:连接套接字,之后就是用这个套接字来收发数据的
第一个参数:bind所用的套接字
第二个参数:客户端地址结构体指针,这个是一个输入参数
第三个参数:客户端地址结构体大小
5.struct sockaddr_in 此结构体在#include
netstat -at: 查看所有的tcp协议连接信息
UDP编程实战:
1.服务器端:socket bind
然后使用recvfrom函数接收客户端发来的信息
2.客户端:socket
然后使用sendto函数向服务器端发送信息
总结一下:
1.send recv一般用在TCP协议中
2.sendto recvfrom一般用在UDP协议中
3.sendto 可以指定发送的目的地址和端号,send需要socket已经连接才可以使用,sendto可以用在
无连接的socket中。
TCP代码:
#include
#include
#include
#include
#include
#define SERVER_PORT (8090)
#define SERVER_IPADDR ("192.168.2.240")
#define NUM (5)
int main(void)
{
socklen_t len = 0;
int ret = -1;
int bindSocketFd = -1;
int acceptSocketFd = -1;
struct sockaddr_in serverAddr = {0};
struct sockaddr_in clientAddr = {0};
bindSocketFd = socket(AF_INET, SOCK_STREAM, 0);
if (bindSocketFd < 0)
{
perror("");
return -1;
}
fprintf(stderr, "bindSocketFd = %d\n", bindSocketFd);
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IPADDR);
ret = bind(bindSocketFd, (const struct sockaddr *)&serverAddr, sizeof(struct sockaddr));
if (ret)
{
fprintf(stderr, "bind fail\n");
return -1;
}
fprintf(stderr, "bind ok!\n");
ret = listen(bindSocketFd, NUM);
if (ret)
{
fprintf(stderr, "listen fail\n");
return -1;
}
fprintf(stderr, "listen ok!\n");
len = sizeof(struct sockaddr);
acceptSocketFd = accept(bindSocketFd, (struct sockaddr *)&clientAddr, &len);
if (acceptSocketFd < 0)
{
fprintf(stderr, "accept fail\n");
return -1;
}
fprintf(stderr, "accept ok\n");
send(acceptSocketFd, "123", 3, 0);
return 0;
}
#include
#include
#include
#include
#define SERVER_PORT (8090)
#define SERVER_IPADDR ("192.168.2.240")
int main(void)
{
int ret = -1;
char buff[512] = {0};
struct sockaddr_in serverAddr = {0};
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (socket_fd < 0)
{
perror("");
return -1;
}
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(SERVER_PORT);
serverAddr.sin_addr.s_addr = inet_addr(SERVER_IPADDR);
ret = connect(socket_fd, (const struct sockaddr *)&serverAddr, (socklen_t)sizeof(struct sockaddr));
if (ret < 0)
{
perror("");
return -1;
}
recv(socket_fd, buff, 3, 0);
fprintf(stderr, "recv: %s\n", buff);
return 0;
}
UDP代码:
#include
#include
#include
#include
#include
#define SERVER_IP "192.168.1.188"
#define SERVER_PORT 8888
#define BUFF_LEN 1024
/*
server:
socket-->bind-->recvfrom-->sendto-->close
*/
char buf[BUFF_LEN];
int main(int argc, char* argv[])
{
int server_fd, ret;
socklen_t len;
struct sockaddr_in ser_addr;
struct sockaddr_in clent_addr;
server_fd = socket(AF_INET, SOCK_DGRAM, 0); //AF_INET:IPV4;SOCK_DGRAM:UDP
if(server_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
// ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
ser_addr.sin_addr.s_addr = inet_addr(SERVER_IP);;
//IP地址,需要进行网络序转换,INADDR_ANY:本地地址
ser_addr.sin_port = htons(SERVER_PORT); //端口号,需要网络序转换
ret = bind(server_fd, (struct sockaddr*)&ser_addr, sizeof(ser_addr));
if(ret < 0)
{
printf("socket bind fail!\n");
return -1;
}
//handle_udp_msg(server_fd); //处理接收到的数据
while (1)
{
memset(buf, 0, BUFF_LEN);
len = sizeof(clent_addr);
ret = recvfrom(server_fd, buf, BUFF_LEN, 0, (struct sockaddr*)&clent_addr, &len); //recvfrom是拥塞函数,没有数据就一直拥塞
if(ret == -1)
{
printf("recieve data fail!\n");
return;
}
printf("recv:%s\n",buf); //打印client发过来的信息
}
close(server_fd);
return 0;
}
#include
#include
#include
#include
#include
#define SERVER_PORT 8888
#define BUFF_LEN 512
#define SERVER_IP "192.168.1.188"
void client_udp_sned(int fd, struct sockaddr* dst)
{
socklen_t len;
struct sockaddr_in src;
while(1)
{
char buf[BUFF_LEN] = "TEST UDP MSG!\n";
len = sizeof(*dst);
printf("client:%s\n",buf); //打印自己发送的信息
sendto(fd, buf, BUFF_LEN, 0, dst, len);
sleep(1); //一秒发送一次消息
}
}
/*
client:
socket-->sendto-->revcfrom-->close
*/
int main(int argc, char* argv[])
{
int client_fd;
struct sockaddr_in ser_addr;
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if(client_fd < 0)
{
printf("create socket fail!\n");
return -1;
}
memset(&ser_addr, 0, sizeof(ser_addr));
ser_addr.sin_family = AF_INET;
ser_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
//ser_addr.sin_addr.s_addr = htonl(INADDR_ANY); //注意网络序转换
ser_addr.sin_port = htons(SERVER_PORT); //注意网络序转换
client_udp_sned(client_fd, (struct sockaddr*)&ser_addr);
close(client_fd);
return 0;
}