Linux Day18 TCP_UDP协议及相关知识

一、网络基础概念

1.1 网络

网络是由若干结点和连接这些结点的链路组成,网络中的结点可以是计算机,交换机、

路由器等设备。

1.2 互联网

把多个网络连接起来就构成了互联网。目前最大的互联网就是因特网。

网络设备有:交换机、路由器、集线器

传输介质有:双绞线、同轴电缆、光纤,无线

00530813c3d64ef5a92822bd3ceacfae.png8266b7137811410998c777e59b748f9a.gif

1.3 IP地址

IP 地址就是给因特网上的每一个主机(或路由器)的每一个接口分配的一个在全世界

范围内唯一的标识符。IP 地址因其特殊的结构使我们可以在因特网上很方便地进行寻址。

IP 地址有分 IPV4 和 IPV6 两种类别格式,IPV4 是类似”A.B.C.D”的格式,它是 32 位的,用“.”分成四个段,每个段是 8 个位(值为 0-255),用 10 进制表示。IPV6 地址是 128 位,格式类似”XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX:XXXX”,用“:“分成 8 个段,每个段 16 个位,用 4 个 16 进制数表示。

接下来描述的 IP 地址默认都指的是 IPV4 的地址。为了便于寻址,了解目标主机的位置,每个 IP 地址由网络号和主机号两个部分构成。同一个物理网络上所有的主机都使用同 一个网络号,只是主机号不同

1.4 inet_addr()

计算机使用的是无符号整型,而用户熟悉的是点分十进制的字符串。

该函数可以将点分十进制字符串表示的ipv4地址转化为用网络字节序整数表示的ipv4.即无符号整型

1.5 查看ip地址方法

1、Windows

win+r --->cmd-->ipconfig

f93a7b326a984b978c3c0ed4bdb7e210.png6da50f2572344364a15c84e5c8b536a1.gif

2、Linux

ifconfig

1.6 端口号

应用程序的代号,进程号是会变的,但是端口号不会变。所以我们使用端口号去唯一标识应用程序的。

1.7 127.0.0.1

127.0.0.1是回送地址,指本地机,一般用来测试使用。回送地址(127.x.x.x)是本机回送地址(Loopback Address),即主机IP堆栈内部的IP地址,主要用于网络软件测试以及本地机进程间通信,无论什么程序,一旦使用回送地址发送数据,协议软件立即返回,不进行任何网络传输。

通信需要:源ip+端口 目的ip+端口

1.8 协议

网络协议就是一组网络规则的集合,是我们共同遵守的约定或标准。常见的协议:

◼ HTTP:超文本传输协议

◼ FTP: 文件传输协议

◼ TELNET : 是 internet 远程登陆服务的标准协议。

◼ TCP : 传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可

靠的、基于字节流的传输层通信协议

◼ UDP :用户数据报协议

◼ IP : Internet Protocol 简称 IP,又译为网际协议或互联网协议

◼ ICMP :因特网控制报文协议

◼ ARP : 地址解析协议,是根据 IP 地址获取 MAC 地址的协议

◼ RARP : 逆地址解析协议

TCP报头

d82d8e3d4206482d96661a5c723206e3.png

这里的ACK,FIN,SYN后面介绍

1.9 服务器端编写

1.9.0 OSI 的 7 层模型与 tcp/ip 协议族体系 4 层结构**

1.9.0.1 OSI/ISO模型

应用层

表示层

会话层

传输层 加TCP/IP协议

网络层 加IP协议

数据链路层 加帧

物理层

1.9.0.2 TCP/IP模型

应用层

表示层

网络层

网际接口层(数据链路层+物理层)

tcp的通信流程80d8fbb4c2044513b03cc430b5a396b7.png

1.9.1 创建套接字

 

int socket(int domain, int type, int protocol)

有了套接字就可以通过网络进行数据的收发

socket()创建套接字,成功返回套接字的文件描述符,失败返回-1

domain: 设置套接字的协议族, AF_INET(IPv4)和 AF_INET6(IPv6)

type: 设置套接字的服务类型 SOCK_STREAM(流服务,用于TCP协议)和SOCK_DGRAM(数据报,用于UDP协议)

注意 PF开头的是协议族,AF开头的是地址族,在Windows上没有任何区别,在Linux上差别不大

4a0cbf9c0be545af8cdae097df07e015.png

protocol: 一般设置为 0,表示使用默认协议

1.9.2 命名socket

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

用来指定套接字使用的IP地址和端口。

bind()将 sockfd 与一个 socket 地址绑定,成功返回 0,失败返回-1

sockfd 是网络套接字描述符,即socket()的返回值。

addr 是地址结构,是一个结构体。

addrlen 是 socket 地址的长度。

 

专用 socket 地址结构

TCP/IP 协议族有 sockaddr_in 和 sockaddr_in6 两个专用 socket 地址结构体,它们分

别用于 IPV4 和 IPV6:

sin_family: 地址族 AF_INET

sin_port: 端口号,需要用网络字节序表示,在这里我们要注意大小端问题,在不同的操作系统上,大小端是不一样的,为了方便我们的服务器端,我们可以使用htons()函数将所有端口的字节序统一好。

sin_addr: IPV4 地址结构:s_addr 以网络字节序表示 IPV4 地址

 

struct sockaddr_in

{

sa_family_t sin_family;

u_int16_t sin_port;

struct in_addr sin_addr;

};

1.9.3 监听socket

socket被命名后,还不能马上接受客户连接,我们需要使用如下系统调用创建一个监听队列以存放待处理的客户连接。

int listen(int sockfd, int backlog);

listen()创建一个监听队列以存储待处理的客户连接,成功返回 0,失败返回-1

sockfd 是被监听的 socket 套接字

backlog 表示处于完全连接状态的 socket 的上限,典型值为5

1.9.4 接受连接

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept()从 listen 监听队列中接收一个连接,成功返回一个新的连接 socket,

该 socket 唯一地标识了被接收的这个连接,失败返回-1

sockfd 是执行过 listen 系统调用的监听 socket

addr 参数用来获取被接受连接的远端 socket 地址,一般是客户端

addrlen 指定该 socket 地址的长度

注意:accept返回值为0表明我们的客户端结束标准输入,这时我们接受到的值就为0.

1.9.5 TCP数据读写

ssize_t recv(int sockfd, void *buff, size_t len, int flags);

ssize_t send(int sockfd, const void *buff, size_t len, int flags);

recv()读取 sockfd 上的数据,buff 和 len 参数分别指定读缓冲区的位置和大小

send()往 socket 上写入数据,buff 和 len 参数分别指定写缓冲区的位置和数据长度

flags 参数为数据收发提供了额外的控制,一般为0

1.9.6 关闭连接

关闭该连接对应的socket

int close(int sockfd);

close()关闭一个连接,实际上就是关闭该连接对应的 socket

1.10 客户端编写

1.10.1 创建套接字 socket()

与服务器端一样

1.10.2 发起连接

int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen);

connect()客户端需要通过此系统调用来主动与服务器建立连接,

成功返回 0,失败返回-1

sockfd 参数是由 socket()返回的一个 socket。

serv_addr 是服务器监听的 socket 地址

addrlen 则指定这个地址的长度

1.10.3 发送数据 send()

与服务器端一样

1.10.4 接受数据 recv()

与服务器端一样

1.10.5 结束连接 close()

与服务器端一样

综上所述,TCP协议的基本流程就是如下图所示

a85df326e56d4441ad61cdaf8c530707.png

1.11 编写代码

1.0版本 只能进行一次通信

1.11.1 编写服务端(1.0)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){exit(1);}struct sockaddr_in saddr,caddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//当前主机的ip地址,通过ifconfig获取int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("bind err\n");exit(0);}res=listen(sockfd,5);if(res<0){printf("listen err\n");exit(0);}while(1){int len=sizeof(caddr);int c=accept(sockfd,(struct sockaddr*)&caddr,&len);if(c<0){continue;}printf("accept c=%d\n",c);char buff[128]={0};int n=recv(c,buff,strlen(buff)-1,0);printf("n=%d\n",n);printf("buff=%s\n",buff);send(c,"ok",2,0);close(c);}
​
}

 

1.11.2 编写客户端(1.0)

 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){printf("socket err\n");exit(1);}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//注意服务器和客户端必须要连接在同一个网络下int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("connect err\n");exit(0);}   printf("input:\n");char buff[128]={0};fgets(buff,128,stdin);send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("recv:%s\n",buff);close(sockfd);
}

结果

1c941607ee2844389422b3b12fd8265a.png

服务器端可以accept到客户端发送来的数据

客户端可以将服务器端接收到数据后返回的信息接受

二、 TCP 协议特点

2.1 连接的建立与断开

TCP 协议提供的是:面向连接、可靠的、字节流服务。

使用 TCP 协议通信的双发必须先建立连接,然后才能开始数据的读写。双方都必须为该连接分配必要的内核资源,以管理连接的状态和连接上数据的传输。TCP 连接是全双工的,双方的数据可以通过一个连接进行读写。完成数据交换之后,通信双方都必须断开连接以释放系统资源。

TCP 的服务器端和客户端编程流程如下:

ece62a83d15e478392d5fa41ffa79c95.png

socket()方法是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。

bind()方法是用来指定套接字使用的 IP 地址和端口。

listen()方法是用来创建监听队列。监听队列有两种,一个是存放未完成三次握手的连接,一种是存放已完成三次握手的连接。listen()第二个参数就是指定已完成三次握手队列的长度。

accept()处理存放在 listen 创建的已完成三次握手的队列中的连接。每处理一个连接,则accept()返回该连接对应的套接字描述符。如果该队列为空,则 accept 阻塞。

connect()方法一般由客户端程序执行,需要指定连接的服务器端的 IP 地址和端口。该方法执行后,会进行三次握手, 建立连接。

send()方法用来向 TCP 连接的对端发送数据。send()执行成功,只能说明将数据成功写入到发送端的发送缓冲区中,并不能说明数据已经发送到了对端。send()的返回值为实际写入到发送缓冲区中的数据长度。

recv()方法用来接收 TCP 连接的对端发送来的数据。recv()从本端的接收缓冲区中读取数据,如果接收缓冲区中没有数据,则 recv()方法会阻塞。返回值是实际读到的字节数,如果recv()返回值为 0, 说明对方已经关闭了 TCP 连接。

close()方法用来关闭 TCP 连接。此时,会进行四次挥手

2.2 TCP 固定报头

TCP报头267366871c0042688e2ac9d85de44b62.png

ACK:表示确认号是否有效,我们称携带ACK标志的TCP的报文段为确认报文段。

SYN:表示请求建立一个连接,我们称携带SYN标志的TCP的报文段为同步报文段。

FIN:表示通知对方本端要关闭连接了,我们称携带FIN标志的TCP的报文段为结束报文段。

seq(Sequence Number):表示这个tcp包的序列号。

2.3 三次握手

(1)第一次握手,主机A向主机B发出请求数据包:“我想给你发数据,可以吗?”这是第一次对话。

(2)第二次握手,主机B向主机A发送同意连接,并要求同步的数据包(同步就是两台主机协调工作,一台在发送,一台在接收):“可以,你什么时候发?”这是第二次对话。

(3)第三次握手,主机A再发出一个数据包确认主机B的要求同步:“我现在就发,你接收吧!”这是第三次对话。

握手发生在connet()的操作下。883dd0247fb7444aa0b4797a1be6d292.png

主机A向主机B发送请求数据包,这是第一次对话,S表示SYN,即请求建立一个连接

主机B向主机A 发送同意连接(回应 ACK,确认该序列号有效),并要求同步数据包(SYN),所以有seq和ACK

主机A向主机B 发送一个数据包确认主机B的要求同步。ACK表示确认该序列号有效7578544f50ef49798bae57b50ce1ec2b.png

 

2.4 中间传输信息9b426271e3424137a81429b0b636831e.png

A主机给B主机发送了一个长度为3的字符串

B主机给A主机发送了一个长度为2的字符串,表明他收到该信息

A主机给B主机发送了确认收到的,即ack

 

2.5 四次挥手

断开连接

四次挥手:

9ce7bdccd8a840648701e439df57307b.png

d0f98f5da01f455e930bda70a39a389e.png

三次挥手:客户端和服务器端关闭相差不大,这个时候就只有三次即中间的ACK和FIN合并在一起。08ce417ae18e4b19b22d70e337147f9f.png

2.6 服务器端 (2.0)

服务器端和客户端可以进行多次交互,直到客户端结束连接

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1){exit(1);}struct sockaddr_in saddr, caddr;memset(&saddr, 0, sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int res = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (res == -1){printf("bind err\n");exit(0);}res = listen(sockfd, 5);if (res < 0){printf("listen err\n");exit(0);}while (1){int len = sizeof(caddr);int c = accept(sockfd, (struct sockaddr *)&caddr, &len);if (c < 0){continue;}while (1){printf("accept c=%d\n", c);char buff[128] = {0};int n = recv(c, buff, 127, 0);if (n <= 0)//recv返回值n==0表明客户端已经关闭连接了。{break;}printf("n=%d\n", n);printf("buff=%s\n", buff);send(c, "ok", 2, 0);}sleep(2);close(c);}
}

如果将recv接受数据能力改成每次收取1个,即int n = recv(c, buff, 1, 0);

2.6.1 缓冲区

12b81e0ab3954c48b402b3830ca7fbe4.png

如果recv能力改成1,那么每次只能从缓冲区中读取一个字符,直到接收缓冲区中没有数据,然后才会阻塞

8f8c19d512be4f79bee0a21761fa66f7.png

2.6.2 查看缓冲区

netstat -natp

Recv-Q :接收缓冲区

Send-Q:发送缓冲区

ESTABLLTSHED:已完成三次握手

bbb6ed73f93147e38bfac4c645da63fe.png

此时有2个ok在接收缓冲区,在下一次输出

2.6.3 流失服务特点

5c135299caf541398816c87e68024dd7.png不管中间如何分割,但是最后从发送端到接收端得到的结果是一致的

可靠性:

1:应答确认,超时重传机制

2:去重(报文有序号,有相同序号会丢失重复的),乱序重排(报文有序号)

2.6.4 粘包

粘包发生在发送或接收缓冲区中;应用程序从缓冲区中取数据是整个缓冲区中有多少取多少;那么就有可能第一个数据的尾部和第二个数据的头部同时存在缓冲区,而TCP是流式的,数据无边界,这时发生粘包。

 

4c086824587fd897a5954679fde3e9c0.jpeg

a1a9887df7ca442fb50eb9245dd34c14.png

客户端发了三次数据,服务器端一次接受完,客户端在Recv()处等待服务器回应收到,处于阻塞状态,而服务器端处于第二次recv()所以阻塞,这时这个粘包就会出现问题。解决方法就是在头部加上标识信息。

2.6.5 流量控制

TCP 协议是利用滑动窗口实现流量控制的。一般来说,我们总是希望数据传输得更快一些,不会一次只发一个字节。但是如果发送方把数据发得过快,接受方就可能来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来的及接收。

在 TCP 的报头中有一个字段叫做接收通告窗口,这个字段由接收端填充,是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。所以发送端就会有一个发送窗口,这个发送窗口的大小是由接收端填充的接收通告窗口的大小决定的,并且窗口的位置会随着发送端数据的发送和接收到接收端对数据的确认而不断的向右滑动,将之称为滑动窗口。发送方的滑动窗口示意图如下:

f88505d487d84246a6c463a79fc611fc.png

2.7 客户端 (2.0)

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int main()
{int sockfd=socket(AF_INET,SOCK_STREAM,0);if(sockfd==-1){printf("socket err\n");exit(1);}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr= inet_addr("127.0.0.1");int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("connect err\n");exit(0);}   while(1){printf("input:\n");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}send(sockfd,buff,strlen(buff)-1,0);memset(buff,0,128);recv(sockfd,buff,127,0);printf("recv:%s\n",buff);}close(sockfd);
}

02f2e5d6d4df4c098d1c90696ba7cc92.png

2.8 多线程,多进程并发

之前的代码存在一个问题:当一个客户端与服务器建立连接以后,服务器端 accept()返回,进而准备循环接收客户端发过来的数据。如果客户端暂时没发数据,服务端会在第 40 行的 recv()阻塞。此时,其他客户端向服务器发起连接后,由于服务器阻塞了,无法执行 accept()接受连接,也就是其他客户端发送的数据,服务器无法读取。服务器也就无法并发同时处理多个客户端。

目的:一个服务器能够同时收到多个客户端的数据

多线程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
​
void * fun(void* arg)
{int *p = (int*)arg;int c = *p;free(p);while( 1 ){char buff[128] = {0};int n = recv(c,buff,1,0);if ( n <= 0)// recv返回值n == 0 说明客户但关闭连接了{break;}
​printf("recv=%s\n",buff);send(c,"ok",2,0);}
​close(c);//关闭连接  挥手printf("client close\n");
}
int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);//套接字 文件描述符if ( sockfd == -1 ){exit(1);}
​struct sockaddr_in saddr,caddr;//套接字地址 ip port memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("192.168.84.248");
​int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if ( res == -1 ){printf("bind err\n");exit(1);}
​res = listen(sockfd,5);if ( res == -1 ){exit(1);}
​while( 1 ){int c = accept(sockfd,NULL,NULL);if ( c < 0 ){continue;}
​printf("accept c=%d\n",c);
​pthread_t id;int * p = (int*)malloc(sizeof(c));*p = c;pthread_create(&id,NULL,fun,(void*)p);}
​
}

多进程

使用fork(),父子进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
​
​
void fun(int c)
{while( 1 ){char buff[128] = {0};int n = recv(c,buff,127,0);if ( n <= 0)// recv返回值n == 0 说明客户但关闭连接了{break;}
​printf("recv=%s\n",buff);send(c,"ok",2,0);}
​close(c);//关闭连接  挥手printf("client close\n");
}
int main()
{int sockfd = socket(AF_INET,SOCK_STREAM,0);//套接字 文件描述符if ( sockfd == -1 ){exit(1);}
​struct sockaddr_in saddr,caddr;//套接字地址 ip port memset(&saddr,0,sizeof(saddr));saddr.sin_family = AF_INET;saddr.sin_port = htons(6000);saddr.sin_addr.s_addr = inet_addr("192.168.84.248");
​int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if ( res == -1 ){printf("bind err\n");exit(1);}
​res = listen(sockfd,5);if ( res == -1 ){exit(1);}
​while( 1 ){int c = accept(sockfd,NULL,NULL);if ( c < 0 ){continue;}
​printf("accept c=%d\n",c);
​pid_t pid=fork();if(pid<0){printf("fork err\n");close(c);continue;}if(pid==0){close(sockfd);fun(c);exit(0);}}
​
}

2.9 抓包

使用命令

netstat -natp

三次握手时:

7d55ca0c038c498a9d82fceb26144c5b.png

四次挥手时:

ecc2b62f900c45689b18b290107a6e0e.png因为时间原因没有截到 TIME_WAIT

e0b5dd44caa7469d83c274631ccdbe7f.png三、 UDP 协议特点

UDP 数据报服务特点:发送端应用程序每执行一次写操作,UDP 模块就将其封装成一个 UDP 数据报发送。接收端必须及时针对每一个 UDP 数据报执行读操作,否则就会丢包。并且,如果用户没有指定足够的应用程序缓冲区来读取 UDP 数据,则 UDP 数据将被截断。

50008ee78b594a68a89fc710deb95b23.png与TCP不同的是UDP接收到的数据报会直接发给接收端,不会存在缓存区。

(1)UDP是一个非连接的协议,传输数据之前,源端和终端不建立连接,当它想传送时,就简单地去抓取来自应用程序的数据,并尽可能快地把它扔到网络上。

在发送端,UDP传送数据的速度仅受应用程序生成数据的速度、计算机的能力和传输带宽的限制;在接收端,UDP把每个消息段放在队列中,应用程序每次从队列中读一个消息段。

(2)由于传输数据不建立连接,因此不需要维护连接状态,包括收发状态等。一台服务机可同时向多个客户机传输相同的消息。

(3)UDP信息包的包头很短,只有8字节,相对于TCP的20字节包头信息,UDP的包头开销很小。

(4)吞吐量不受拥挤控制算法的调节,只受应用软件生成数据的速率、传输带宽、源端和终端主机性能的限制。

(5)UDP会尽最大努力去传输和接受数据且没有限制,但并不保证可靠的数据交付,主机也不需要维持复杂的链接状态表(里面有许多参数)。

(6)UDP是面向报文的。发送方的UDP对应用程序传过来的报文,在添加包头后就向下交付给IP层。既不拆分,也不合并,而只是保留这些报文的边界,因此,应用程序需要自己限制合适的报文大小,以免报文太大导致丢失率高。

3.1 API

UDP 数据读写:

recvfrom()读取 sockfd 上的数据,buff 和 len 参数分别指定读缓冲区的位置和大

src_addr 记录发送端的 socket 地址

addrlen 指定该地址的长度

sendto()往 socket 上写入数据,buff 和 len 参数分别指定写缓冲区的位置和数据长

dest_addr 指定接收数据端的 socket 地址

addrlen 指定该地址的长度

ssize_t recvfrom(int sockfd, void buff, size_t len, int flags,struct sockaddr src_addr, socklen_t *addrlen);

ssize_t sendto(int sockfd, void buff, size_t len, int flags,struct sockaddr dest_addr, socklen_t addrlen);

3.2 服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{int sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd==-1){printf("socket err\n");exit(1);}struct sockaddr_in saddr,caddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(res==-1){printf("bind err\n");exit(0);}while(1){int len=sizeof(caddr);char buff[128]={0};recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);printf("buff=%s\n",buff);sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof(caddr));
​
​}close(sockfd);exit(0);
​
}

3.3 客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
int main()
{int sockfd=socket(AF_INET,SOCK_DGRAM,0);if(sockfd==-1){printf("socket err\n");exit(1);}struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");while(1){print("input:\n");char buff[128]={0};fgets(buff,128,stdin);if(strncmp(buff,"end",3)==0){break;}sendto(sockfd,buff,strlen(buff)-1,0,(struct sockaddr*)&saddr,sizeof(saddr));memset(buff,0,128);recvfrom(sockfd,buff,127,0,NULL,NULL);printf("buff=%s\n",buff);
​}close(sockfd);exit(0);
​
​
}

四、TCP和UDP区别

TCP安全、可靠、面向连接,但是传输速度慢。UDP不安全、不可靠、面向非连接,但是传输速度快。

TCP更适合与文件传输,如果出现错误,会重发。

UDP更适合于实时性,如视频通话,在网卡的情况下,卡住,网好后,能够将实时的画面更新,而不会把之前的再发一遍。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/90449.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MATLAB源码-第38期】基于OFDM的块状导频和梳状导频误码率性能对比,不同信道估计方法以及不同调制方式对比。

1、算法描述 块状导频和梳状导频都是用于无线通信系统中信道估计的方法。 块状导频&#xff1a; 定义&#xff1a; 在频域上&#xff0c;块状导频是连续放置的一组导频符号。这意味着所有的导频符号都集中在一个短的时间段内发送。 优点&#xff1a; 对于时间选择性信道&#…

Python 打印素数

"""打印素数介绍&#xff1a;素数是指只有两个正因数&#xff08;1和它本身&#xff09;的自然数&#xff0c;而且必须大于1。例如&#xff1a;2、3、5、7、11、13、17、19、23、29等等都是素数。小于2的数不是素数&#xff0c;因为它没有两个正因数。例如&…

基于JAVA+SpringBoot的新闻发布平台

✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xff1a; 随着科技的飞速发展和…

IO流 之 缓冲流(字节缓冲流和字符缓冲流)

缓冲流对原始流进行了包装&#xff0c;以提高原始流读写数据的性能。 字节缓冲流&#xff08;BufferedInputStream和BufferedOutputStream&#xff09; 字节缓冲流在内存中提供了一个默认为8kb的区域&#xff0c;用于缓冲&#xff0c;当流开始时&#xff0c;先读取一个8kb的内…

skywalking入门

参考&#xff1a; https://www.jianshu.com/p/ffa7ddcda4ab 参考&#xff1a; https://developer.aliyun.com/article/1201085 skywalking&#xff08;APM&#xff09; 调用链路分析以及应用监控分析工具 Skywalking主要由三大部分组成&#xff1a;agent、collector、webapp-…

十六.镜头知识之工业镜头的质量判断因素

十六.镜头知识之工业镜头的质量判断因素 文章目录 十六.镜头知识之工业镜头的质量判断因素1.分辨率(Resolution)2.明锐度(Acutance)3.景深(DOF)&#xff1a;4. 最大相对孔径与光圈系数5.工业镜头各参数间的相互影响关系5.1.焦距大小的影响情况5.2.光圈大小的影响情况5.3.像场中…

CISSP学习笔记:人员安全和风险管理概念

第二章 人员安全和风险管理概念 2.1 促进人员安全策略 职责分离: 把关键的、重要的和敏感工作任务分配给若干不同的管理员或高级执行者&#xff0c;防止共谋工作职责:最小特权原则岗位轮换:提供知识冗余&#xff0c;减少伪造、数据更改、偷窃、阴谋破坏和信息滥用的风险&…

快速幂矩阵-python

看了大神讲解&#xff0c;理论在这里&#xff1a;快速幂算法&#xff08;全网最详细地带你从零开始一步一步优化&#xff09;-CSDN博客 例题&#xff1a;求整数 base 的 整数 power 次方&#xff0c;对整数 num_mod 取幂。 python 代码如下&#xff1a; import timedef norm…

LabVIEW在运行时调整表控件列宽

LabVIEW在运行时调整表控件列宽 如何在LabIEW中运行时调整表控件的列宽大小&#xff1f; 在VI运行时&#xff0c;有两种不同的方法可以更改表中列的宽度。首先&#xff0c;可以使用鼠标手动更改它们;其次&#xff0c;可以从框图中以编程方式更改它们。 手动更改列宽 只有在…

IPsec_SSL VPN身份鉴别过程简要

一、IPsec VPN身份鉴别&#xff08;参考国密标准《GMT 0022-2014 IPsec VPN技术规范》&#xff09; IKE第一阶段&#xff08;主模式&#xff09; “消息2”由响应方发出&#xff0c;消息中具体包含一个SA载荷&#xff08;确认所接受的SA提议&#xff09;、响应方的签名证书和…

基于AI图像识别的智能缺陷检测系统,在钢铁行业的应用-技术方案

目录 概述 废钢智能检判方案简介 废钢智能检判系统优势及价值 废钢人工检判过程 废钢等级检判标准 废钢检判结果 智能检判方案-废钢智能检判算法 算法一&#xff1a;废钢等级识别算法 算法二&#xff1a;不合格料的位置识别算法 算法三&#xff1a;不合格料的类型识别…

【再识C进阶3(下)】详细地认识字符分类函数,字符转换函数和内存函数

前言 &#x1f493;作者简介&#xff1a; 加油&#xff0c;旭杏&#xff0c;目前大二&#xff0c;正在学习C&#xff0c;数据结构等&#x1f440; &#x1f493;作者主页&#xff1a;加油&#xff0c;旭杏的主页&#x1f440; ⏩本文收录在&#xff1a;再识C进阶的专栏&#x1…

全网最全面最精华的设计模式讲解,从程序员转变为工程师的第一步

前言 现代社会&#xff0c;技术日新月异&#xff0c;要想跟上技术的更新就必须不断学习&#xff0c;而学习技术最有效方式就是阅读优秀的源码&#xff0c;而优秀的源码都不是简单的逻辑堆积&#xff0c;而是有很灵活的设计模式应用其中&#xff0c;如果我们不懂设计模式&#…

idea2023根据表自动生成+springboot跑起来

idea安装插件 idea中显示数据库连接 就可以看到如下界面 选中你想生成的表&#xff0c;右键如下操作 如上就有了所有需要的后端代码 生成后&#xff0c;要查看一下mapper.xml中的文件是否 正确&#xff0c;若有误请先去修改&#xff0c;例如我的版本下生成了xml文件中缺乏…

基于SpringBoot的银行账目账户管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09;有保障的售后福利 代码参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作…

51单片机用IIc控制OLED显示数组内容

为了能够看到51单片机接收到的串口数据&#xff0c;我选择了用oled显示收到的数据&#xff0c;特此花重金买了一块oled屏128X64的屏幕大概10来块钱吧&#xff01;首先要达成的小目标就是能够显示数组的内容&#xff0c;建立一个字符数组&#xff0c;用来接收串口收到的数据&…

基于SpringBoot的古典舞在线交流平台的设计与实现

目录 前言 一、技术栈 二、系统功能介绍 系统主界面 用户注册界面 论坛交流界面 课程详情界面 购物车界面 我的订单界面 管理员登录界面 会员用户管理界面 服饰管理界面 课程管理界面 三、核心代码 1、登录模块 2、文件上传模块 3、代码封装 前言 随着互联网技术…

Python函数:chr()和ord()

两个函数是基于Unicode编码表进行进行字符与字码之间的转换。 chr()函数是通过字码转换成字符: 如图,坐标(1,4e10)丑 使用chr需要线将坐标相加得到&#xff1a;4e11 chr默认传入10进制的字码. 如图是各进制的字码。 也可以传入其他进制&#xff0c;不过需要在前面传入的参数最前…

Docker学习_镜像和容器篇

简介 Docker是一种容器化的技术&#xff0c;可以实现在一台宿主机电脑上运行多个不同的容器&#xff0c;每个容器之间都相互独立&#xff0c;具有完整的一套文件&#xff0c;网络和端口。 可以将其理解为一种虚拟机技术&#xff0c;只不过和VMware等虚拟化技术不同&#xff0…

LM小型可编程控制器软件(基于CoDeSys)笔记三十一:保持变量和非保持变量

所谓变量&#xff0c;就是用字母、数字和下划线组成的一个标识符。 按照数据类型的不同&#xff0c;变量可以分为标准类型和用户自定义类型。其中标准类型包括布尔型 &#xff08; BOOL &#xff09;、整型&#xff08; INT &#xff09;、实型&#xff08; REAL &#xff09…