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. 网络编程主要函数
socket int socket(int domain, int type,int protocol); 此函数用于创建一个套接字。
- domain 是网络程序所在的主机采用的通讯协族(AF_UNIX 和 AF_INET等)。
- AF_UNIX 只能够用于单一的 Unix 系统进程间通信,而
AF_INET 是针对 Internet 的,因而可以允许远程通信使用。
- AF_UNIX 只能够用于单一的 Unix 系统进程间通信,而
- 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:标准输入流,通常指向键盘输入。
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 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 服务器端(接收数据):
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 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;
}