请选择 进入手机版 | 继续访问电脑版

[其它综合] 详情分析 TCP与UDP传输协议

[复制链接]
查看157 | 回复43 | 2021-9-12 15:11:41 | 显示全部楼层 |阅读模式
目次

一、什么是 socket ?

Socket 的英文原义是“孔”或“插座”。在编程中,Socket 被称做套接字,是网络通讯 中的一种约定。Socket 编程的应用无处不在,我们寻常 用的 QQ、微信、欣赏 器等程序,都与 Socket 编程有关。
那么我们利用 欣赏 器查资料,这个过程的技术原理是怎样的呢?如下所示:

详情分析

TCP与UDP传输协议

利用 欣赏 器,有两个告急 的名词:服务端与客户端,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 毗连 (三次握手)具体 表示 图为:

详情分析

TCP与UDP传输协议

阐明 :SYN 和 ACK 是都是标志位,此中 SYN 代表新建一个毗连 ,ACK 代表确认。此中 m、n 都是随机数。具体 阐明 如:

第一次握手:SYN 标志位被置位,客户端向服务端发送一个随机数 m。
第二次握手:ACK、SYN 标志位被置位。服务端向客户端发送 m+1 表示确认刚才收到的数据,同时向客户端发送一个随机数 n。
第三次握手:ACK 标志被置位,客户端向服务端发送 n+1 表示确认收到数据。
TCP 断开(四次挥手):TCP 断开毗连 时,客户端、服务端要完成四次数据交互工作才能建立毗连 ,常称为四次挥手。可形象比喻为如下对话:
客户端:服务端您好,我发送数据完毕了,即将和您断开毗连 。
服务端:客户端您好,我稍稍准备 一下,再给您断开
服务端:客户端您好,我准备 好了,您可以断开毗连 了。
客户端:好的,合作愉快 。

TCP 断开(四次挥手)具体 表示 图为:

详情分析

TCP与UDP传输协议

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()函数
函数原型,如下所示:

  1. 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 套接字:

  1. int tcp_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
复制代码

创建 UDP 套接字:

  1. int udp_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
复制代码

绑定套接字:bind() 函数
函数原型,如下所示:

  1. 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 举行 绑定:

  1. /* 创建服务端socket */
  2. int ServerSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);​
  3. /* 设置服务端信息 */
  4. struct sockaddr_in ServerSockAddr;
  5. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
  6. // 给结构体ServerSockAddr清零
  7. ServerSockAddr.sin_family = PF_INET;
  8. // 使用IPv4地址
  9. ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  10. // 本机IP地址
  11. ServerSockAddr.sin_port = htons(1314); // 端口
  12. /* 绑定套接字 */
  13. bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
复制代码

此中 struct sockaddr_in 范例 的布局 体变量用于保存 IPv4 的 IP 信息,假如 IPv6,则有对应的布局 体:

  1. struct sockaddr_in6 {
  2. sa_family_t sin6_family; // 地址类型,取值为AF_INET6
  3. in_port_t sin6_port; // 16位端口号
  4. uint32_t sin6_flowinfo; // IPv6流信息
  5. struct in6_addr sin6_addr; // 具体的IPv6地址
  6. uint32_t sin6_scope_id; // 接口范围ID
  7. };
复制代码

建立毗连 :connect() 函数
函数原型,参数与 bind() 的参数雷同 ,如下所示:

  1. int connect(int sock, struct sockaddr *serv_addr, socklen_t addrlen);
复制代码

利用 示例:

  1. int ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  2. connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR));
复制代码

监听:listen() 函数
函数如下所示:

  1. int listen(int sock, int backlog);
复制代码

函数的参数阐明 :
sock 参数:sock 为必要 进入监听状态的套接字。
backlog 参数:backlog 为哀求 队列的最大长度。
利用 示例:
 /* 进入监听状态 */

  1. listen(ServerSock, 10);
复制代码

吸取 哀求 :accept() 函数
函数如下所示:

  1. int accept(int sock, struct sockaddr *addr, socklen_t *addrlen);
复制代码

函数参数阐明 :
sock 参数:sock 为服务器端套接字。
addr参数:addr 为 sockaddr_in 布局 体变量。
addrlen 参数:addrlen 为参数 addr 的长度,可由 sizeof() 求得。
返回值:一个新的套接字,用于与客户端通讯 。
利用 示例:

  1. /* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
  2. int ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &len);
复制代码

关闭:close() 函数
函数如下:

  1. int close(int fd);
复制代码

函数参数 fd:要关闭的文件形貌 符。
利用 示例:

  1. close(ServerSock);
复制代码

数据的吸取 和发送
数据收发函数有几组:

  1. read()/write()
  2. recv()/send()
  3. readv()/writev()
  4. recvmsg()/sendmsg()
  5. recvfrom()/sendto()
复制代码

函数原型如下:

  1. ssize_t read(int fd, void *buf, size_t count);
  2. 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,
  3. const struct sockaddr *dest_addr, socklen_t addrlen);
  4. ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
  5. struct sockaddr *src_addr, socklen_t *addrlen);
  6. ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
  7. ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
复制代码

recv() 函数:
sockfd 参数:sockfd 为要吸取 数据的套接字。
buf 参数:buf 为要吸取 的数据的缓冲区地址。
len 参数:len 为要吸取 的数据的字节数。

flags 参数:flags 为吸取 数据时的选项,常设为 0。

  1. ssize_t recv(int sockfd, void *buf, size_t len, int flags);
复制代码

send() 函数:
sockfd 参数:sockfd 为要发送数据的套接字。
buf 参数:buf 为要发送的数据的缓冲区地址。
len 参数:len 为要发送的数据的字节数。
flags 参数:flags 为发送数据时的选项,常设为 0。

  1. 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 的布局 体变量长度的变量地址值。
 

  1. 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 的地址值布局 体变量的长度。

  1. ssize_t sendto(int sock, void *buf, size_t nbytes, int flags, struct sockaddr *to, socklen_t addrlen);
复制代码

② windows 下的 socket API 接口

  1. SOCKET socket(int af, int type, int protocol);
  2. int bind(SOCKET sock, const struct sockaddr *addr, int addrlen);
  3. int connect(SOCKET sock, const struct sockaddr *serv_addr, int addrlen);
  4. int listen(SOCKET sock, int backlog);
  5. SOCKET accept(SOCKET sock, struct sockaddr *addr, int *addrlen);
  6. int closesocket( SOCKET s);
  7. int send(SOCKET sock, const char *buf, int len, int flags);
  8. int recv(SOCKET sock, char *buf, int len, int flags);
  9. int recvfrom(SOCKET sock, char *buf, int nbytes, int flags, const struct sockaddr *from, int *addrlen);
  10. int sendto(SOCKET sock, const char *buf, int nbytes, int flags, const struct sockadr *to, int addrlen);
复制代码

③ TCP、UDP 通讯 的 socket 编程流程图

TCP 通讯 socket 编程流程:

详情分析

TCP与UDP传输协议

UDP 通讯 socket 编程流程:

详情分析

TCP与UDP传输协议

四、socket 的应用实例

① 基于 TCP 的本地客户端、服务端信息交互实例

本例的例子实现的功能为:本地 TCP 客户端往本地 TCP 服务端发送数据,TCP 服务端收到数据则会打印输出,同时把原数据返回给 TCP 客户端。这个例子雷同 于在做单片机的串口实验 时,串口上位机往我们的单片机发送数据,单片机收到数据则把该数据原样返回给上位机。
windows 的程序:
服务端程序 tcp_server.c:

  1. #include <stdio.h>
  2. #include <winsock2.h>
  3. #define BUF_LEN 100
  4. int main(void)
  5. {
  6. WSADATA wd;
  7. SOCKET ServerSock, ClientSock;
  8. char Buf[BUF_LEN] = {0};
  9. SOCKADDR ClientAddr;
  10. SOCKADDR_IN ServerSockAddr;
  11. int addr_size = 0, recv_len = 0;
  12. /* 初始化操作sock需要的DLL */
  13. WSAStartup(MAKEWORD(2,2),&wd);
  14. /* 创建服务端socket */
  15. if (-1 == (ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
  16. {
  17. printf("socket error!\n");
  18. exit(1);
  19. }
  20. /* 设置服务端信息 */
  21. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零
  22. ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
  23. ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");// 本机IP地址
  24. ServerSockAddr.sin_port = htons(1314); // 端口
  25. /* 绑定套接字 */
  26. if (-1 == bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
  27. {
  28. printf("bind error!\n");
  29. exit(1);
  30. }
  31. /* 进入监听状态 */
  32. if (-1 == listen(ServerSock, 10))
  33. {
  34. printf("listen error!\n");
  35. exit(1);
  36. }
  37. addr_size = sizeof(SOCKADDR);
  38. while (1)
  39. {
  40. /* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
  41. if (-1 == (ClientSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &addr_size)))
  42. {
  43. printf("socket error!\n");
  44. exit(1);
  45. }
  46. /* 接受客户端的返回数据 */
  47. int recv_len = recv(ClientSock, Buf, BUF_LEN, 0);
  48. printf("客户端发送过来的数据为:%s\n", Buf);
  49. /* 发送数据到客户端 */
  50. send(ClientSock, Buf, recv_len, 0);
  51. /* 关闭客户端套接字 */
  52. closesocket(ClientSock);
  53. /* 清空缓冲区 */
  54. memset(Buf, 0, BUF_LEN);
  55. }
  56. /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
  57. /* 关闭服务端套接字 */
  58. //closesocket(ServerSock);
  59. /* WSACleanup();*/
  60. return 0;
  61. }
复制代码

客户端程序 tcp_client.c:

  1. #include <stdio.h>
  2. #include <winsock2.h>
  3. #define BUF_LEN 100
  4. int main(void) {
  5. WSADATA wd;
  6. SOCKET ClientSock;
  7. char Buf[BUF_LEN] = {0};
  8. SOCKADDR_IN ServerSockAddr;
  9. /* 初始化操作sock需要的DLL */
  10. WSAStartup(MAKEWORD(2,2),&wd);
  11. /* 向服务器发起请求 */
  12. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
  13. ServerSockAddr.sin_family = AF_INET;
  14. ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  15. ServerSockAddr.sin_port = htons(1314);
  16. while (1)
  17. {
  18. /* 创建客户端socket */
  19. if (-1 == (ClientSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
  20. {
  21. printf("socket error!\n");
  22. exit(1);
  23. }
  24. if (-1 == connect(ClientSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR)))
  25. {
  26. printf("connect error!\n");
  27. exit(1);
  28. }
  29. printf("请输入一个字符串,发送给服务端:");
  30. gets(Buf);
  31. /* 发送数据到服务端 */
  32. send(ClientSock, Buf, strlen(Buf), 0);
  33. /* 接受服务端的返回数据 */
  34. recv(ClientSock, Buf, BUF_LEN, 0);
  35. printf("服务端发送过来的数据为:%s\n", Buf);
  36. memset(Buf, 0, BUF_LEN); // 重置缓冲区
  37. closesocket(ClientSock); // 关闭套接字
  38. }
  39. // WSACleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
  40. return 0;
  41. }
复制代码

上面的 IP 地址概念那一部分中,有夸大 127.0.0.1 这个 IP 是一个特别 的 IP 地址,这是回送地址,即本地机,一样寻常 用来测试利用 。此外,端口设置为 1314,这是随意设置的,只要范围在 1024~65536 之间就可以。
利用 gcc 编译器编译,编译下令 如下:

  1. gcc tcp_client.c -o tcp_client.exe -lwsock32
  2. gcc tcp_server.c -o tcp_server.exe -lwsock32
复制代码

这里必须要加 -lwsock32 这个参数用于链接 windows 下 socket 编程必须的 winsock2 这个库。假如 利用 集成开发 环境,则必要 把 wsock32.lib 放在工程目次 下,并在代码中 #include 下面加上一行 #pragma comment(lib, “ws2_32.lib”) 代码。

先启动服务端程序 tcp_server,再启动客户端程序 tcp_client,并在客户端中输入字符串,则当服务端会吸取 到字符串时会打印输出,与此同时也会往客户端返回雷同 的数据:

  1. // tcp_server
  2. 客户端发送过来的数据为:hello
  3. 客户端发送过来的数据为:5201314
  4. // tcp_client
  5. 请输入一个字符串,发送给服务端:hello
  6. 服务端发送过来的数据为:hello
  7. 请输入一个字符串,发送给服务端:5201314
  8. 服务端发送过来的数据为:5201314
  9. 请输入一个字符串,发送给服务端:
复制代码

Linux 程序
在 linux 下,“统统 都是文件”,以是 这里的套接字也当做文件来对待 。
服务端程序 linux_tcp_server.c:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. #define BUF_LEN 100
  9. int main(void) {
  10. int ServerFd, ClientFd;
  11. char Buf[BUF_LEN] = {0};
  12. struct sockaddr ClientAddr;
  13. int addr_len = 0, recv_len = 0;
  14. struct sockaddr_in ServerSockAddr;
  15. int optval = 1;
  16. /* 创建服务端文件描述符 */
  17. if (-1 == (ServerFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
  18. {
  19. printf("socket error!\n");
  20. exit(1);
  21. }
  22. /* 设置服务端信息 */
  23. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零
  24. ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
  25. ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取IP地址
  26. ServerSockAddr.sin_port = htons(6666); // 端口
  27. // 设置地址和端口号可以重复使用
  28. if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
  29. {
  30. printf("setsockopt error!\n");
  31. exit(1);
  32. }
  33. /* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */
  34. if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(struct sockaddr)))
  35. {
  36. printf("bind error!\n");
  37. exit(1);
  38. }
  39. /* 进入监听状态 */
  40. if (-1 == (listen(ServerFd, 10)))
  41. {
  42. printf("listen error!\n");
  43. exit(1);
  44. }
  45. addr_len = sizeof(struct sockaddr);
  46. while (1)
  47. {
  48. /* 监听客户端请求,accept函数返回一个新的套接字,发送和接收都是用这个套接字 */
  49. if (-1 == (ClientFd = accept(ServerFd, (struct sockaddr*)&ClientAddr, &addr_len)))
  50. {
  51. printf("accept error!\n");
  52. exit(1);
  53. }
  54. /* 接受客户端的返回数据 */
  55. if ((recv_len = recv(ClientFd, Buf, BUF_LEN, 0)) < 0)
  56. {
  57. printf("recv error!\n");
  58. exit(1);
  59. }
  60. printf("客户端发送过来的数据为:%s\n", Buf);
  61. /* 发送数据到客户端 */
  62. send(ClientFd, Buf, recv_len, 0);
  63. /* 关闭客户端套接字 */
  64. close(ClientFd);
  65. /* 清空缓冲区 */
  66. memset(Buf, 0, BUF_LEN);
  67. }
  68. return 0;
  69. }
复制代码

客户端程序 linux_tcp_client.c:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #define BUF_LEN 100
  8. int main(void) {
  9. int ClientFd;
  10. char Buf[BUF_LEN] = {0};
  11. struct sockaddr_in ServerSockAddr;
  12. /* 向服务器发起请求 */
  13. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
  14. ServerSockAddr.sin_family = AF_INET;
  15. ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  16. ServerSockAddr.sin_port = htons(6666);
  17. while (1)
  18. {
  19. /* 创建客户端socket */
  20. if (-1 == (ClientFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))
  21. {
  22. printf("socket error!\n");
  23. exit(1);
  24. }
  25. /* 连接 */
  26. if (-1 == connect(ClientFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))
  27. {
  28. printf("connect error!\n");
  29. exit(1);
  30. }
  31. printf("请输入一个字符串,发送给服务端:");
  32. gets(Buf);
  33. /* 发送数据到服务端 */
  34. send(ClientFd, Buf, strlen(Buf), 0);
  35. memset(Buf, 0, BUF_LEN); // 重置缓冲区
  36. /* 接受服务端的返回数据 */
  37. recv(ClientFd, Buf, BUF_LEN, 0);
  38. printf("服务端发送过来的数据为:%s\n", Buf);
  39. memset(Buf, 0, BUF_LEN); // 重置缓冲区
  40. close(ClientFd); // 关闭套接字
  41. }
  42. return 0;
  43. }
复制代码

Linux 下编译就不必要 添加 -lwsock32 参数:

  1. gcc linux_tcp_server.c -o linux_tcp_server
  2. gcc linux_tcp_client.c -o linux_tcp_client
复制代码

实验 征象 :

  1. $ ./linux_tcp_server
  2. 客户端发送过来的数据为:hello
  3. 客户端发送过来的数据为:world
  4. $ ./linux_tcp_client
  5. 请输入一个字符串,发送给服务端:hello
  6. 服务端发送过来的数据为:hello
  7. 请输入一个字符串,发送给服务端:world
  8. 服务端发送过来的数据为:hello
  9. 请输入一个字符串,发送给服务端:
复制代码

在调试这份程序时,出现了绑定错误:

  1. $ ./linux_tcp_client
  2. bind error!
复制代码

经上网查询发现是端口重复利用 ,可以在调用 bind() 函数之前调用 setsockopt() 函数以办理 端口重复利用 的题目 :

详情分析

TCP与UDP传输协议

② 基于 UDP 的本地客户端、服务端信息交互实例

windows 的程序
服务端程序 udp_server.c:

  1. #include <stdio.h>
  2. #include <winsock2.h>
  3. #define BUF_LEN 100
  4. int main(void) {
  5. WSADATA wd;
  6. SOCKET ServerSock;
  7. char Buf[BUF_LEN] = {0};
  8. SOCKADDR ClientAddr;
  9. SOCKADDR_IN ServerSockAddr;
  10. int addr_size = 0;
  11. /* 初始化操作sock需要的DLL */
  12. WSAStartup(MAKEWORD(2,2),&wd);
  13. /* 创建服务端socket */
  14. if(-1 == (ServerSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
  15. {
  16. printf("socket error!\n");
  17. exit(1);
  18. }
  19. /* 设置服务端信息 */
  20. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零
  21. ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
  22. ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取IP地址
  23. ServerSockAddr.sin_port = htons(1314); // 端口
  24. /* 绑定套接字 */
  25. if (-1 == (bind(ServerSock, (SOCKADDR*)&ServerSockAddr, sizeof(SOCKADDR))))
  26. {
  27. printf("bind error!\n");
  28. exit(1);
  29. }
  30. addr_size = sizeof(SOCKADDR);
  31. while (1)
  32. {
  33. /* 接受客户端的返回数据 */
  34. int str_len = recvfrom(ServerSock, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
  35. printf("客户端发送过来的数据为:%s\n", Buf);
  36. /* 发送数据到客户端 */
  37. sendto(ServerSock, Buf, str_len, 0, &ClientAddr, addr_size);
  38. /* 清空缓冲区 */
  39. memset(Buf, 0, BUF_LEN);
  40. }
  41. /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
  42. /* 关闭服务端套接字 */
  43. //closesocket(ServerSock);
  44. /* WSACleanup();*/
  45. return 0;
  46. }
复制代码

客户端程序 udp_client.c:

  1. #include <stdio.h>
  2. #include <winsock2.h>
  3. #define BUF_LEN 100
  4. int main(void) {
  5. WSADATA wd;
  6. SOCKET ClientSock;
  7. char Buf[BUF_LEN] = {0};
  8. SOCKADDR ServerAddr;
  9. SOCKADDR_IN ServerSockAddr;
  10. int ServerAddrLen = 0;
  11. /* 初始化操作sock需要的DLL */
  12. WSAStartup(MAKEWORD(2,2),&wd);
  13. /* 创建客户端socket */
  14. if (-1 == (ClientSock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
  15. {
  16. printf("socket error!\n");
  17. exit(1);
  18. }
  19. /* 向服务器发起请求 */
  20. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
  21. ServerSockAddr.sin_family = PF_INET;
  22. ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  23. ServerSockAddr.sin_port = htons(1314);
  24. ServerAddrLen = sizeof(ServerAddr);
  25. while (1)
  26. {
  27. printf("请输入一个字符串,发送给服务端:");
  28. gets(Buf);
  29. /* 发送数据到服务端 */
  30. sendto(ClientSock, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));
  31. /* 接受服务端的返回数据 */
  32. recvfrom(ClientSock, Buf, BUF_LEN, 0, &ServerAddr, &ServerAddrLen);
  33. printf("服务端发送过来的数据为:%s\n", Buf);
  34. memset(Buf, 0, BUF_LEN); // 重置缓冲区
  35. }
  36. closesocket(ClientSock); // 关闭套接字
  37. // WSACleanup(); /*如果有退出循环的条件,这里还需要清除对socket库的使用*/
  38. return 0;
  39. }
复制代码

Linux 下的程序
服务端程序 linux_udp_server.c:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #include <netinet/in.h>
  8. #define BUF_LEN 100
  9. int main(void) {
  10. int ServerFd;
  11. char Buf[BUF_LEN] = {0};
  12. struct sockaddr ClientAddr;
  13. struct sockaddr_in ServerSockAddr;
  14. int addr_size = 0;
  15. int optval = 1;
  16. /* 创建服务端socket */
  17. if ( -1 == (ServerFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
  18. {
  19. printf("socket error!\n");
  20. exit(1);
  21. }
  22. /* 设置服务端信息 */
  23. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr)); // 给结构体ServerSockAddr清零
  24. ServerSockAddr.sin_family = AF_INET; // 使用IPv4地址
  25. ServerSockAddr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取IP地址
  26. ServerSockAddr.sin_port = htons(1314); // 端口
  27. // 设置地址和端口号可以重复使用
  28. if (setsockopt(ServerFd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) < 0)
  29. {
  30. printf("setsockopt error!\n");
  31. exit(1);
  32. }
  33. /* 绑定操作,绑定前加上上面的socket属性可重复使用地址 */
  34. if (-1 == bind(ServerFd, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr)))
  35. {
  36. printf("bind error!\n");
  37. exit(1);
  38. }
  39. addr_size = sizeof(ClientAddr);
  40. while (1)
  41. {
  42. /* 接受客户端的返回数据 */
  43. int str_len = recvfrom(ServerFd, Buf, BUF_LEN, 0, &ClientAddr, &addr_size);
  44. printf("客户端发送过来的数据为:%s\n", Buf);
  45. /* 发送数据到客户端 */
  46. sendto(ServerFd, Buf, str_len, 0, &ClientAddr, addr_size);
  47. /* 清空缓冲区 */
  48. memset(Buf, 0, BUF_LEN);
  49. }
  50. close(ServerFd);
  51. return 0;
  52. }
复制代码

客户端程序 linux_udp_client.c:

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <arpa/inet.h>
  6. #include <sys/socket.h>
  7. #define BUF_LEN 100
  8. int main(void) {
  9. int ClientFd;
  10. char Buf[BUF_LEN] = {0};
  11. struct sockaddr ServerAddr;
  12. int addr_size = 0;
  13. struct sockaddr_in ServerSockAddr;
  14. /* 创建客户端socket */
  15. if (-1 == (ClientFd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
  16. {
  17. printf("socket error!\n");
  18. exit(1);
  19. }
  20. /* 向服务器发起请求 */
  21. memset(&ServerSockAddr, 0, sizeof(ServerSockAddr));
  22. ServerSockAddr.sin_family = PF_INET;
  23. ServerSockAddr.sin_addr.s_addr = inet_addr("127.0.0.1");
  24. ServerSockAddr.sin_port = htons(1314);
  25. addr_size = sizeof(ServerAddr);
  26. while (1)
  27. {
  28. printf("请输入一个字符串,发送给服务端:");
  29. gets(Buf);
  30. /* 发送数据到服务端 */
  31. sendto(ClientFd, Buf, strlen(Buf), 0, (struct sockaddr*)&ServerSockAddr, sizeof(ServerSockAddr));
  32. /* 接受服务端的返回数据 */
  33. recvfrom(ClientFd, Buf, BUF_LEN, 0, &ServerAddr, &addr_size);
  34. printf("服务端发送过来的数据为:%s\n", Buf);
  35. memset(Buf, 0, BUF_LEN); // 重置缓冲区
  36. }
  37. close(ClientFd); // 关闭套接字
  38. return 0;
  39. }
复制代码

以上就是详情分析 TCP与UDP传输协议的具体 内容,更多关于分析 TCP与UDP传输协议的资料请关注脚本之家别的 相干 文章!


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

avatar 夜昙SS | 2021-9-12 20:13:26 | 显示全部楼层
每天顶顶贴,一身轻松啊!
回复

使用道具 举报

avatar 123457441 | 2021-9-12 21:14:08 | 显示全部楼层
收藏了,改天让朋友看看!
回复

使用道具 举报

avatar 海沙心诖 | 2021-9-20 06:00:25 | 显示全部楼层
admin楼主好聪明啊!
回复

使用道具 举报

avatar Abby_guguk | 2021-9-26 20:41:58 | 显示全部楼层
管它三七二十一!
回复

使用道具 举报

avatar 湘军 | 2021-9-26 23:29:05 | 显示全部楼层
admin楼主的病历本丢我这里了!
回复

使用道具 举报

avatar 扬帆46 | 2021-10-4 09:42:43 | 显示全部楼层
楼上是GG还是MM啊?
回复

使用道具 举报

avatar chris是小胖纸 | 2021-10-6 10:53:55 | 显示全部楼层
看在admin楼主的面子上,认真回帖!
回复

使用道具 举报

avatar 名人堂熊猫虞kk | 2021-10-6 11:49:44 | 显示全部楼层
系统居然说我是在灌水,我有吗?
回复

使用道具 举报

avatar 嗅觉Y不缺失 | 2021-10-7 13:50:51 | 显示全部楼层
看帖、回帖、拿分、走人
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则