UDP协议:udp协议中文名称叫用户数据报协议
我们首先回忆一下大端和小端的问题:
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
网络字节序:我们知道内存存储数据多字节数据相对于内存地址来说有大端和小端之分,磁盘文件的多字节数据相对于文件中的偏移地址也有大端和小端之分,网络数据同样也有大端和小端之分,那么我们就应该用这些定义网络数据流的地址:
UDP套接字调用connect(不同于TCP):没有三路握手过程,内核只是检查是否存在错误,记录对端IP地址和端口号。
下面介绍一下这些函数:
int socket(int domain, int type, int protocol);//创建socket文件描述符
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);//绑定端口号
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);//发送数据
几种网络协议的地址格式:
下面我们来看一下UDP服务器的代码实现:
#include
#include
#include
#include
#include
#include
#include
#include
/*
* UDP网络聊天程序--服务器端
* 1. 创建socket--建立与网卡驱动的联系 socket
* 2. 为socket绑定地址---确定socket操作的地址 bind
* 3. 收发数据 recvfrom/sendto
* 4. 关闭socket close
*/
int main()
{
int sockfd = -1;
//创建套接字-建立于网卡驱动之间的关联
//int socket(int domain, int type, int protocol);
//domain 地址域 AF_IENT---IPV4
//type 套接字类型 SOCK_DGRAM 数据报套接字
//protocol 协议 IPPROTO_UDP UDP协议
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
perror("socket");
return -1;
}
//为socket绑定地址信息
// /usr/include/netinet/in.h
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
//inet_addr---将字符串的IP地址转换为网络主机地址
addr.sin_addr.s_addr = inet_addr("192.168.122.131");
if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("bind");
return -1;
}
while(1) {
char buff[1024] = {0};
struct sockaddr_in cli_addr;
socklen_t len = sizeof(struct sockaddr_in);
//接收客户端发送的消息,并将客户端地址放到cli_addr中
//返回实际接收的数据长度
recvfrom(sockfd, buff, 1023, 0,
(struct sockaddr*)&cli_addr, &len);
printf("client say:%s\n", buff);
char tmp[1024] = {0};
scanf("%s", tmp);
//返回实际发送的数据长度
//将数据发送给cli_addr这个地址
sendto(sockfd, tmp, strlen(tmp), 0,
(struct sockaddr*)&cli_addr, len);
}
close(sockfd);
return 0;
}
UDP客户端:
#include
#include
#include
#include
#include
#include
#include
#include
/*
* UDP网络聊天程序--客户端
* 1. 创建socket
* 2. 定义服务器地址
* 3. 向服务器地址发送数据
* 4. 接收数据
* 5. 关闭socket
*/
int main()
{
int sockfd = -1;
sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockfd < 0) {
perror("socket");
return -1;
}
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(9000);
serv_addr.sin_addr.s_addr = inet_addr("192.168.122.131");
socklen_t len = sizeof(struct sockaddr_in);
while(1) {
char buff[1024] = {0};
scanf("%s", buff);
sendto(sockfd, buff, strlen(buff), 0,
(struct sockaddr*)&serv_addr, len);
char tmp[1024] = {0};
struct sockaddr_in addr;
recvfrom(sockfd, tmp, 1023, 0,
(struct sockaddr*)&addr, &len);
printf("serv say:%s\n", tmp);
}
close(sockfd);
return 0;
}
TCP客户端/服务器图解:
代码实现:
服务端
#include
#include
#include
#include
#include
#include
#include
#include
/*
* 基于TCP的一个简单网络聊天程序
* 1. 创建套接字
* 2. 为套接字绑定地址
* 3. 开始监听套接字
* 4. 接收连接成功的新socket
* 5. 收发数据
* 6. 关闭socket
*/
/*
* 如何判断TCP连接断开
* 对于接收方来说----如果连接断开,recv返回值为0
* 对于发送方来说----如果连接断开,每次调用send都会触发连接断开异
* 常,接收到系统发送的SIGPIPE信号,导致进程退出
* 如果不想让进程退出,那么就需要在网络程序运行初始化的时
* 候,对SIGPIPE信号做出自定义/忽略处理
*/
int main(int argc, char *argv[])
{
int lst_sockfd = -1;
if (argc != 3) {
printf("Usage: ./tcp_server ip port\n");
return -1;
}
//创建一个拉皮条的socket---监听socket
lst_sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (lst_sockfd < 0) {
perror("socket error");
return -1;
}
//为监听socket绑定一个监听的地址
struct sockaddr_in lst_addr;
lst_addr.sin_family = AF_INET;
lst_addr.sin_port = htons(atoi(argv[2]));
lst_addr.sin_addr.s_addr = inet_addr(argv[1]);
//inet_aton(argv[1], &lst_addr.sin_addr);
//inet_pton(AF_INET, argv[1], &lst_addr.sin_addr);
socklen_t len = sizeof(struct sockaddr_in);
int ret = bind(lst_sockfd, (struct sockaddr*)&lst_addr, len);
if (ret < 0) {
perror("bind error");
return -1;
}
//开始监听socket
if (listen(lst_sockfd, 5) < 0) {
perror("listen error");
return -1;
}
while(1) {
int new_fd;
struct sockaddr_in cli_addr;
//从已成功连接队列取出一个新连接
//假如这个队列里边没有新连接就会阻塞等待客户端的连接
new_fd = accept(lst_sockfd, (struct sockaddr*)&cli_addr,
&len);
if (new_fd < 0) {
perror("accept error");
continue;
}
//接收新连接客户端的数据
//这个新的连接里边已经定义好了数据通信的源地址端口和
//目的地址端口,因此在接收或发送数据的时候,就不需要
//重新确定了。
while(1) {
char buff[1024] = {0};
ssize_t r_len = recv(new_fd, buff, 1023, 0);
if (r_len == 0) {
printf("peer shutdown!!\n");
break;
}
printf("client:%s:%d say:%s\n",
inet_ntoa(cli_addr.sin_addr),
ntohs(cli_addr.sin_port),
buff);
/*
//向新连接的客户端回复数据
printf("server say:");
fflush(stdout);
char tmp[1024] = {0};
scanf("%s", tmp);
send(new_fd, tmp, strlen(tmp), 0);
*/
}
close(new_fd);
}
close(lst_sockfd);
return 0;
}
客户端
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* 基于TCP的一个简单聊天程序客户端
* 1. 创建socket
* 2. 向服务器端发起连接
* 3. 收发数据
* 4. 关闭socket
*/
void sigcb(int signo)
{
printf("recv a signal:SIGPIPE\n");
}
int main(int argc, char *argv[])
{
int sockfd = -1;
if (argc != 3) {
printf("Usage: ./tcp_client ip port\n");
return -1;
}
signal(SIGPIPE, sigcb);
//1. 创建socket
sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockfd < 0) {
perror("socket error");
return -1;
}
//向服务器端发起连接
struct sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(atoi(argv[2]));
serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
socklen_t len = sizeof(struct sockaddr_in);
int ret = connect(sockfd, (struct sockaddr*)&serv_addr, len);
if (ret < 0) {
perror("connect error");
return -1;
}
//连接建立成功之后,sockfd将确定下来,操作的数据应该从哪来,
//到哪去 ,所以收发数据,将不用再确定从哪发,发给谁
while(1) {
//向服务器端发送数据
//printf("client say:");
//fflush(stdout);
char buff[1024] = {0};
//scanf("%s", buff);
sprintf(buff, "%s", "zheshiyige ceshi shuju!!");
send(sockfd, buff, strlen(buff), 0);
sleep(1);
//接收服务器端的回复
/*
char tmp[1024] = {0};
recv(sockfd, tmp, 1023, 0);
printf("server say:%s\n", tmp);
*/
}
close(sockfd);
return 0;
}