详情解析TCP与UDP传输协议

目录
  • 一、什么是 socket ?
  • 二、Socket 编程的重要概念
    • ① IP 地址
    • ② TCP/IP 端口
    • ③ 协议
  • 三、socket 编程的 API 接口
    • ① Linux 下的 socket API 接口
    • ② windows 下的 socket API 接口
    • ③ TCP、UDP 通信的 socket 编程流程图
  • 四、socket 的应用实例
    • ① 基于 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传输协议的资料请关注我们其它相关文章!

(0)

相关推荐

  • php socket通信(tcp/udp)实例分析

    本文实例讲述了php socket通信(tcp/udp)方法.分享给大家供大家参考,具体如下: 注意 1.在socket_bind的时候ip地址不能真回环地址如127.0.0.1 2.server.php后台跑起来的时候nohup php server.php > /var/tmp/a.log 2>&1 & 一: udp 方式 1) server.php <?php //error_reporting( E_ALL ); set_time_limit( 0 ); ob_i

  • C语言编写基于TCP和UDP协议的Socket通信程序示例

    Tcp多线程服务器和客户端程序 服务器程序: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #define PORT 8082 #define BUFSIZE 512 cha

  • python基于socket实现的UDP及TCP通讯功能示例

    本文实例讲述了python基于socket实现的UDP及TCP通讯功能.分享给大家供大家参考,具体如下: Server: import socket address = ('127.0.0.1', 31500) s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind(address) while True: data, addr = s.recvfrom(2048) if not data: print "client has ex

  • 详解Android 基于TCP和UDP协议的Socket通信

    本来想讲一下基础的网络通信方面的知识点,发现太枯燥乏味了,不过笔试中也经常会问到这方面的问题,所以关于通信方面的知识点,小编会放到面试中去,因为实战中也就面试会用到这方面知识点 Android与服务器的通信方式主要有两种,一是Http通信,一是Socket通信.两者的最大差异在于,http连接使用的是"请求-响应方式",即在请求时建立连接通道,当客户端向服务器发送请求后,服务器端才能向客户端返回数据. 而Socket通信中基于TCP/IP协议的通信则是在双方建立起连接后就可以直接进行数

  • 详情解析TCP与UDP传输协议

    目录 一.什么是 socket ? 二.Socket 编程的重要概念 ① IP 地址 ② TCP/IP 端口 ③ 协议 三.socket 编程的 API 接口 ① Linux 下的 socket API 接口 ② windows 下的 socket API 接口 ③ TCP.UDP 通信的 socket 编程流程图 四.socket 的应用实例 ① 基于 TCP 的本地客户端.服务端信息交互实例 ② 基于 UDP 的本地客户端.服务端信息交互实例 一.什么是 socket ? Socket 的英

  • Android中实现TCP和UDP传输实例

    TCP和UDP在网络传输中非常重要,在Android开发中同样重要. 首先我们来看一下什么是TCP和UDP. 什么是TCP? TCP:Transmission Control Protocol 传输控制协议TCP是一种面向连接(连接导向)的.可靠的.基于字节流的运输层(Transport layer)通信协议,由IETF的RFC 793说明(specified).在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能.应用层向TCP层发送用于网间传输的.用8位字节表示的数据流,然后TCP

  • java 中模拟UDP传输的发送端和接收端实例详解

    java 中模拟UDP传输的发送端和接收端实例详解 一.创建UDP传输的发送端 1.建立UDP的Socket服务: 2.将要发送的数据封装到数据包中: 3.通过UDP的Socket服务将数据包发送出去: 4.关闭Socket服务. import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; public class

  • Java后端学习精华之TCP通信传输协议详解

    目录 Socket连接模型 消息协议 传输过程中数据类型需要了解的细节 TCP通信代码 上篇教程回顾 ServerSocket --监听客户端的连接,他的作用主要是建立一个连接 -ServerSocket -建立连接,拿到一个Socket -Telnet 127.0.0.1 8888- 客户端使用Telnet访问服务端 建立连接 -服务端可以拿到一个Socket的对象 -获取这个对象的输入输出流 -写入和读取数据 Socket连接模型 服务端和客户端通过Socket进行连接,虽然是一个Socke

  • tcp、udp、ip协议分析_动力节点Java学院整理

    互连网早期的时候,主机间的互连使用的是NCP协议.这种协议本身有很多缺陷,如:不能互连不同的主机,不能互连不同的操作系统,没有纠错功能.为了改善这种缺点,大牛弄出了TCP/IP协议.现在几乎所有的操作系统都实现了TCP/IP协议栈. TCP/IP协议栈主要分为四层:应用层.传输层.网络层.数据链路层,每层都有相应的协议,如下图 所谓的协议就是双方进行数据传输的一种格式.整个网络中使用的协议有很多,所幸的是每一种协议都有RFC文档.在这里只对IP.TCP.UDP协议头做一个分析. 首先来看看在网络

  • 计算机网络传输协议TCP三次握手与四次挥手原理

    目录 TCP三次握手四次挥手 服务器状态转换 客户端状态转换 TCP状态转换图 TCP中常见的面试题 为什么是三次握手,不是一次或者两次 为什么是三次握手,四次挥手 如果已经建立了连接,但是客户端突然出现故障了怎么办? 为什么会有TIME_WAIT状态 我们来想一想,为什么TIME_WAIT的时间是2MSL 解决TIME_WAIT状态引起的bind失败的方法 TCP三次握手四次挥手 我们之前在 传输层协议TCP与UDP中详细介绍了UDP协议和TCP协议格式以及他们各自的特点,我们知道TCP协议是

  • 网络传输协议(http协议)

    概述:指服务器和客户端间进行通信时的约束和规范,客户端与服务端的数据交互并不是杂乱无章的,需要遵照(基于)一定的规范进行 常见的协议: a) HTTP.HTTPS 超文本传输协议 b) FTP 文件传输协议 c) SMTP 简单邮件传输协议 本文主要介绍http超文本传输协议. 1.HTTP协议 即超文本传输协议,网站是基于HTTP协议的,例如网站的图片.CSS.JS等都是基于HTTP协议进行传输的.HTTP协议是由从客户机到服务器的请求(Request)和从服务器到客户机的响应(Respons

  • Python树莓派学习笔记之UDP传输视频帧操作详解

    本文实例讲述了Python树莓派学习笔记之UDP传输视频帧操作.分享给大家供大家参考,具体如下: 因为我在自己笔记本电脑上没能成功安装OpenCV-Contrib模块,因此不能使用人脸识别等高级功能,不过已经在树莓派上安装成功了,所以我想实现把树莓派上采集的视频帧传输到PC的功能,这样可以省去给树莓派配显示屏的麻烦,而且以后可能可以用在远程监控上. 1 UDP还是TCP 首先考虑用哪种传输方式,平常TCP用的非常多,但是像视频帧这种数据用TCP不是太合适,因为视频数据的传输最先要考虑的是速度而不

  • 图文分析详解计算机网络TCP与UDP两者区别及原理

    目录 1.概念 2.从是否面向连接来看 2.1.UDP 2.2.TCP 3.从连接对象个数来看 3.1.UDP 3.2.TCP 4.从对应用报文的处理来看 4.1.UDP 4.2.TCP 发送方: 接收方: 5.从向其上层提供传输服务来看 5.1.UDP 5.2.TCP 6.从首部格式来看 6.1.UDP 6.2.TCP 7.总结 1.概念 UDP和TCP 在使用TCP/IP 体系结构的网络通信中,这两个协议的使用频率仅次于网际层的IP协议.TCP/IP 体系结构应用层中的某些协议需要使用运输层

随机推荐