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);此函数用于创建一个套接字。
- domain 是网络程序所在的主机采用的通讯协族(AF_UNIX 和 AF_INET等)。
- AF_UNIX 只能够用于单一的 Unix 系统进程间通信,而
AF_INET 是针对 Internet 的
,因而可以允许远程通信
使用。SOCK_STREAM
表明用的是 TCP
协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。SOCK_DGRAM
表明用的是UDP
协议,这样只会提不可靠,无连接的通信。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
:标准输入流,通常指向键盘输入。
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;}