1、TCP的三次握手 客户端 connec函数调用的时候发起
多播:
数据的收发仅仅在同一分组中进行
多播的特点:
1、多播地址标示一组接口
2、多播可以用于广域网使用
在IPv4中,多播是可选的
Pv4的D类地址是多播地址
十进制:224.0.0.1 ~ 239.255.255.254 任意一个IP地址 都代表一个多播组
十六进制:E0.00.00.01 à EF.FF.FF.FE
总结:1、主机先加入多播组 2、往多播组发送数据
在IPv4因特网域(AF_INET)中,多播地址结构体用如下结构体ip_mreq表示
- int setsockopt(int sockfd, int level,int optname,
- const void *optval, socklen_t optlen);
- 成功执行返回0,否则返回-1
level | optname | 说明 | optval类型 |
IPPROTO_IP | IP_ADD_MEMBERSHIP | 加入多播组 | ip_mreq{} |
IP_DROP_MEMBERSHIP | 离开多播组 | ip_mreq{} |
只能将自己加入到某个多播组
- #include<stdio.h>
- #include<string.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
- //将主机 加入到多播组 224.0.0.2 接受
- int main()
- {
- int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
-
- //让sockfd有一个固定的IP端口
- struct sockaddr_in my_addr;
- bzero(&my_addr,sizeof(my_addr));
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(8000);
- my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(sockfd, (struct sockaddr *)&my_addr,sizeof(my_addr));
-
- //将192.168.0.111 加入到多播组 224.0.0.2中
- struct ip_mreq mreq;
- mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.2");
- mreq.imr_interface.s_addr = htonl(INADDR_ANY);
- setsockopt(sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,sizeof(mreq));
-
- while(1)
- {
- unsigned char buf[1500]="";
- recvfrom(sockfd,buf,sizeof(buf), 0,NULL,NULL);
- printf("buf=%s\n", buf);
- }
-
- close(sockfd);
- return 0;
- }
运行结果:
TCP回顾
1、面向连接的流式协议;可靠、出错重传、且每收到一个数据都要给出相应的确认
2、通信之前需要建立链接
3、服务器被动链接,客户端是主动链接
TCP与UDP的差异:
TCP C/S架构
1、知道“服务器”的ip、port
2、Connect主动连接“服务器”
3、需要用到的函数
socket—创建“主动TCP套接字”
connect—连接“服务器”
send—发送数据到“服务器”
recv—接受“服务器”的响应
close—关闭连接
int connect(int sockfd,const struct sockaddr *addr,socklen_t len);
功能:
主动跟服务器建立链接
参数:
sockfd:socket套接字
addr: 连接的服务器地址结构
len: 地址结构体长度
返回值:
成功:0 失败:其他
注意:
1、connect建立连接之后不会产生新的套接字
2、连接成功后才可以开始传输TCP数据
3、头文件:#include
ssize_t send(int sockfd, const void* buf,size_t nbytes, int flags);
功能:
用于发送数据
参数:
sockfd: 已建立连接的套接字
buf: 发送数据的地址
nbytes: 发送缓数据的大小(以字节为单位)
flags: 套接字标志(常为0)
返回值:
成功发送的字节数
头文件:
#include
ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
功能:
用于接收网络数据
参数:
sockfd:套接字
buf: 接收网络数据的缓冲区的地址
nbytes: 接收缓冲区的大小(以字节为单位)
flags: 套接字标志(常为0)
返回值:
成功接收到字节数
头文件:
#include
案例:TCP客户端
- #include<stdio.h>
- #include<string.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
- //TCP客户端
- int main()
- {
- //创建一个TCP套接字 SOCK_STREAM
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
- printf("sockfd = %d\n", sockfd);
-
- //bind是可选的
- struct sockaddr_in my_addr;
- bzero(&my_addr,sizeof(my_addr));
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(9000);
- my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(sockfd,(struct sockaddr *)&my_addr,sizeof(my_addr));
-
- //connect链接服务器
- struct sockaddr_in ser_addr;
- bzero(&ser_addr,sizeof(ser_addr));
- ser_addr.sin_family = AF_INET;
- ser_addr.sin_port = htons(8000);//服务器的端口
- ser_addr.sin_addr.s_addr = inet_addr("192.168.0.110");//服务器的IP
- //如果sockfd没有绑定固定的IP以及端口 在调用connect时候 系统给sockfd分配自身IP以及随机端口
- connect(sockfd, (struct sockaddr *)&ser_addr,sizeof(ser_addr));
-
- //给服务器发送数据 send
- printf("发送的消息:");
- char buf[128]="";
- fgets(buf,sizeof(buf),stdin);
- buf[strlen(buf)-1]=0;
- send(sockfd, buf, strlen(buf), 0);
-
- //接收服务器数据 recv
- char msg[128]="";
- recv(sockfd, msg,sizeof(msg), 0);
- printf("服务器的应答:%s\n", msg);
-
- //关闭套接字
- close(sockfd);
- return 0;
- }
运行结果:
1、具备一个可以确知的地址 bind
2、让操作系统知道是一个服务器,而不是客户端 listen
3、等待连接的到来 accpet
对于面向连接的TCP协议来说,连接的建立才真正意味着数据通信的开始.
int listen(int sockfd, int backlog);
功能:
将套接字由主动修改为被动。
使操作系统为该套接字设置一个连接队列,用来记录所有连接到该套接字的连接。
参数:
sockfd: socket监听套接字
backlog:连接队列的长度
返回值:
成功:返回0
int accept(int sockfd,struct sockaddr *cliaddr, socklen_t *addrlen);
功能:
从已连接队列中取出一个已经建立的连接,如果没有任何连接可用,则进入睡眠等待(阻塞)
参数:
sockfd: socket监听套接字
cliaddr: 用于存放客户端套接字地址结构
addrlen:套接字地址结构体长度的地址
返回值:
已连接套接字
头文件:
#include
注意:
返回的是一个已连接套接字,这个套接字代表当前这个连接
- #include<stdio.h>
- #include<string.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <arpa/inet.h>
- #include <fcntl.h>
-
- int main()
- {
- //1、创建一个tcp监听套接字
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
- //2、使用bind函数 给监听套接字 绑定固定的ip以及端口
- struct sockaddr_in my_addr;
- bzero(&my_addr,sizeof(my_addr));
- my_addr.sin_family = AF_INET;
- my_addr.sin_port = htons(8000);
- my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(sockfd, (struct sockaddr *)&my_addr, sizeof(my_addr));
-
- //3、使用listen创建连接队列 主动变被动
- listen(sockfd, 10);
-
- //4、使用accpet函数从连接队列中 提取已完成的连接 得到已连接套接字
- struct sockaddr_in cli_addr;
- socklen_t cli_len = sizeof(cli_addr);
- int new_fd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
- //new_fd代表的是客户端的连接 cli_addr存储是客户端的信息
- char ip[16]="";
- inet_ntop(AF_INET,&cli_addr.sin_addr.s_addr, ip,16);
- printf("客户端:%s:%hu连接了服务器\n",ip,ntohs(cli_addr.sin_port));
-
- //5、获取客户端的请求 以及 回应客户端
- char buf[128]="";
- recv(new_fd, buf,sizeof(buf),0);
- printf("客户端的请求为:%s\n",buf);
-
- send(new_fd,"recv", strlen("recv"), 0);
-
-
- //6、关闭已连接套接字
- close(new_fd);
-
- //7、关闭监听套接字
- close(sockfd);
-
- return 0;
- }
运行结果:
SYN是一个链接请求:是TCP报文中的某一个二进制位
第一次握手:客户端 发送SYN请求 链接服务器
第二次握手:服务器 ACK回应客户端的链接请求 同时 服务器给客户端发出链接请求
第三次握手:客户端 ACK回应 服务器的链接请求
不缺分客户端 或 服务器先后问题。
第一次挥手:A调用close 激发底层 发送FIN关闭请求 并且A处于半关闭状态
第二次挥手:B的底层给A回应ACK 同时导致B的应用层recv/read收到0长度数据包
第三次挥手:B调用close 激发底层给A发送FIN关闭请求 并且B处于半关闭状态
第四次挥手:A的底层给B回应ACK 同时 A处于完全关闭状态,B收到A的ACK也处于完全关闭状态