《TCP/IP网络编程》(第六章)基于UDP的服务器端/客户端

1.UDP/IP协议栈

在这里插入图片描述

2.基于UDP/IP服务器端/客户端函数调用关系

在这里插入图片描述
PS: 客户端服务器不需要使用bind()函数将地址信息分配到套接字上,这一操作在调用sendto()函数时完成。

3.Linux系统实现迭代回声服务器/客户端

回声服务器/客户端的要求已经在前面的章节讲过了,这里不再赘述,下面是基于UDP的数据I/O函数

  • sendto()函数
ssize_t sendto(
int sock, //这是套接字描述符,是一个整数值,唯一标识了一个打开的套接字。
const void *buff, //指向要发送的数据缓冲区的指针。
size_t nbytes, //这是要发送的数据的长度(以字节为单位)。
int flags,//这是一个选项标志,用于修改 sendto 函数的行为。
const struct sockaddr *to, //指向目标地址的指针,该地址结构包含了目的主机的网络地址信息。
socklen_t addrlen//这是目的地址结构 (to) 的长度。
);
  • recvfrom()函数
ssize_t recvfrom(
int sock, //这是套接字描述符,是一个整数值,唯一标识了一个打开的套接字。
void *buff, // 指向一个缓冲区的指针,用于存储接收到的数据。
size_t nbytes, // 缓冲区的长度,即可以接收的最大数据量。
int flags,// 用于修改recvfrom行为的标志位,通常设置为0。
struct sockaddr *from, // 指向一个sockaddr结构的指针,用于存储发送方的地址信息。
socklen_t *addrlen// sockaddr结构的大小
);

服务器端代码

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> // POSIX标准定义的通用函数,如close()
#include <arpa/inet.h> // 提供inet相关的函数,如inet_addr()
#include <sys/socket.h> // 提供socket相关的函数和数据结构

#define BUFF_SIZE 30 //缓冲区大小
void error_handling(char* message);

int main(int argc, char *argv[]) 
{
    int serv_sock; // 服务器套接字
    int clnt_sock; // 客户端套接字
    char opinfor[BUFF_SIZE];
	int str_len,recvLen,clntAdrSize;

    struct sockaddr_in serv_addr; // 服务器地址结构
    struct sockaddr_in clnt_addr; // 客户端地址结构
    socklen_t clnt_addr_size; // 客户端地址结构的大小

    
    if(argc!=2)
    {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1); 
    }

    // 创建一个服务器套接字
    serv_sock=socket(PF_INET, SOCK_DGRAM, 0);//使用SOCK_DGRAM创建UDP套接字
    if(serv_sock==-1) 
        error_handling("socket() error"); 

    // 初始化服务器地址结构
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family=AF_INET; // 地址族设置为IPv4
    serv_addr.sin_addr.s_addr=htonl(INADDR_ANY); // 服务器地址设置为任意
    serv_addr.sin_port=htons(atoi(argv[1])); // 设置监听端口为命令行参数指定的端口

    // 绑定套接字,调用bind()函数分配ip地址和端口号
    if(bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr))==-1)
        error_handling("bind() error"); 
    
    while(1)
    {
        clnt_addr_size=sizeof(clnt_addr);
        recvLen=recvfrom(serv_sock, opinfor, BUFF_SIZE, 0, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        if(recvLen==-1)
            error_handling("recvfrom() error");
        sendto(serv_sock, opinfor, recvLen, 0, (struct sockaddr*)&clnt_addr, clnt_addr_size);
    }
    
    // 关闭客户端和服务器套接字
    close(serv_sock);
    
    return 0; 
}

void error_handling(char *message)
{
    fputs(message, stderr); 
    fputc('\n', stderr); 
    exit(1); 
}

客户端代码

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <unistd.h> 
#include <arpa/inet.h> 
#include <sys/socket.h> 

#define BUFF_SIZE 30 // 缓冲区大小
void error_handling(char *message);

int main(int argc, char *argv[]) 
{
    int sock; // 客户端套接字
    struct sockaddr_in serv_addr; // 服务器地址结构
    char opmsg[BUFF_SIZE]; // 用于接收从服务器发送的消息
    int str_len;

    
    if (argc != 3)
    {
        printf("Usage : %s <IP> <port>\n", argv[0]);
        exit(1); 
    }

    // 创建一个客户端套接字
    sock = socket(PF_INET, SOCK_DGRAM, 0);//使用SOCK_DGRAM创建UDP套接字
    if (sock == -1)
        error_handling("socket() error");
   
    // 初始化服务器地址结构
    memset(&serv_addr, 0, sizeof(serv_addr));
    serv_addr.sin_family = AF_INET; // 地址族设置为IPv4
    serv_addr.sin_addr.s_addr = inet_addr(argv[1]); // 设置服务器IP地址
    serv_addr.sin_port = htons(atoi(argv[2])); // 设置服务器端口号

    while (1)
    {
        fputs("Insert message(Q to quit): ", stdout);
        fgets(opmsg, BUFF_SIZE, stdin);

        if (!strcmp(opmsg, "q\n") || !strcmp(opmsg, "Q\n"))
            break;

        sendto(sock, opmsg, strlen(opmsg), 0, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
        str_len = recvfrom(sock, opmsg, BUFF_SIZE, 0, NULL, 0);
        opmsg[str_len] = 0;
        printf("Message from server: %s", opmsg);
    }

    // 关闭套接字
    close(sock);
    return 0; 
}


void error_handling(char* message){
    fputs(message, stderr); 
    fputc('\n', stderr); 
    exit(1); 
}

运行代码
在这里插入图片描述

4.Windows系统实现迭代回声服务器/客户端

该程序示例迁移到Windows系统差异不大

  • sendto()函数
int sendto(
  SOCKET s,// 套接字描述符,用于指定发送数据的套接字。
  const char FAR *buf,// 指向要发送的数据缓冲区的指针。
  int len,// 要发送的数据长度。
  int flags,// 控制选项,通常设置为 0。
  const struct sockaddr FAR *to,// 指向目标地址的结构体指针,包含目的地址信息。
  int tolen// 上述目标地址结构体的大小。
);
  • recvfrom()函数
int recvfrom(
  SOCKET s,// 上述目标地址结构体的大小。
  char FAR *buf,// 指向用于存储接收数据的缓冲区的指针。
  int len,// 缓冲区的长度。
  int flags,// 控制选项,通常设置为 0。
  struct sockaddr FAR *from,// 指向存储发送方地址的结构体指针。
  int FAR *fromlen // 指向变量的指针,该变量在调用前包含 from 缓冲区的长度,在调用后包含返回地址结构的实际长度。
);

服务器端代码

#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")// 指定链接到winsock库

#define BUFF_SIZE 30 //缓冲区大小
void error_handling(char* message);


int main(int argc, const char* argv[])
{
	WSADATA wsaData;// Windows Sockets API需要的数据结构
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAddr, clntAddr;
	char message[BUFF_SIZE];
	int strlen,clntAdrSize;

	if (argc != 2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)// 初始化Windows Sockets服务
		error_handling("WSAStartup() error!");

	//创建套接字	
	hServSock = socket(PF_INET, SOCK_DGRAM, 0);//使用SOCK_DGRAM,即创建UDP套接字
	if (hServSock == INVALID_SOCKET)
		error_handling("socket() error");
		
	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY); 
	servAddr.sin_port = htons(atoi(argv[1]));

	// 将socket绑定到地址和端口
	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)
		error_handling("bind() error");
	
	while(1){
		clntAdrSize = sizeof(clntAddr);// 设置客户端地址结构的大小
		strlen = recvfrom(hServSock, message, BUFF_SIZE, 0, (SOCKADDR*)&clntAddr, &clntAdrSize);
		sendto(hServSock, message, strlen, 0, (SOCKADDR*)&clntAddr, clntAdrSize);

	}

	closesocket(hServSock);// 关闭服务器socket
	WSACleanup();// 清理Windows Sockets服务
	return 0;
}

void error_handling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

客户端代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib") // 指定链接到winsock库

#define BUFF_SIZE 30 // 缓冲区大小

void ErrorHandling(const char *message);

int main(int argc, char *argv[])
{
  WSADATA wsaData;
  SOCKET hSocket;
  SOCKADDR_IN servAddr;  // 用于存储服务器的地址信息
  char message[BUFF_SIZE]; // 用于接收从服务器发送的消息
  int message_size;
  if (argc != 3)
  {
    printf("Usage : %s <IP> <port>\n", argv[0]);
    exit(1);
  }

  // 初始化Windows Sockets服务
  if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    ErrorHandling("WSAStartup() error!");

  // 创建套接字
  hSocket = socket(PF_INET, SOCK_DGRAM, 0);//使用SOCK_DGRAM,即创建UDP套接字
  if (hSocket == INVALID_SOCKET)
    ErrorHandling("socket() error");

  memset(&servAddr, 0, sizeof(servAddr));
  servAddr.sin_family = AF_INET;                 // 设置地址族为IPv4
  servAddr.sin_addr.s_addr = inet_addr(argv[1]); // 设置服务器的IP地址
  servAddr.sin_port = htons(atoi(argv[2]));      // 设置服务器的端口号

  while(1){
    printf("Input message(Q to quit): ");
    fgets(message, BUFF_SIZE, stdin);
    if (!strcmp(message, "Q\n") || !strcmp(message, "q\n"))
      break;
 
  sendto(hSocket, message, strlen(message), 0, (SOCKADDR *)&servAddr, sizeof(servAddr));
  message_size = recvfrom(hSocket, message, BUFF_SIZE, 0, NULL, NULL);
  message[message_size] = 0;
  printf("Message from server: %s", message);
  }

  
  closesocket(hSocket); // 关闭套接字
  WSACleanup();         // 清理Windows Sockets服务
  return 0;
}

void ErrorHandling(const char *message)
{
  fputs(message, stderr);
  fputc('\n', stderr);
  exit(1);
}

运行代码
在这里插入图片描述

5.UDP的数据传输特性

  • 无连接: UDP可以不建立连接发送数据
  • 低延迟: 因为缺少连接建立的维护和开销,通常比TCP更快,但每次交换的数据量越大,TCP的速率越接近UDP
  • 不保证完整性、不保证交付: 不能够保证数据报的顺序、完整性,也无法保证数据能够到达目的地
  • 没有流控制: 流控制是UDP和TCP的重要区分标志。
  • 由于以上特点,适合容忍数据丢失一部分的场景,例如视频流、游戏等

6.已连接的UDP和未连接的UDP

通过sendto()函数传输数据大致分为三个阶段:

  • 向UDP套接字注册目标IP和端口号
  • 传输数据
  • 删除UDP套接字注册目标IP和端口号

所以如果要和同一主机进行长时间的通信,使用已连接的UDP套接字可以提升效率,这样可以省去第一和第三阶段。
创建已连接的UDP套接字,使用connect()函数,当使用已连接的UDP套接字之后,便可以使用write()/read()函数进行通信

PS: 调用connect并不意味着和对方UDP套接字连接,只是向UDP套接字注册IP和端口号信息

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,我可以为您提供一些关于创建基于TCP服务器端/客户端的指导。 首先,您需要了解一些基本概念和步骤: 1. TCP/IP协议:TCP/IP协议是一种常用的网络通信协议,用于控制网络上的数据传输。它是由两个协议组成的:TCP(传输控制协议)和IP(网际协议)。 2. 服务器端服务器端是指在网络上提供服务的计算机程序。服务器端通常需要创建一个socket,监听客户端的连接请求,并接受来自客户端的请求,处理请求并返回响应。 3. 客户端客户端是指连接到服务器端的计算机程序。客户端通常需要创建一个socket,连接到服务器端,并发送请求,等待服务器端响应。 接下来,我们可以按照以下步骤创建一个基于TCP服务器端/客户端,用于收发文件: 1. 创建服务器端socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 绑定服务器端socket:使用bind()函数将socket与服务器端地址(IP地址和端口号)绑定起来。 3. 监听客户端连接请求:使用listen()函数启动服务器端socket监听客户端连接请求。 4. 接受客户端连接请求:使用accept()函数接受来自客户端的连接请求,并创建一个新的socket用于与客户端通信。 5. 接收客户端发送的文件:使用recv()函数从客户端接收文件数据,并写入到服务器端的文件中。 6. 发送响应给客户端:使用send()函数向客户端发送一个响应,表示文件传输完成。 7. 关闭服务器端socket:使用close()函数关闭服务器端socket。 客户端的步骤如下: 1. 创建客户端socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 连接服务器端:使用connect()函数连接到服务器端,指定服务器端地址(IP地址和端口号)。 3. 打开要发送的文件:使用fopen()函数打开要发送的文件。 4. 发送文件数据给服务器端:使用send()函数将文件数据发送给服务器端。 5. 接收服务器端的响应:使用recv()函数接收服务器端的响应,判断文件是否传输完成。 6. 关闭客户端socket:使用close()函数关闭客户端socket。 以上是基于TCP服务器端/客户端的基本步骤,您可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青石横刀策马

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值