掘金 人工智能 05月15日 10:38
网络编程:TCP/UDP
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

本文深入探讨了网络通信的基础概念,包括IP地址和端口的作用,以及TCP和UDP两种主要的传输协议。文章详细对比了TCP的面向连接、可靠传输特性与UDP的无连接、高效传输特性,并解释了为何在某些场景下UDP更适用,例如视频通话。此外,还介绍了网络编程中常用的socket、bind、listen、accept、connect、send、recv等函数,以及TCP和UDP的编程实例,包括服务器端和客户端的代码实现,旨在帮助读者理解网络通信的原理并掌握基本的网络编程技能。

🌐 **IP与端口:** IP地址和端口是网络通信中用于标识源和目的地的关键要素,确保数据能够准确地在网络中传输。

🚀 **TCP与UDP:** TCP提供面向连接的可靠传输服务,适用于对数据完整性要求高的场景;UDP提供无连接的快速传输服务,适用于实时性要求高的场景,如视频通话,即使偶有丢包也能保证流畅体验。

🛠️ **网络编程函数:** socket函数用于创建套接字,bind函数将地址绑定到套接字,listen函数监听连接,accept函数接受连接,connect函数建立连接,send/recv函数用于发送和接收数据,这些函数是网络编程的基础。

🔑 **TCP编程实例:** 文章提供了TCP服务器端和客户端的详细代码示例,展示了如何使用socket、bind、listen、accept、send和recv等函数实现可靠的网络通信。

📡 **UDP编程实例:** 文章提供了UDP服务器端和客户端的代码示例,展示了如何使用socket、bind、sendto和recvfrom等函数实现无连接的网络通信。

1. 网络通信

1.1 IP和端口

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

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

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

1.3 两种传输方式: TCP/UDP

网络协议一般被分为5层

应用层: 它是体系结构中的最高层, 直接为用户的应用进程(例如电子邮件、文件传输和终端仿真)提供服务。在因特网中的应用层协议很多,如支持万维网应用的 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 网络通信交互图

1.7 通信方式

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

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

2. 网络编程主要函数

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

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);

功能

参数解析

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

功能

参数解析

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

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

fgets(ucSendBuf, 999, stdin);

功能

参数解析

    memset:确保结构体填充字段清零,避免未定义行为。inet_aton:将字符串 IP 转换为二进制格式,需注意过时问题和错误处理。fgets:安全读取用户输入,需处理换行符和缓冲区溢出。

3. TCP编程

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

3.2 客户端(发送数据):

3.3 服务器测试结果:

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

3.4 开发板测试结果:

#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     10int 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 8888int 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 服务器端(接收数据):

4.2 客户端(发送数据):

#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 8888int 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 8888int 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 0iRet = connect(iSocketClient, (const struct sockaddr *)&tSocketServerAddr, sizeof(struct sockaddr));if (-1 == iRet){printf("connect error!\n");return -1;}#endifwhile (1){if (fgets(ucSendBuf, 999, stdin)){#if 0iSendLen = send(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0);#elseiAddrLen = sizeof(struct sockaddr);iSendLen = sendto(iSocketClient, ucSendBuf, strlen(ucSendBuf), 0,                      (const struct sockaddr *)&tSocketServerAddr, iAddrLen);#endifif (iSendLen <= 0){close(iSocketClient);return -1;}}}return 0;}

Fish AI Reader

Fish AI Reader

AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

FishAI

FishAI

鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

联系邮箱 441953276@qq.com

相关标签

网络通信 TCP UDP Socket编程 网络协议
相关文章