详情解析TCP与UDP传输协议
一、什么是 socket ?
socket 的英文原义是“孔”或“插座”。在编程中,socket 被称做套接字,是网络通信中的一种约定。socket 编程的应用无处不在,我们平时用的 qq、微信、浏览器等程序,都与 socket 编程有关。
那么我们使用浏览器查资料,这个过程的技术原理是怎样的呢?如下所示:
使用浏览器,有两个重要的名词:服务端与客户端,socket 编程的目的就是如何实现这两端之间的通信。
二、socket 编程的重要概念
① ip 地址
ip 地址(internet protocol address)是指互联网协议地址,又译为网际协议地址。ip 地址被用来给 internet 上的电脑一个编号,可以把“个人电脑”比作“一台电话”,那么“ip 地址”就相当于“电话号码”。若计算机 1 知道计算机 2 的 ip 地址,则计算机 1 就能访问计算机 2。
ip 地址是一个 32 位的二进制数,通常被分割为 4 个“8位二进制数”(也就是 4 个字节)。ip 地址通常用点分十进制表示成(a.b.c.d)的形式,其中,a,b,c,d 都是 0~255 之间的十进制整数。例:点分十进 ip 地址(100.4.5.6),实际上是 32 位二进制数(01100100.00000100.00000101.00000110)。
ip 地址有 ipv4 与 ipv6 之分,现在用得较多的是 ipv4。其中,有一个特殊的 ip 地址需要记住:127.0.0.1,这是回送地址,即本地机,一般用来测试使用。
② tcp/ip 端口
若计算机 1 知道计算机 2 的 ip 地址,则计算机 1 就能访问计算机 2。但是,要访问计算机 2 中的不同的应用软件,则还得需要一个信息:端口。
服务端口,最多可以有65536个,使用 16bit 进行编号,即其范围为:0 ~ 65535。但 0 ~ 1023 的端口一般由系统分配给特定的服务程序,例如 web 服务的端口号为 80,ftp 服务的端口号为 21 等。
③ 协议
协议(protocol)是通信双方进行数据交互的一种约定,如 tcp、udp 协议。
tcp(transmission control protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,数据可以准确发送,数据丢失会重发,tcp 协议常用于 web 应用中。
tcp 连接(三次握手):tcp 传输起始时,客户端、服务端要完成三次数据交互工作才能建立连接,常称为三次握手。可形象比喻为如下对话:
客户端:服务端您好,我有数据要发给你,请求您开通访问权限。
服务端:客户端您好,已给您开通权限,您可以发送数据了。
客户端:收到,谢谢。
tcp 连接(三次握手)具体示意图为:
说明:syn 和 ack 是都是标志位,其中 syn 代表新建一个连接,ack 代表确认。其中 m、n 都是随机数。具体说明如:
第一次握手:syn 标志位被置位,客户端向服务端发送一个随机数 m。
第二次握手:ack、syn 标志位被置位。服务端向客户端发送 m+1 表示确认刚才收到的数据,同时向客户端发送一个随机数 n。
第三次握手:ack 标志被置位,客户端向服务端发送 n+1 表示确认收到数据。
tcp 断开(四次挥手):tcp 断开连接时,客户端、服务端要完成四次数据交互工作才能建立连接,常称为四次挥手。可形象比喻为如下对话:
客户端:服务端您好,我发送数据完毕了,即将和您断开连接。
服务端:客户端您好,我稍稍准备一下,再给您断开
服务端:客户端您好,我准备好了,您可以断开连接了。
客户端:好的,合作愉快。
tcp 断开(四次挥手)具体示意图为:
fin 也是一个标志位,代表断开连接,类似三次握手。
为什么建立连接只需要三次数据交互,而断开连接需要四次呢?
建立连接时,服务端在监听状态下,收到建立连接请求的 syn 报文后,把 ack 和 syn 放在一个报文里发送给客户端。
而关闭连接时,当收到对方的 fin 报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即 close,也可以发送一些数据给对方后,再发送 fin 报文给对方来表示同意现在关闭连接,因此,己方 ack 和 fin 一般都会分开发送。
udp 协议:udp(user datagram protocol, 用户数据报协议)是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务,可以保证通讯效率,传输延时小。例如视频聊天应用中用的就是 udp 协议,这样可以保证及时丢失少量数据,视频的显示也不受很大影响。
什么是协议族?协议族是多个协议的统称。比如 tcp/ip 协议族,其不仅仅是 tcp 协议、ip 协议,而是多个协议的集合,其包含 ip、tcp、udp、ftp、smtp 等协议。
三、socket 编程的 api 接口
① linux 下的 socket api 接口
创建 socket:socket()函数
函数原型,如下所示:
int socket(int af, int type, int protocol);
函数说明:
af 参数:af 为地址族(address family),也就是 ip 地址类型,常用的有 af_inet 和 af_inet6,其前缀也可以是 pf(protocol family),即 pf_inet 和 pf_inet6。
type 参数:type 为数据传输方式,常用的有 面向连接(sock_stream)方式(即 tcp) 和 无连接(sock_dgram)的方式(即 udp)。
protocol 参数:protocol 表示传输协议,常用的有 ipproto_tcp 和 ipptoto_udp,分别表示 tcp 传输协议和 udp 传输协议。
创建 tcp 套接字:
int tcp_socket = socket(af_inet, sock_stream, ipproto_tcp);
创建 udp 套接字:
int udp_socket = socket(af_inet, sock_dgram, ipproto_udp);
绑定套接字:bind() 函数
函数原型,如下所示:
int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
函数说明:
sock 参数:sock 为 socket 文件描述符。
addr 参数:addr 为 sockaddr 结构体变量的指针。
addrlen 参数:addrlen 为 addr 变量的大小,可由 sizeof() 计算得出。
将创建的套接字 serversock 与本地 ip127.0.0.1、端口 1314 进行绑定:
/* 创建服务端socket */ int serversock = socket(pf_inet, sock_stream, ipproto_tcp); /* 设置服务端信息 */ struct sockaddr_in serversockaddr; memset(&serversockaddr, 0, sizeof(serversockaddr)); // 给结构体serversockaddr清零 serversockaddr.sin_family = pf_inet; // 使用ipv4地址 serversockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // 本机ip地址 serversockaddr.sin_port = htons(1314); // 端口 /* 绑定套接字 */ bind(serversock, (sockaddr*)&serversockaddr, sizeof(sockaddr));
其中 struct sockaddr_in 类型的结构体变量用于保存 ipv4 的 ip 信息,若是 ipv6,则有对应的结构体:
struct sockaddr_in6 { sa_family_t sin6_family; // 地址类型,取值为af_inet6 in_port_t sin6_port; // 16位端口号 uint32_t sin6_flowinfo; // ipv6流信息 struct in6_addr sin6_addr; // 具体的ipv6地址 uint32_t sin6_scope_id; // 接口范围id };
建立连接:connect() 函数
函数原型,参数与 bind() 的参数类似,如下所示:
int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
使用示例:
int clientsock = socket(af_inet, sock_stream, ipproto_tcp); connect(clientsock, (sockaddr*)&serversockaddr, sizeof(sockaddr));
监听:listen() 函数
函数如下所示:
int listen(int sock, int backlog);
函数的参数说明:
sock 参数:sock 为需要进入监听状态的套接字。
backlog 参数:backlog 为请求队列的最大长度。
使用示例:
/* 进入监听状态 */
listen(serversock, 10);
接收请求:accept() 函数
函数如下所示:
int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
函数参数说明:
sock 参数:sock 为服务器端套接字。
addr参数:addr 为 sockaddr_in 结构体变量。
addrlen 参数:addrlen 为参数 addr 的长度,可由 sizeof() 求得。
返回值:一个新的套接字,用于与客户端通信。
使用示例:
/* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */ int clientsock = accept(serversock, (sockaddr*)&clientaddr, &len);
关闭:close() 函数
函数如下:
int close(int fd);
函数参数 fd:要关闭的文件描述符。
使用示例:
close(serversock);
数据的接收和发送
数据收发函数有几组:
read()/write() recv()/send() readv()/writev() recvmsg()/sendmsg() recvfrom()/sendto()
函数原型如下:
ssize_t read(int fd, void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count); ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
recv() 函数:
sockfd 参数:sockfd 为要接收数据的套接字。
buf 参数:buf 为要接收的数据的缓冲区地址。
len 参数:len 为要接收的数据的字节数。
flags 参数:flags 为接收数据时的选项,常设为 0。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
send() 函数:
sockfd 参数:sockfd 为要发送数据的套接字。
buf 参数:buf 为要发送的数据的缓冲区地址。
len 参数:len 为要发送的数据的字节数。
flags 参数:flags 为发送数据时的选项,常设为 0。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
recvfrom() 函数:
sock:用于接收 udp 数据的套接字;
buf:保存接收数据的缓冲区地址;
nbytes:可接收的最大字节数(不能超过 buf 缓冲区的大小);
flags:可选项参数,若没有可传递 0;
from:存有发送端地址信息的 sockaddr 结构体变量的地址;
addrlen:保存参数 from 的结构体变量长度的变量地址值。
ssize_t recvfrom(int sock, void *buf, size_t nbytes, int flags, struct sockadr *from, socklen_t *addrlen);
sendto() 函数:
sock:用于传输 udp 数据的套接字;
buf:保存待传输数据的缓冲区地址;
nbytes:带传输数据的长度(以字节计);
flags:可选项参数,若没有可传递 0;
to:存有目标地址信息的 sockaddr 结构体变量的地址;
addrlen:传递给参数 to 的地址值结构体变量的长度。
ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
② windows 下的 socket api 接口
socket socket(int af, int type, int protocol); int bind(socket sock, const struct sockaddr *addr, int addrlen); int connect(socket sock, const struct sockaddr *serv_addr, int addrlen); int listen(socket sock, int backlog); socket accept(socket sock, struct sockaddr *addr, int *addrlen); int closesocket( socket s); int send(socket sock, const char *buf, int len, int flags); int recv(socket sock, char *buf, int len, int flags); int recvfrom(socket sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen); int sendto(socket sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);
③ tcp、udp 通信的 socket 编程流程图
tcp 通信 socket 编程流程:
udp 通信 socket 编程流程:
四、socket 的应用实例
① 基于 tcp 的本地客户端、服务端信息交互实例
本例的例子实现的功能为:本地 tcp 客户端往本地 tcp 服务端发送数据,tcp 服务端收到数据则会打印输出,同时把原数据返回给 tcp 客户端。这个例子类似于在做单片机的串口实验时,串口上位机往我们的单片机发送数据,单片机收到数据则把该数据原样返回给上位机。
windows 的程序:
服务端程序 tcp_server.c:
#include <stdio.h> #include <winsock2.h> #define buf_len 100 int main(void) { wsadata wd; socket serversock, clientsock; char buf[buf_len] = {0}; sockaddr clientaddr; sockaddr_in serversockaddr; int addr_size = 0, recv_len = 0; /* 初始化操作sock需要的dll */ wsastartup(makeword(2,2),&wd); /* 创建服务端socket */ if (-1 == (serversock = socket(af_inet, sock_stream, ipproto_tcp))) { printf("socket error!\n"); exit(1); } /* 设置服务端信息 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); // 给结构体serversockaddr清零 serversockaddr.sin_family = af_inet; // 使用ipv4地址 serversockaddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本机ip地址 serversockaddr.sin_port = htons(1314); // 端口 /* 绑定套接字 */ if (-1 == bind(serversock, (sockaddr*)&serversockaddr, sizeof(sockaddr))) { printf("bind error!\n"); exit(1); } /* 进入监听状态 */ if (-1 == listen(serversock, 10)) { printf("listen error!\n"); exit(1); } addr_size = sizeof(sockaddr); while (1) { /* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */ if (-1 == (clientsock = accept(serversock, (sockaddr*)&clientaddr, &addr_size))) { printf("socket error!\n"); exit(1); } /* 接受客户端的返回数据 */ int recv_len = recv(clientsock, buf, buf_len, 0); printf("客户端发送过来的数据为:%s\n", buf); /* 发送数据到客户端 */ send(clientsock, buf, recv_len, 0); /* 关闭客户端套接字 */ closesocket(clientsock); /* 清空缓冲区 */ memset(buf, 0, buf_len); } /*如果有退出循环的条件,这里还需要清除对socket库的使用*/ /* 关闭服务端套接字 */ //closesocket(serversock); /* wsacleanup();*/ return 0; }
客户端程序 tcp_client.c:
#include <stdio.h> #include <winsock2.h> #define buf_len 100 int main(void) { wsadata wd; socket clientsock; char buf[buf_len] = {0}; sockaddr_in serversockaddr; /* 初始化操作sock需要的dll */ wsastartup(makeword(2,2),&wd); /* 向服务器发起请求 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); serversockaddr.sin_family = af_inet; serversockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); serversockaddr.sin_port = htons(1314); while (1) { /* 创建客户端socket */ if (-1 == (clientsock = socket(af_inet, sock_stream, ipproto_tcp))) { printf("socket error!\n"); exit(1); } if (-1 == connect(clientsock, (sockaddr*)&serversockaddr, sizeof(sockaddr))) { printf("connect error!\n"); exit(1); } printf("请输入一个字符串,发送给服务端:"); gets(buf); /* 发送数据到服务端 */ send(clientsock, buf, strlen(buf), 0); /* 接受服务端的返回数据 */ recv(clientsock, buf, buf_len, 0); printf("服务端发送过来的数据为:%s\n", buf); memset(buf, 0, buf_len); // 重置缓冲区 closesocket(clientsock); // 关闭套接字 } // wsacleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/ return 0; }
上面的 ip 地址概念那一部分中,有强调 127.0.0.1 这个 ip 是一个特殊的 ip 地址,这是回送地址,即本地机,一般用来测试使用。此外,端口设置为 1314,这是随意设置的,只要范围在 1024~65536 之间就可以。
使用 gcc 编译器编译,编译命令如下:
gcc tcp_client.c -o tcp_client.exe -lwsock32 gcc tcp_server.c -o tcp_server.exe -lwsock32
这里必须要加 -lwsock32 这个参数用于链接 windows 下 socket 编程必须的 winsock2 这个库。若是使用集成开发环境,则需要把 wsock32.lib 放在工程目录下,并在代码中 #include <winsock2.h> 下面加上一行 #pragma comment(lib, “ws2_32.lib”) 代码。
先启动服务端程序 tcp_server,再启动客户端程序 tcp_client,并在客户端中输入字符串,则当服务端会接收到字符串时会打印输出,与此同时也会往客户端返回相同的数据:
// tcp_server 客户端发送过来的数据为:hello 客户端发送过来的数据为:5201314 // tcp_client 请输入一个字符串,发送给服务端:hello 服务端发送过来的数据为:hello 请输入一个字符串,发送给服务端:5201314 服务端发送过来的数据为:5201314 请输入一个字符串,发送给服务端:
linux 程序
在 linux 下,“一切都是文件”,所以这里的套接字也当做文件来看待。
服务端程序 linux_tcp_server.c:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> #define buf_len 100 int main(void) { int serverfd, clientfd; char buf[buf_len] = {0}; struct sockaddr clientaddr; int addr_len = 0, recv_len = 0; struct sockaddr_in serversockaddr; int optval = 1; /* 创建服务端文件描述符 */ if (-1 == (serverfd = socket(af_inet, sock_stream, ipproto_tcp))) { printf("socket error!\n"); exit(1); } /* 设置服务端信息 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); // 给结构体serversockaddr清零 serversockaddr.sin_family = af_inet; // 使用ipv4地址 serversockaddr.sin_addr.s_addr = htonl(inaddr_any); // 自动获取ip地址 serversockaddr.sin_port = htons(6666); // 端口 // 设置地址和端口号可以重复使用 if (setsockopt(serverfd, sol_socket, so_reuseaddr, &optval, sizeof(optval)) < 0) { printf("setsockopt error!\n"); exit(1); } /* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */ if (-1 == bind(serverfd, (struct sockaddr*)&serversockaddr, sizeof(struct sockaddr))) { printf("bind error!\n"); exit(1); } /* 进入监听状态 */ if (-1 == (listen(serverfd, 10))) { printf("listen error!\n"); exit(1); } addr_len = sizeof(struct sockaddr); while (1) { /* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */ if (-1 == (clientfd = accept(serverfd, (struct sockaddr*)&clientaddr, &addr_len))) { printf("accept error!\n"); exit(1); } /* 接受客户端的返回数据 */ if ((recv_len = recv(clientfd, buf, buf_len, 0)) < 0) { printf("recv error!\n"); exit(1); } printf("客户端发送过来的数据为:%s\n", buf); /* 发送数据到客户端 */ send(clientfd, buf, recv_len, 0); /* 关闭客户端套接字 */ close(clientfd); /* 清空缓冲区 */ memset(buf, 0, buf_len); } return 0; }
客户端程序 linux_tcp_client.c:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define buf_len 100 int main(void) { int clientfd; char buf[buf_len] = {0}; struct sockaddr_in serversockaddr; /* 向服务器发起请求 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); serversockaddr.sin_family = af_inet; serversockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); serversockaddr.sin_port = htons(6666); while (1) { /* 创建客户端socket */ if (-1 == (clientfd = socket(af_inet, sock_stream, ipproto_tcp))) { printf("socket error!\n"); exit(1); } /* 连接 */ if (-1 == connect(clientfd, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr))) { printf("connect error!\n"); exit(1); } printf("请输入一个字符串,发送给服务端:"); gets(buf); /* 发送数据到服务端 */ send(clientfd, buf, strlen(buf), 0); memset(buf, 0, buf_len); // 重置缓冲区 /* 接受服务端的返回数据 */ recv(clientfd, buf, buf_len, 0); printf("服务端发送过来的数据为:%s\n", buf); memset(buf, 0, buf_len); // 重置缓冲区 close(clientfd); // 关闭套接字 } return 0; }
linux 下编译就不需要添加 -lwsock32 参数:
gcc linux_tcp_server.c -o linux_tcp_server gcc linux_tcp_client.c -o linux_tcp_client
实验现象:
$ ./linux_tcp_server 客户端发送过来的数据为:hello 客户端发送过来的数据为:world $ ./linux_tcp_client 请输入一个字符串,发送给服务端:hello 服务端发送过来的数据为:hello 请输入一个字符串,发送给服务端:world 服务端发送过来的数据为:hello 请输入一个字符串,发送给服务端:
在调试这份程序时,出现了绑定错误:
$ ./linux_tcp_client bind error!
经上网查询发现是端口重复使用,可以在调用 bind() 函数之前调用 setsockopt() 函数以解决端口重复使用的问题:
② 基于 udp 的本地客户端、服务端信息交互实例
windows 的程序
服务端程序 udp_server.c:
#include <stdio.h> #include <winsock2.h> #define buf_len 100 int main(void) { wsadata wd; socket serversock; char buf[buf_len] = {0}; sockaddr clientaddr; sockaddr_in serversockaddr; int addr_size = 0; /* 初始化操作sock需要的dll */ wsastartup(makeword(2,2),&wd); /* 创建服务端socket */ if(-1 == (serversock = socket(af_inet, sock_dgram, ipproto_udp))) { printf("socket error!\n"); exit(1); } /* 设置服务端信息 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); // 给结构体serversockaddr清零 serversockaddr.sin_family = af_inet; // 使用ipv4地址 serversockaddr.sin_addr.s_addr = htonl(inaddr_any); // 自动获取ip地址 serversockaddr.sin_port = htons(1314); // 端口 /* 绑定套接字 */ if (-1 == (bind(serversock, (sockaddr*)&serversockaddr, sizeof(sockaddr)))) { printf("bind error!\n"); exit(1); } addr_size = sizeof(sockaddr); while (1) { /* 接受客户端的返回数据 */ int str_len = recvfrom(serversock, buf, buf_len, 0, &clientaddr, &addr_size); printf("客户端发送过来的数据为:%s\n", buf); /* 发送数据到客户端 */ sendto(serversock, buf, str_len, 0, &clientaddr, addr_size); /* 清空缓冲区 */ memset(buf, 0, buf_len); } /*如果有退出循环的条件,这里还需要清除对socket库的使用*/ /* 关闭服务端套接字 */ //closesocket(serversock); /* wsacleanup();*/ return 0; }
客户端程序 udp_client.c:
#include <stdio.h> #include <winsock2.h> #define buf_len 100 int main(void) { wsadata wd; socket clientsock; char buf[buf_len] = {0}; sockaddr serveraddr; sockaddr_in serversockaddr; int serveraddrlen = 0; /* 初始化操作sock需要的dll */ wsastartup(makeword(2,2),&wd); /* 创建客户端socket */ if (-1 == (clientsock = socket(af_inet, sock_dgram, ipproto_udp))) { printf("socket error!\n"); exit(1); } /* 向服务器发起请求 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); serversockaddr.sin_family = pf_inet; serversockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); serversockaddr.sin_port = htons(1314); serveraddrlen = sizeof(serveraddr); while (1) { printf("请输入一个字符串,发送给服务端:"); gets(buf); /* 发送数据到服务端 */ sendto(clientsock, buf, strlen(buf), 0, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr)); /* 接受服务端的返回数据 */ recvfrom(clientsock, buf, buf_len, 0, &serveraddr, &serveraddrlen); printf("服务端发送过来的数据为:%s\n", buf); memset(buf, 0, buf_len); // 重置缓冲区 } closesocket(clientsock); // 关闭套接字 // wsacleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/ return 0; }
linux 下的程序
服务端程序 linux_udp_server.c:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <netinet/in.h> #define buf_len 100 int main(void) { int serverfd; char buf[buf_len] = {0}; struct sockaddr clientaddr; struct sockaddr_in serversockaddr; int addr_size = 0; int optval = 1; /* 创建服务端socket */ if ( -1 == (serverfd = socket(af_inet, sock_dgram, ipproto_udp))) { printf("socket error!\n"); exit(1); } /* 设置服务端信息 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); // 给结构体serversockaddr清零 serversockaddr.sin_family = af_inet; // 使用ipv4地址 serversockaddr.sin_addr.s_addr = htonl(inaddr_any); // 自动获取ip地址 serversockaddr.sin_port = htons(1314); // 端口 // 设置地址和端口号可以重复使用 if (setsockopt(serverfd, sol_socket, so_reuseaddr, &optval, sizeof(optval)) < 0) { printf("setsockopt error!\n"); exit(1); } /* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */ if (-1 == bind(serverfd, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr))) { printf("bind error!\n"); exit(1); } addr_size = sizeof(clientaddr); while (1) { /* 接受客户端的返回数据 */ int str_len = recvfrom(serverfd, buf, buf_len, 0, &clientaddr, &addr_size); printf("客户端发送过来的数据为:%s\n", buf); /* 发送数据到客户端 */ sendto(serverfd, buf, str_len, 0, &clientaddr, addr_size); /* 清空缓冲区 */ memset(buf, 0, buf_len); } close(serverfd); return 0; }
客户端程序 linux_udp_client.c:
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #define buf_len 100 int main(void) { int clientfd; char buf[buf_len] = {0}; struct sockaddr serveraddr; int addr_size = 0; struct sockaddr_in serversockaddr; /* 创建客户端socket */ if (-1 == (clientfd = socket(af_inet, sock_dgram, ipproto_udp))) { printf("socket error!\n"); exit(1); } /* 向服务器发起请求 */ memset(&serversockaddr, 0, sizeof(serversockaddr)); serversockaddr.sin_family = pf_inet; serversockaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); serversockaddr.sin_port = htons(1314); addr_size = sizeof(serveraddr); while (1) { printf("请输入一个字符串,发送给服务端:"); gets(buf); /* 发送数据到服务端 */ sendto(clientfd, buf, strlen(buf), 0, (struct sockaddr*)&serversockaddr, sizeof(serversockaddr)); /* 接受服务端的返回数据 */ recvfrom(clientfd, buf, buf_len, 0, &serveraddr, &addr_size); printf("服务端发送过来的数据为:%s\n", buf); memset(buf, 0, buf_len); // 重置缓冲区 } close(clientfd); // 关闭套接字 return 0; }
以上就是详情解析tcp与udp传输协议的详细内容,更多关于解析tcp与udp传输协议的资料请关注其它相关文章!