1、TCP/UDP 的概念
TCP:
- TCP(Transmission Control Protocol)协议指的是传输控制协议,是一个面向连接的传输协议,他是一个能提供高可靠性的通信协议,所谓高可靠性指的是数据无丢失、数据无误、数据无重到达
适用场景
- 适用于对传输质量要求较高,以及传输大量数据的通信
- 在需要传输可靠数据的场合通常会选择使用TCP通信协议
- 比如QQ、微信、支付宝等通信软件的账户登录和支付宝相关功能是通常采用可靠的TCP通信协议来实现
UDP:
-
UDP(User Datagram Protocol)指的是用户数据报协议,是一种不可靠的无连接的协议,在数据发送前,不需要提前建立连接,所以可以更高效的传输数据
-
适用场景:
- 发送小尺寸的数据(列如对DNS服务器进行地址查询或者路由器更新路由表)
- 在收到数据,给出应答比较困难的网络中适用于UDP(比如无线网络)
- 适用于广播/组播式通信
- QQ/微信等即时通信软件地点对点文件通讯以及音视频通话时
- 流媒体、VolOP、IPTV等网络多媒体服务
2、API接口
1、网络套接字
int socket(int domain, int type, int protocol);
参数分析:
domain:域
AF_INET/PF_INET:网际协议
AF_UNIX/PF_UNIX:本地协议,可写成AF_LOCAL/PF_LOCAL
type:类型
SOCK_STREAM:流式套接字 TCP协议
SOCK_AGRAM:数据报套接字 UDP协议
protocol:协议
一般为0
返回值:
成功:待连接套接字
失败:-1
2、绑定地址
struct sockaddr//通用IP信息结构体
{
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in//IPV4地址结构体
{
u_short sin_family;//地址簇
u_short sin_port;//端口
struct in_addr sin_addr;//IPV4地址
char sin_zero[8];
};
struct in_addr
{
in_addr_t s_addr;//无符号32位网络地址
};
3、UDP通信 API
1、UDP发送数据
ssize_t sendto((int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
参数分析:
sockfd:UDP套接字
buf:即将发送的数据
len:数据的长度
flags:发送标志,与函数send的flag一致
dest_addr:对端网络地址
addr_len:地址长度
返回值:
成功:已发送字节数
失败:-1
2、UDP接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
参数分析:
sockfd:UDP套接字
buf:存储数据缓冲区
len:缓冲区大小
flags:接受标志,与函数send的flag一致
src_addr:对端网络地址
addrlen:地址长度
返回值:
成功:已接受字节数
失败:-1
3、UDP通信流程:
练习:尝试自行写一个UDP进行通信的代码(两个程序一个服务器,一个客户端)
服务端:server.c
#include "stdio.h"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//采用UDP通信
//绑定套接字
struct sockaddr_in serveraddr, clientaddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(50001);
serveraddr.sin_addr.s_addr = inet_addr("172.26.178.93");
bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
char buf[100] = {0};
socklen_t addrle = sizeof(clientaddr);
while(1)
{
bzero(buf, 100);
recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&clientaddr, &addrle);
printf("接收到数据:%s\n", buf);
}
}
客户端:client.c
#include "stdio.h"
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
//创建套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);//采用UDP通信
//绑定套接字
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(50001);
serveraddr.sin_addr.s_addr = inet_addr("172.26.178.93");
bind(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
char buf[100] = {0};
socklen_t addrle = sizeof(serveraddr);
while(1)
{
bzero(buf, 100);
fgets(buf, sizeof(buf), stdin);
sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&serveraddr,sizeof(serveraddr));
}
}
4、TCP通信 API
1、监听套接字
-
将连接套接字设置为监听套接字,并设置最大同时接收连接请求的个数:
int listen(int sockfd, int backlog); 参数分析: sockfd:待连接套接字 backlog:最大同时连接请求个数 返回值: 成功:0,并将socket设置为监听套接字 失败:-1
注意:由于历史原因,各系统对backlog的理解不一致,以Linux为例,监听端能同时接收的最大的连接请求个数为backlog + 4
2、等待连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数分析:
sockfd:监听套接字
addr:通用结构体地址,用于存储对端地址(IP + PORT)
addrlen:参数addr的存储区域大小
返回值:
成功:已连接套接字(非负整数)
失败:-1
注意:
- 该函数是用来等待客户连接的,如果有新的客户端连接上来,那么该函数会返回一个新的sockfd作为该客户端的连接套接字
- 如果把accept写在循环体内部,该函数会造成阻塞。如果同时有多个客户端连接那么该函数会返回这些客户所对应的多个已连接套接字
3、TCP发送
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数分析:
socket:已连接套接字
buf:即将被发送的数据
len:数据长度
flags:发送标志
MSG_NOSIGNAL:当对端已关闭时,不产生SIGPIPE信号
MSG_OOB:发送紧急数据, 只针对TCP连接
返回值:
成功:已发送套接字
失败:-1
4、TCP接收
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数分析:
sockfd:已连接套接字
buf:存储数据缓冲区
len:缓冲区大小
flags:接受标志
MSG_OOB:接受紧急数据
返回值:
成功:已接受字节数
失败:-1
5、连接对端监听套接字
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数分析:
sockfd:已连接套接字
addr:包含对端地址的(IP + PORT)通用地址结构体指针
addrlen:地址结构体大小
返回值:
成功:0
失败:-1
6、TCP通信流程
练习:使用TCP 实现一下两个程序互相聊条(有接收也有发送的操作)
客户端:server.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
int conn_fd;
typedef struct
{
int fd;
unsigned short int cont_port;
}connect_fd;
void *func(void *arg)//port
{
connect_fd fd = *(connect_fd *)arg;
while(1)
{
printf("[%u]连接成功\n",fd.cont_port);
char msg[100];
bzero(msg,100);
recv(fd.fd,msg,100,0);
if(strcmp(msg, "quit\n") == 0)//quit
{
printf("[%u]断开连接\n",fd.cont_port);
break;
}
printf("[%u]recv:%s\n", fd.cont_port, msg);
}
}
int main()
{
pthread_t tid;
//创建套接字
int sock_fd = socket(PF_INET,SOCK_STREAM,0);
//绑定地址(IP+PORT)
struct sockaddr_in srvaddr;
srvaddr.sin_family = PF_INET;//地址族
//端口
srvaddr.sin_port = htons(55551); //端口一般取50000以上
//IP地址
srvaddr.sin_addr.s_addr = inet_addr("172.23.205.172");//将文本字符串转换为32位无符号网络地址
bind(sock_fd,(struct sockaddr *)&srvaddr,sizeof(srvaddr));
//设置监听套接字
listen(sock_fd,4);
while(1)
{
struct sockaddr_in caddr;
//无符号的32位短整型
//unsigned short int
socklen_t addrlen = sizeof(caddr);
//等待客户端连接
printf("等待连接\n");
conn_fd = accept(sock_fd,(struct sockaddr *)&caddr,&addrlen);
unsigned short int duankou = ntohs(caddr.sin_port);
//线程信息
connect_fd msg;
msg.cont_port = duankou;
msg.fd = conn_fd;
//线程
pthread_create(&tid, NULL, &func,(void *)&msg);
sleep(1);
}
//关闭套接字
close(conn_fd);
close(sock_fd);
return 0;
}
客户端:client.c
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main()
{
//创建套接字
int sock_fd = socket(PF_INET,SOCK_STREAM,0);//tcp流式套接字、udp数据报套接字
//绑定地址(IP+PORT)
struct sockaddr_in srvaddr;
srvaddr.sin_family = PF_INET;
//端口
srvaddr.sin_port = htons(55551);//./tcps 7777
//IP地址
srvaddr.sin_addr.s_addr = inet_addr("172.23.205.172");
//发起连接请求
connect(sock_fd,(struct sockaddr *)&srvaddr,sizeof(srvaddr));
char buf[100] = {0};
while(1)
{
bzero(buf,100);
fgets(buf,100,stdin); //从键盘输入数据
send(sock_fd,buf,strlen(buf),0); //将数据发送给服务端
}
//关闭套接字
close(sock_fd);
return 0;
}