网络编程:TCP/UDP

147 阅读9分钟

1. 网络通信

1.1 IP和端口

所有的数据传输,都有三个要素 :源、目的、长度。怎么表示源或者目的呢?

1747141514228.png

在网络传输中需要使用“ IP 和端口”来表示源或目的。

1.2 网络传输中的 2 个对象: server 和 client

1747141700791.png

1.3 两种传输方式: TCP/UDP

网络协议一般被分为5层

1747142665591.png

应用层: 它是体系结构中的最高层, 直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。在因特网中的应用层协议很多,如支持万维网应用的 HTTP 协议,支持电子邮件的 SMTP 协议,支持文件传送的 FTP 协议, DNS,POP3, SNMP, Telnet 等等。
运输层: 负责向两个主机中进程之间的通信提供服务。 运输层主要使用以下两种协议:
传输控制协议 TCP(Transmission Control Protocol):面向连接的,数据传输的单位是报文段,能够提供可靠的交付。
用户数据包协议 UDP(User Datagram Protocol):无连接的,数据传输的单位是用户数据报,不保证提供可靠的交付,只能提供“尽最大努力交付”。
网络层: 负责将被称为数据包(datagram)的网络层分组从一台主机移动到另一台主机。
链路层: 因特网的网络层通过源和目的地之间的一系列路由器路由数据报
物理层: 在物理层上所传数据的单位是比特。物理层的任务就是透明地传送比特流

1.4 TCP 和 UDP 原理上的区别

TCP 向它的应用程序提供了面向连接的服务。这种服务有 2 个特点:可靠传输、流量控制(即发送方/接收方速率匹配)。它包括了应用层报文划分为短报文, 并提供拥塞控制机制。
UDP 协议向它的应用程序提供无连接服务。它没有可靠性,没有流量控制,也没有拥塞控制

1.5 为何存在 UDP 协议

既然 TCP 提供了可靠数据传输服务,而 UDP 不能提供,那么 TCP 是否总是首选呢?
答案是否定的,因为有许多应用更适合用UDP,举个例子:视频通话时,使用UDP,偶尔的丢包、偶尔的花屏时可以忍受的;如果使用TCP,每个数据包都要确保可靠传输,当它出错时就重传,这会导致后续的数据包被阻滞,视频效果反而不好。
使用 UDP 时,有如下特点:
(1)关于何时发送什么数据控制的更为精细,采用 UDP 时只要应用进程将数据传递给 UDP, UDP就会立即将其传递给网络层。而 TCP 有重传机制,而不管可靠交付需要多长时间。但是实时应用通常不希望过分的延迟报文段的传送,且能容忍一部分数据丢失。
(2)无需建立连接,不会引起建立连接时的延迟
(3)无连接状态,能支持更多的活跃客户
(4)分组首部开销较小

1.6 TCP/UDP 网络通信交互图

1747143778532.png

1747146413865.jpg

image.png

1747225257320.jpg

1.7 通信方式

进程间通信(Inter-Process Communication)是不同进程之间交换数据的机制,主要分为 本地 IPC 和 网络 IPC

类型通信范围常见方式特点
本地 IPC同一台主机的进程管道、消息队列、共享内存、信号量、套接字高效、低延迟、基于内核或内存共享
网络 IPC跨主机的进程套接字(Socket)支持跨网络通信、标准化协议(TCP/IP)

2. 网络编程主要函数

socket int socket(int domain, int type,int protocol); 此函数用于创建一个套接字。

  • domain 是网络程序所在的主机采用的通讯协族(AF_UNIX 和 AF_INET等)。
    • AF_UNIX 只能够用于单一的 Unix 系统进程间通信,而 AF_INET 是针对 Internet 的,因而可以允许远程通信使用。
  • type 是网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM 等)。
    • SOCK_STREAM 表明用的是 TCP 协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。
    • SOCK_DGRAM 表明用的是UDP 协议,这样只会提不可靠,无连接的通信。
  • 关于 protocol,由于指定了 type,所以这个地方一般只要用 0 来代替就可以了。
    此函数执行成功时返回文件描述符,失败时返回-1,看 errno 可知道出错的详细情况。

bind

int bind(int sockfd, struct sockaddr my_addr, int addrlen);

从函数用于将地址绑定到一个套接字。
⚫ sockfd 是由 socket 函数调用返回的文件描述符。
⚫ my_addr 是一个指向 sockaddr 的指针。
⚫ addrlen 是 sockaddr 结构的长度。 不 过 由 于 系 统 的 兼 容 性 , 我们一般使用另外一个结构(struct sockaddr_in) 来代替。 sockaddr_in 的定义:

struct sockaddr_in{
unsigned short sin_family;
unsigned short sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
}

如果使用 Internet 所以 sin_family 一般为 AF_INET。
⚫ sin_addr 设置为 INADDR_ANY 表示可以和任何的主机通信。
⚫ sin_port 是要监听的端口号。
⚫ bind 将本地的端口同 socket 返回的文件描述符捆绑在一起.成功是返回 0, 失败的情况和 socket 一样 listen

accept

connect

send

recv

recvfrom

sendto

memset(tSocketServerAddr.sin_zero, 0, 8);

功能

  • 清零结构体填充字段:将 sockaddr_in 结构体的 sin_zero 字段初始化为全零。
  • 作用:确保结构体在内存中没有未初始化的数据,避免潜在的安全隐患或数据传输错误。

参数解析

  • tSocketServerAddr:类型为 struct sockaddr_in,用于存储 IPv4 地址和端口信息。
  • sin_zero:一个 8 字节的填充字段,用于保证 sockaddr_in 与 sockaddr 结构体大小一致。
  • memset:内存设置函数,第二个参数 0 表示填充值,第三个参数 8 表示填充字节数。

inet_aton(argv[1], &tSocketServerAddr.sin_addr)

功能

  • IP地址转换:将点分十进制格式的 IPv4 地址字符串(如 "192.168.1.100")转换为网络字节序的二进制形式,并存储到 sin_addr 字段。

参数解析

  • argv[1] :命令行传入的字符串参数,表示服务器的 IPv4 地址。
  • tSocketServerAddr.sin_addr:类型为 struct in_addr,用于存储转换后的二进制 IP 地址。

函数过时inet_aton 已逐渐被 inet_pton 取代(支持 IPv6)

inet_pton(AF_INET, argv[1], &tSocketServerAddr.sin_addr);

fgets(ucSendBuf, 999, stdin);

功能

  • 读取用户输入:从标准输入(键盘)读取最多 999 个字符(包括换行符)到缓冲区 ucSendBuf 中。

参数解析

  • ucSendBuf:字符数组(缓冲区),用于存储输入数据。
  • 999:最大读取字符数(实际有效字符数为 999-1,预留一个位置给空终止符 \0)。
  • stdin:标准输入流,通常指向键盘输入。
  1. memset:确保结构体填充字段清零,避免未定义行为。
  2. inet_aton:将字符串 IP 转换为二进制格式,需注意过时问题和错误处理。
  3. fgets:安全读取用户输入,需处理换行符和缓冲区溢出。

3. TCP编程

3.1 服务器端(接收数据):

1747208351818.jpg

3.2 客户端(发送数据):

1747209350484.png

3.3 服务器测试结果:

1747215086023.jpg

signal(SIGCHLD,SIG_IGN);子进程结束后需要回收 使用ps -A查看 1747216870716.png

3.4 开发板测试结果:

1747218328558.jpg

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>


/* socket
 * bind
 * listen
 * accept
 * send/recv
 */

#define SERVER_PORT 8888
#define BACKLOG     10

int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;
	int iRet;
	int iAddrLen;

	int iRecvLen;
	unsigned char ucRecvBuf[1000];

	int iClientNum = -1;

	signal(SIGCHLD,SIG_IGN);
	
	iSocketServer = socket(AF_INET, SOCK_STREAM, 0);
	if (-1 == iSocketServer)
	{
		printf("socket error!\n");
		return -1;
	}

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(tSocketServerAddr.sin_zero, 0, 8);
	
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (-1 == iRet)
	{
		printf("bind error!\n");
		return -1;
	}

	iRet = listen(iSocketServer, BACKLOG);
	if (-1 == iRet)
	{
		printf("listen error!\n");
		return -1;
	}

	while (1)
	{
		iAddrLen = sizeof(struct sockaddr);
		iSocketClient = accept(iSocketServer, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
		if (-1 != iSocketClient)
		{
			iClientNum++;
			printf("Get connect from client %d : %s\n",  iClientNum, inet_ntoa(tSocketClientAddr.sin_addr));
			if (!fork())
			{
				/* 子进程的源码 */
				while (1)
				{
					/* 接收客户端发来的数据并显示出来 */
					iRecvLen = recv(iSocketClient, ucRecvBuf, 999, 0);
					if (iRecvLen <= 0)
					{
						close(iSocketClient);
						return -1;
					}
					else
					{
						ucRecvBuf[iRecvLen] = '\0';
						printf("Get Msg From Client %d: %s\n", iClientNum, ucRecvBuf);
					}
				}				
			}
		}
	}
	
	close(iSocketServer);
	return 0;
}


#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	unsigned char ucSendBuf[1000];
	int iSendLen;

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

	iSocketClient = socket(AF_INET, SOCK_STREAM, 0);

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
 	{
		printf("invalid server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);


	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
	if (-1 == iRet)
	{
		printf("connect error!\n");
		return -1;
	}

	while (1)
	{
		if (fgets(ucSendBuf, 999, stdin))
		{
			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
			if (iSendLen <= 0)
			{
				close(iSocketClient);
				return -1;
			}
		}
	}
	
	return 0;
}

4. UDP编程

4.1 服务器端(接收数据):

1747227145208.png

4.2 客户端(发送数据):

1747226950498.jpg

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>


/* socket
 * bind
 * sendto/recvfrom
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketServer;
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	struct sockaddr_in tSocketClientAddr;
	int iRet;
	int iAddrLen;

	int iRecvLen;
	unsigned char ucRecvBuf[1000];

	int iClientNum = -1;
	
	iSocketServer = socket(AF_INET, SOCK_DGRAM, 0);
	if (-1 == iSocketServer)
	{
		printf("socket error!\n");
		return -1;
	}

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
	memset(tSocketServerAddr.sin_zero, 0, 8);
	
	iRet = bind(iSocketServer, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));
	if (-1 == iRet)
	{
		printf("bind error!\n");
		return -1;
	}


	while (1)
	{
		iAddrLen = sizeof(struct sockaddr);
		iRecvLen = recvfrom(iSocketServer, ucRecvBuf, 999, 0, (struct sockaddr *)&tSocketClientAddr, &iAddrLen);
		if (iRecvLen > 0)
		{
			ucRecvBuf[iRecvLen] = '\0';
			printf("Get Msg From %s : %s\n", inet_ntoa(tSocketClientAddr.sin_addr), ucRecvBuf);
		}
	}
	
	close(iSocketServer);
	return 0;
}

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>

/* socket
 * connect
 * send/recv
 */

#define SERVER_PORT 8888

int main(int argc, char **argv)
{
	int iSocketClient;
	struct sockaddr_in tSocketServerAddr;
	
	int iRet;
	unsigned char ucSendBuf[1000];
	int iSendLen;
	int iAddrLen;

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

	iSocketClient = socket(AF_INET, SOCK_DGRAM, 0);

	tSocketServerAddr.sin_family      = AF_INET;
	tSocketServerAddr.sin_port        = htons(SERVER_PORT);  /* host to net, short */
 	//tSocketServerAddr.sin_addr.s_addr = INADDR_ANY;
 	if (0 == inet_aton(argv[1], &tSocketServerAddr.sin_addr))
 	{
		printf("invalid server_ip\n");
		return -1;
	}
	memset(tSocketServerAddr.sin_zero, 0, 8);

#if 0
	iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));	
	if (-1 == iRet)
	{
		printf("connect error!\n");
		return -1;
	}
#endif

	while (1)
	{
		if (fgets(ucSendBuf, 999, stdin))
		{
#if 0
			iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);
#else
			iAddrLen = sizeof(struct sockaddr);
			iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,
			                      (const struct sockaddr *)&tSocketServerAddr, iAddrLen);

#endif
			if (iSendLen <= 0)
			{
				close(iSocketClient);
				return -1;
			}
		}
	}
	
	return 0;
}