LINUX-网络编程-TCP-UDP

1.目的:

不同主机,进程间通信。

2.解决的问题

1)主机与主机之间物理层面必须互相联通。

2)进程与进程在软件层面必须互通。

IP地址:计算机的软件地址,用来标识计算机设备

MAC地址:计算机的硬件地址(固定)

网络端口号:标记同一主机的不同网络进程
在局域网内的通信,数据发送到交换机,交换机会直接发给其他pc,不是局域网内的通信,交换机识别后会发送给路由器,此时如果是UDP协议,路由器不会提前规划好路径,会自行在路由器间进行传递,再回到对方交换机,最后回到对方主机。如果是TCP协议,路由器则会提前规划好路线,数据按照规划好的路线传送数据。

3.网络协议

网络通信标准:

OSI七层模型:开放系统互联模型(open system interconnect)

理论模型:

不同体系结构设备间,网络通信标准。

应用层:要传输的数据信息,如文件传输,电子邮件等

表示层:数据加密,解密操作,压缩,解压缩

会话层:建立数据传输通道--->会话

传输层:传输的方式UDP TCP 端口号

网络层:实现数据路由,路径规划,路由器ip

数据链路层:封装成帧,点对点通信(局域网内通信),差错检测,交换机 ARP

物理层:定义物理设备标准、电气特性,比如网线,光纤等传输介质 比特流 bit 0 1
TCP/IP模型:应用类型

五层:

应用层:

HTTP:超文本传输协议

HTTPS:超文本传输协议(SSL加密算法)

FTP:文件传输协议(TCP)

TFTP:简单文件传输协议(UDP)

MQTT:消息队列遥测传输(物联网协议)

DNS:域名解析服务(www.baidu.com--->IP地址)

传输层:

TCP:传输控制协议(先确定好传输通道)

UDP:用户数据报协议(没有预先确定好传输通道,造成数据乱序,丢失)

网络层:

IP协议:

IPV4:

IPV6:

数据链路层:

ARP:地址解析协议

物理层:
四层模型:

应用层:

传输层:

网络层:

网络接口层:

4. IP协议

网络层:

IP协议

192.168.1.128

IPv4 32位

IPv6 128位

192.168.1.140 (用户表示形式) 点分十进制

11000000 10101000 00000000 01000011 (计算机存储形式) 32bits

IP地址 = 网络位 + 主机位

192.168.0.121/24

24:网络位的位数

网络位:该IP地址位于哪个网段(局域网)内

主机位:这个网段(局域网)第几台主机

子网掩码:

如:255.255.255.0

11111111.11111111.11111111.00000000

用来区分IP地址的网络位和主机位,搭配IP地址使用。

子网掩码是1的部分对应IP地址的网络位

子网掩码是0的部分对应IP地址的主机位

192.168.1.0

网段号:

IP地址网络位不变,主机位全为0,则为该IP地址的网段号

192.168.1.3

255.255.0.0

192.168.0.0

位于

192.168.1.0 网段内(网段内的IP能直接通信)

192.168.1.3

255.0.0.0

广播号:

192.168.1.255

IP地址网络位不变,主机位全为1,则为该IP地址的广播号

192.168.1.3

255.255.255.0

广播号:

192.168.1.255(向广播号发送信息,所有局域网内IP都能收到此信息)

feiQ VNC

192.168.1.255

网关地址:

192.168.1.1

IP地址的划分:

(1)A类地址:

范围:1.0.0.0 - 126.255.255.255

子网掩码:255.0.0.0 126*2^24

用于管理大规模网络

私有IP地址:10.0.0.0 - 10.255.255.255

127.0.0.0 回环地址

(2)B类地址:

范围:128.0.0.0 - 191.255.255.255

子网掩码:255.255.0.0 (192-128)*2^16

管理大中规模网络

私有IP地址:172.16.0.0 - 172.31.255.255

(3)C类地址:

范围:192.0.0.0 - 223.255.255.255

子网掩码:255.255.255.0 (224-192)*2^8

管理中小规模网络

私有IP地址:192.168.0.0 - 192.168.255.255

(4)D类地址:

224.0.0.0 - 239.255.255.255

组播和广播使用

(5)E类地址:

240.0.0.0 - 255.255.255.254

用来进行实验

公有IP:由电信公司直接分配,并需要付费的IP地址, 可以直接访问internet

私有IP:不能直接访问internet的ip地址

节省ip地址

5.网络端口号

端口号:16位整形数据(unsigned short)0 - 65535

端口号功能:标记同一主机上的不同网络进程

分类:

1)任何TCP/IP实现所提供的服务都有1-1023之间的端口号

http:80

FTP:20/21

TFPT:69

HTTPS:443

2)端口号从1024-49151是被注册的端口号,被IANA制定为特殊服务使用。

MQTT:1883/8883

3)从49152-65535是动态的或私有端口号。

6.网络配置

  1. ping ip地址/域名

查看当前主机和IP/域名所对应的这台主机网络是否联通

ping www.baidu.com

2.ifconfig

在LINUX下查看ip

ipconfig(在windows下查看ip)

3.网络配置

1)虚拟机->设置->网络适配器-->桥接模式

2)编辑->虚拟网络编辑器->更改设置->VMnet0-->桥接至--->当前点电脑(pc)正在上网的网卡-->应用-->确认

3)修改网络配置文件

sudo vim /etc/network/interfaces

添加以下代码

cs 复制代码
auto lo
iface lo inet loopback
auto ens33
iface ens33 inet dhcp

4)重启网络服务

sudo /etc/init.d/networking restart

5)测试

ping www.baidu.com

7.网络协议---UDP

UDP:传输层

用户数据报协议(User Datagram Protocol)

1)网络变成模型

B/S模型:browser/server(浏览器、服务器)

1.客户端是通用的客户端(浏览器)

2.一般只做服务器开发

3.客户端要加载的数据都请求于服务器

C/S模型: client/server (客服端、服务器)

1.客户端是一个专用的客服端

2.服务器和客服端都需要开发

3.客户端可保存资源,本地加载,无需所有数据都请求服务器

2)UDP编程流程

套接字(s):文件描述符

网络通信时,应用层可操作的端口。

下面是客户端和服务端的创建流程

#include <sys/type.h>

#include <sys/socket.h>

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

功能:创建通信的套接字

参数:

domain:网络层使用什么协议族

AF_INET: IPv4

AF_INET6:IPv6

type:规定传输层的协议

SOCK_DGRAM:UDP协议

SOCK_STREAM:TCP协议

SOCK_RAW:原始套接字

protocol:0按照默认协议方式创建

返回值:

成功:套接字

失败:-1

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

功能:向网络套接字发送数据

参数:

sockfd:套接字

buf:要发送的数据的首地址

len:要发送的字节数

flag:0:按照默认方式发送

dest_addr:接受放地址信息(IP+端口号)

addrlen:接收方地址大小

返回值:

成功:实际发送的字节数

失败:-1

命令:man 7 ip

cs 复制代码
struct sockaddr_in
{
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr 
{
    uint32_t       s_addr;     /* address in network byte order */
};

网络字节序:大端 network

主机zijiexu: 小端 host

以下是端口的大小端转换函数

uint32_t htonl(uint32_t hostlong); 主机转网络

uint16_t htons(uint16_t hostshort); 主机转网络

uint32_t ntohl(uint32_t netlong); 网络转主机

uint16_t ntohs(uint16_t netshort); 网络转主机

以下是地址的大小端转换形式

in_addr_t inet_addr(const char *cp);

功能:

将字符串IP地址转换成二进制IP地址形式

char *inet_ntoa(struct in_addr in);

功能:

将二进制ip转换成字符串
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

功能:绑定自己的ip地址和端口号

参数:

sockfd:套接字

addr:需要绑定的地址

addrlen:地址大小

返回值:

成功:0

失败:-1
ssize_t recvfrom(int sockfd, void*buf,size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);

功能:从套接字上接收数据

参数:

sockfd:套接字

buf:存放接受数据的内存首地址

len:希望接受的字节数

flags:0:按照默认方式接受(阻塞)

src_addr:发送放的地址信息

addrlen:发送发地址的指针

功能:

成功:实际接受到的字节数

失败:-1

UDP传输有发送缓冲区和接受缓冲区,当发送的数据过快,接受缓冲区的数据已经满了,就会发生丢包

3)UDP 特点

1.面向数据包

2.无需建立链接

3.尽最大努力交付,不安全,不可靠,(数据丢包、乱序)

4.可实现一对一、一对多的传输

5.机制简单,资源开销小,数据实时性高(VNC、直播)

如何避免UDP丢包:

1.发送方以比较慢对的速度发送数据,让接收方有足够的时间处理数据

2.模仿tTCP的机制:应答机制

4)抓包工具

wireshark

网络抓包:抓取设备网卡的网络数据,从而调试和分析网络程序。

1、sudo wireshark(启动)

2.选取要抓取的网卡--->any

3.选择一个过滤条件

4.开始抓取

5.进行一次网络通信

5)UDP报文头部

UDP头部:总共八字节

源端口号:发送方网络进程端口号

目标端口号:接收方网络进程端口号

长度:UDP发送的报文的整体长度(UDP头部+UDP正文)

校验和:数据差错检验

6.代码练习

客户端从终端不断读取数据,通过UDP,发送给服务端,服务端输出:

cs 复制代码
/*************client.c**********/
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char const *argv[])
{
    //socker 创建套接字文件
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);// 参数含义socket(协议族, 套接字类型, 协议)
    if(sockfd < 0)
    {
        perror("socket error");
        return -1;
    }

    // 定义的接收方(服务端)的地址变量
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);    //接收方的端口号
    seraddr.sin_addr.s_addr = inet_addr("192.168.0.182");   //接收方的IP地址

    // 发送数据 sendto(socket, buf, len, flags, toaddr, addrlen)
    while(1)
    {
        char buf[1024] = {0};
        fgets(buf, sizeof(buf), stdin);
        ssize_t cnt = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
        if (cnt < 0)
        {
            perror("sendto error");
            return -1;
        }
        printf("cnt = %ld\n", cnt);
    }

    close(sockfd);
    
    
    return 0;
}
cs 复制代码
//read.c

#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <netdb.h>

int main(int argc, char const *argv[])
{
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("socket");
        return -1;
    }
    // 定义对方地址结构体
    struct sockaddr_in cliaddr;
    int cliaddrlen = sizeof(cliaddr);
    // 定义自己地址结构体
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.0.182");


    // 绑定套接字
    int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret < 0)
    {
        perror("bind");
        return -1;
    }

    // 接收数据
    while(1)
    {
        char buf[1024] = {0};
        //recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&cliaddr, &len);
        ssize_t cnt = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&cliaddr, &cliaddrlen);
        if (cnt < 0)
        {
            perror("recvfrom");
            return -1;
        }

        printf("地址%s,内容:%s\n", inet_ntoa(cliaddr.sin_addr), buf);
    }

    close(sockfd);
    return 0;
}

使用UDP实现全双工聊天

cs 复制代码
//头文件
#ifndef __HEAD_H__
#define __HEAD_H__


#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <pthread.h>

#endif
cs 复制代码
//客户端
#include "head.h"

#define SER_PORT 50000
#define SER_IP   "192.168.0.179"


struct sockaddr_in seraddr;

int init_udp_cli()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	return sockfd;
}



void *send_msg(void *arg)
{
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		fgets(buff, sizeof(buff), stdin);
		ssize_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
		if (cnt < 0)
		{
			perror("sendto error");
			break;
		}
	}
	
	return NULL;
}

void *recv_msg(void *arg)
{
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		memset(buff, 0, sizeof(buff));
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		if (cnt < 0)
		{
			perror("recvfrom error");
			break;
		}
		printf("B-->A : %s\n", buff);
	}

	return NULL;
}


int main(int argc, const char *argv[])
{
	pthread_t tid[2];

	int sockfd = init_udp_cli();
	if (sockfd < 0)
	{
		return -1;
	}
	
	sendto(sockfd, "hello", 5, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));


	pthread_create(&tid[0], NULL, send_msg, &sockfd);
	pthread_create(&tid[1], NULL, recv_msg, &sockfd);

	
	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	
	close(sockfd);

	return 0;
}
cs 复制代码
//服务端
#include "head.h"


#define SER_PORT 50000
#define SER_IP "192.168.0.179"


struct sockaddr_in cliaddr;
socklen_t clilen;

int init_udp_ser()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	
	return sockfd;
}


void *send_msg(void *arg)
{
	
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		fgets(buff, sizeof(buff), stdin);
		size_t cnt = sendto(sockfd, buff, strlen(buff), 0, (struct sockaddr *)&cliaddr, clilen);
		if (cnt < 0)
		{
			perror("sendto error");
			break;
		}
	}
	return NULL;
}

void *recv_msg(void *arg)
{
	char buff[1024] = {0};
	int sockfd = *((int *)arg);

	while (1)
	{
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		if (cnt < 0)
		{
			perror("recvfrom error");
			break;
		}
		printf("A--->B: %s\n", buff);
	}

	return NULL;
}


int main(int argc, const char *argv[])
{
	pthread_t tid[2];
	char buff[1024] = {0};

	clilen = sizeof(cliaddr);

	int sockfd = init_udp_ser();
	if (sockfd < 0)
	{
		return -1;
	}
	
	recvfrom(sockfd, buff, sizeof(buff), 0, (struct sockaddr *)&cliaddr, &clilen);
	
	pthread_create(&tid[0], NULL, send_msg, &sockfd);
	pthread_create(&tid[1], NULL, recv_msg, &sockfd);

	pthread_join(tid[0], NULL);
	pthread_join(tid[1], NULL);
	return 0;
}

使用UDP实现图片的传输。

cs 复制代码
//客户端
#include "head.h"

#define SER_PORT 50000
#define SER_IP   "192.168.0.179"


struct sockaddr_in seraddr;

int init_udp_cli()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	return sockfd;
}


int send_file(int sockfd, const char *filename)
{
	char buff[1024] = {9};
	int fd = open(filename, O_RDONLY);
	if (fd < 0)
	{
		perror("open file error");
		return -1;
	}
	
	off_t len = lseek(fd, 0, SEEK_END);
	lseek(fd, 0, SEEK_SET);
	printf("len = %ld\n", len);

	sendto(sockfd, &len, sizeof(len), 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
	
	while (1)
	{
		size_t cnt = read(fd, buff, sizeof(buff));
		if (cnt <= 0)
		{
			break;
		}
		sendto(sockfd, buff, cnt, 0, (struct sockaddr *)&seraddr, sizeof(seraddr));
		usleep(1);
	}
	
	return 0;
}



int main(int argc, const char *argv[])
{
	if (argc < 2)
	{
		printf("Usage : ./a.out <sendfile>\n");
		return -1;
	}

	pthread_t tid[2];
	int sockfd = init_udp_cli();
	if (sockfd < 0)
	{
		return -1;
	}
	
	
	send_file(sockfd, argv[1]);
	
	close(sockfd);

	return 0;
}
cs 复制代码
//服务端
#include "head.h"


#define SER_PORT 50000
#define SER_IP "192.168.0.179"



int init_udp_ser()
{
	int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd < 0)
	{
		perror("socket error");
		return -1;
	}
	
	struct sockaddr_in seraddr;
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SER_PORT);
	seraddr.sin_addr.s_addr = inet_addr(SER_IP);

	int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	
	return sockfd;
}


int recv_file(int sockfd, const char *filename)
{
	char buff[1024] = {0};
	int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0664);
	if (fd < 0)
	{
		perror("open file error");
		return -1;
	}
	off_t len = 0;
	recvfrom(sockfd, &len, sizeof(len), 0, NULL, NULL);
	printf("len = %ld\n", len);


	while (1)
	{
		ssize_t cnt = recvfrom(sockfd, buff, sizeof(buff), 0, NULL, NULL);
		write(fd, buff, cnt);
		len -= cnt;
		if (0 == len)
		{
			break;
		}
	}

	return 0;
}


int main(int argc, const char *argv[])
{

	if (argc < 2)
	{
		printf("Usage : ./a.out <recvfile>\n");
		return -1;
	}

	int sockfd = init_udp_ser();
	if (sockfd < 0)
	{
		return -1;
	}
	
	recv_file(sockfd, argv[1]);


	close(sockfd);

	return 0;
}

8.网络协议-TCP(transmission control protocol)

TCP: 传输层

传输控制协议(流式套接字)

1)TCP特点:

(1)面向字节流

(2)有连接(通信之前必须建立连接)

(3)安全可靠的传输机制

(4)机制复杂,网络资源开销大

(5)本质只能实现一对一的通信(使用TCP并发方式可以实现一对多通信)

2)TCP 三次握手和四次挥手机制

TCP三次握手:TCP建立连接时,需要进行三次握手,为了确保收发双发通信之前都已准备就绪。

TCP四次挥手:TCP断开连接前,需要进行四次挥手操作,确保断开连接前双方都已通信结束

3)TCP的编程流程

listen()建立两个队列

#include <sys/types.h>

#include <sys/socket.h>

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

功能:请求与服务端建立连接

参数:

sockfd:套接字

addr:要连接的服务端的地址信息

addrlen:服务端地址大小

返回值:

成功:0

失败:-1

#include <sys/types.h>

#include <sys/socket.h>

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

功能:发送网络数据

参数:

sockfd:网络套接字

buf:要发送数据的额首地址

len:发送字节数

flags:0:按照默认方式

返回值:

成功:实际发送字节数

失败:-1

int listen(int sockfd, int backlog);

功能:监听建立三次握手的客户端

参数:

sockfd:监听套接字

backlog:最大允许监听的客户端的个数

返回值:

成功:0

失败:-1

int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);

功能:接受建立三次握手的客户端,并产生一个通讯套接字

参数:

socket:监听套接字

address:客户端的地址信息

address_lne: 客户端地址长的指针

返回值:

成功:通讯套接字

失败:-1

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

功能:从网络套接字上接受数据

参数:

sockfd:通讯套接字

buf:存放接受数据的首地址

len:期望收到的字节数

flag:0:默认方式接受(阻塞)

返回值:

成功:实际收到的字节数

失败:-1

对方断开链接:0

4)TCP粘包问题

**TCP粘包问题:**发送方应用层发送的多包数据,将来在接受方可能一次读到,多包数据产生了黏连。

产生原因:

(1)发送方数据过快,TCP底层可能对多包数据进行重新组帧:

(2)接收方数据处理速度过慢,导致多包数据在接受缓冲区缓存,应用层读数据时,一次将多包数据读出。

解决方法:

(1)调整发送速率

(2)发送指定大小,将来接收方也接受指定大小

结构体:

注意:

1.跨平台之间进行数据传输时,注意结构体对齐问题。

cs 复制代码
struct a
{
    char a;
    int b;
    long c;
};//32bit平台long 占四字节    64bit平台long占8个字节

(3)应用层发送的数据增加分隔符,利用分隔符解析

(4)封装自定义数据帧格式进行发送(协议),严格根据协议解析

AA len(C0) 00 00 00 F0 00 00 10 00 校验 BB(帧头、帧尾)

帧头:

帧尾:

有效数据长度:C0 (一般帧头后一个字节)

有效数据:

校验:

8位和校验

16位校验

CRC校验

5)TCP其他机制

TCP头部的标志位:

SYN:请求链接标志位

ACK:响应报文标志位

PSH:携带数据标志位,通知接收方该从缓冲区读数据

FINL:请求断开标志位

RST(reset):复位标志位

URG(urgent):紧急指针标志位

安全可靠:

(1)三次握手,三次挥手机制

(2)应答机制:TCP对于每一包数据都会给出相应的应答。发送数据时序列号表示这包数据的起始编号,响应报文中的确认号是接收方收到的最后一个字节编号加1

(3)超时重传机制:当数据发送出去等待指定时间没有收到相应,此时认为这包数据丢失,则进行重传

(4)滑动窗口数据:一段缓冲区,缓存TCP已发送未收到响应,准备发送等数据,为了重传仍然能找到数据。

提高效率:

(1)延迟应答机制:发送数据的同时可以等待应答

(2)流量控制机制:结合TCP头部的窗口大小,动态调整发送速率

(3)捎带应答机制:ACK报文可能和应用层的数据同时发送

6)TCP并发服务器构建

TCP服务端并发模型:

1.多进程

进程资源开销大,安全性高

2.多线程

线程相对与进程资源开销小,相同资源情况下,并发量比进程高

3.线程池

为了解决多线程或者多进程模型,在服务器运行过程,频繁创建销毁线程(进程)带来的时间消耗问题。

基于生产者和消费者编程模型,以及任务对列,实现的一套多线程框架。

4.IO多路复用

对多个文件描述符的读写可以复用一个进程。

在不创建新的进程和线程的前提下,使用一个进程实现对多个文件读写的同时检测

阻塞IO模式:

1.多个任务之间是同步的效果

1)select实现IO多路复用

(1)创建文件描述符集合

(2)添加关注的文件描述符集合

(3)使用select传递集合表给内核,内核开始监测事件

(4)当内核监测到事件时,应用层select将解除阻塞,并获得相关事件

(5)根据select返回的结果做不同的任务处理

int select(int nfds, fd_set *readfds, fd_set *writefds,

fd_set *exceptfds, struct timeval *timeout);

功能:传递文件描述符结合表给内核并等待获取事件结果

参数:

nfds : 关注的最大文件描述符+1

readfds:读事件的文件描述符集合

writefds:写事件的文件描述符集合

exceptfds:其他事件的文件描述符集合

timeout:设置select监测时的超时时间

NULL : 不设置超时时间(select一直阻塞等待)

返回值:

成功:返回内核监测的到达事件的个数

失败:-1

0 : 超时时间到达,但没有事件发生,则返回0

void FD_CLR(int fd, fd_set *set); 指定位置清0

int FD_ISSET(int fd, fd_set *set); 判断某个列表是否含有某个文件描述符

void FD_SET(int fd, fd_set *set); 添加某个文件描述符到列表中

void FD_ZERO(fd_set *set); 对整个集合表清零

select特点:

1.使用位图实现对文件描述符集合的保存,最多允许同时监测1024个文件描述符

2.需要应用和内核层的反复数据(文件描述符稽核表)拷贝

3.返回的集合表需要遍历寻找到达的事件

4.只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)

2)poll特点:

1.使用链表实现对文件描述符集合的保存,没有了监测的文件描述符上限限制

2.需要应用和内核层的反复数据(文件描述符稽核表)拷贝

3.返回的集合表需要遍历寻找到达的事件

4.只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式)

3)epoll

1.使用红黑树(特殊二叉树)实现文件描述符集合的存储,没有文件描述符上限限制,提高查找效率;

2.将文件描述符集合创建在内核层,避免了应用层和内核层的反复数据拷贝

3.返回的是到达的事件,不需要遍历,只需要处理事件即可

4.可工作在水平触发模式(低速模式),也可以工作 在(高速模式)

编程流程:

1.创建文件描述符结合 int epoll_create(int size);

2.添加关注的文件描述符 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

3.epoll通知内核开始进行事件监测 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)

4.epoll返回时,获取到达时间的结果

5.根据到达事件做任务处理

int epoll_creat(int size);

功能:通知内核创建文件描述符集合

参数:

size:检测的文件描述符个数

返回值:

成功:文件描述符(代表内核创建的集合)

失败:-1

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

功能:对epoll的文件描述符集合进行操作

参数:

epfd:创建的epoll集合

op:对文件描述符集合进行的操作

EPOLL_CTL_ADD:添加文件描述符到集合

EPOLL_CTL_MOD:修改集合中的文件描述符

EPOLL_CTL_DEL:删除集合中的文件描述符

fd:要操作的文件描述符

evevt:文件描述符对应的事件

返回值:

成功:0

失败:-1

cs 复制代码
typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;

struct epoll_event
{
    uint32_t events;
    epoll_data_t data;
};

 events:文件描述符的事件:
                        EPOLLIN: 读事件
                        EOPLLOUT:写事件
 data.fd : 关注的文件描述符

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

功能:通知内核开始监测文件描述符的事件

参数:

epfd:监测的文件描述符集合

events:保存返回的到达时间的结果(数组)

struct epoll_event evs[MAX_FD_CNT];

maxevents:最大事件个数

timeout:监测的超时时间

-1:不设置超时(一直阻塞)

返回值:

成功:到达事件的个数

失败:-1

数据存储结构:红黑树

7)代码练习

实现客户端可以从终端不断读数据,服务端可以不断接受客户端的数据

cs 复制代码
//客户端
#include "head.h"


int main(int argc, char const *argv[])
{
    //创建套接字 socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket error");
        return -1;
    }
    //创建对方地址
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.1.9");

    //连接服务端
    int ret1 = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret1 == -1)
    {
        perror("conenct error");
        return -1;
    }
    char buf[1024];
    while (1)
    {
        memset(buf, 0, sizeof(buf));
        fgets(buf, sizeof(buf), stdin);
        ssize_t cnt = send(sockfd, buf, strlen(buf), 0);
    }
    close(sockfd);
    
    return 0;
}
cs 复制代码
//服务端
#include "head.h"

int main(int argc, char const *argv[])
{
    //创建监听套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket error");
        return -1;
    }
    
    //创建客户端地址
    struct sockaddr_in cliaddr;
    socklen_t clilen = sizeof(cliaddr);

    //创建服务端地址
    struct sockaddr_in seraddr;
    seraddr.sin_family = AF_INET;
    seraddr.sin_port = htons(50000);
    seraddr.sin_addr.s_addr = inet_addr("192.168.1.9");

    //绑定套接字
    int ret1 = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
    if (ret1 == -1)
    {
        perror("bind error");
        return -1;
    }
    //监听套接字
    int ret2 = listen(sockfd, 10);
    if (ret2 == -1)
    {
        perror("listen error");
        return -1;
    }

    //接受三次握手的套接字
    int conmfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);
    if (conmfd == -1)
    {
        perror("accept error");
        return -1;
    }
    //接受数据
    char buf[1024];

    while (1)
    {
        memset(buf, 0, sizeof(buf));
        ssize_t cnt = recv(conmfd, buf, sizeof(buf), 0);
        if (cnt == -1)
        {
            perror("recv error");
            return -1;
        }
        else if (0 == cnt)
        {
            break;
        }
        
        printf("%s\n", buf);
   
    }
    
    
    return 0;
}

9.HTTP协议

WWW (万维网):万维网

1) url:资源定位符

万维网服务器后台如何标记万维网数据 url:资源定位符

url:<协议>://<主机>:<端口>/<路径> 端口号可以省略

2)HTTP:超文本协议

万维网客户端与万维网服务器之间使用什么方式通信HTTP:超文本协议

HTTP:超文本传输协议

端口:80

备用端口:8080

基于传输层TCP协议

(1)HTTP通信过程

(2)HTTP的报文格式

HTTP请求报文:

GET / HTTP/1.1\r\n

Host: news.sohu.com\r\n

User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/113.0\r\n

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8\r\n

Accept-Language: en-US,en;q=0.5\r\n

Connection: keep-alive\r\n

\r\n

Connection: keep-alive ---->长连接:连接保持一定时间

Connection: close ------>短连接:连接立马断开

HTTP响应报文:

HTTP/1.1 200 OK\r\n

Date: Mon, 25 Aug 2025 06:14:56 GMT\r\n

Content-Type: text/html;charset=utf-8\r\n

Server: openresty\r\n

Vary: Accept-Encoding\r\n

Vary: Origin

Vary: Access-Control-Request-Method

Vary: Access-Control-Request-Headers

Trace-Id: 15e6e7862abd49fdb1c327a6dbeb200d.10490.17561024969448219

Data-Source:

X-Content-Type-Options: nosniff

X-XSS-Protection: 0

S-REQ-ID: 17348448226369344247

S-REQ-TYPE: 0

X-Cache-Lookup: Cache Miss

Content-Encoding: gzip

Cache-Control: no-cache\r\n

Transfer-Encoding: chunked\r\n

X-NWS-LOG-UUID: 17348448226369344247\r\n

Connection: keep-alive\r\n

X-Cache-Lookup: Cache Miss\r\n

\r\n

3)HTML:超文本标记语言

万维网客服端如何展示请求的数据:HTML:超文本标记语言

相关推荐
Pure03194 小时前
OSI七层模型与tcp/ip四层模型
服务器·网络·tcp/ip
qinyia4 小时前
Wisdom SSH 是一款集成了强大 AI 助手功能的 SSH 工具,助你高效管理服务器。
服务器·人工智能·ssh
王火火(DDoS CC防护)4 小时前
服务器IP暴露被攻击了怎么办?
服务器·网络安全·ddos攻击
aiden:)4 小时前
App UI 自动化环境搭建指南
运维·python·ui·appium·自动化
耐达讯通信技术4 小时前
耐达讯自动化RS485与Profinet双向奔赴,伺服驱动器连接“稳稳拿捏”
运维·人工智能·物联网·网络协议·自动化·信息与通信
Kira Skyler4 小时前
抓虫:sw架构防火墙服务启动失败 Unable to initialize Netlink socket: 不支持的协议
java·linux
一张假钞4 小时前
Windows 11主机Ubuntu 24.04虚机共享目录权限问题
linux·运维·ubuntu
耐达讯通信技术4 小时前
嘎嘎厉害!耐达讯自动化RS485转Profinet网关就是食品温控的“天选之子”
运维·服务器·网络·人工智能·网络协议·自动化·信息与通信
GoodG_study4 小时前
一文教您解决win11运行Ubuntu,wsl相关命令出现系统找不到指定文件的错误提示
linux·ubuntu·wsl