Linux 网络通信

news2025/4/8 5:02:39

(一)套接字Socket概念

Socket 中文意思是“插座”,在 Linux 环境下,用于表示进程 x 间网络通信的特殊文件
类型
。本质为内核借助缓冲区形成的伪文件。

既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。Linux 系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致

在 TCP/IP 协议中,“IP 地址+TCPUDP 端口号”唯一标识网络通讯中的一个进程。

“IP 地址+端口号”就对应一个 socket。 欲建立连接的两个进程各自有一个 socket 来标识,那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因此可以用 Socket 来描述网络连接的一对一关系。

在这里插入图片描述

在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。

(二)TCP通信流程图

在这里插入图片描述

(三)Socket编程基础知识

1 网络字节序

在计算机世界里,有两种字节序:

大端字节序 - 低地址高字节,高地址低字节
小段字节序 - 低地址低字节,高地址高字节

内存中的多字节数据相对于内存地址有大端和小端之分,磁盘文件中的
多字节数据相对于文件中的偏移地址也有大端小端之分。网络数据流同样有
大端小端之分,那么如何定义网络数据流的地址呢?发送主机通常将发送缓
冲区中的数据按内存地址从低到高的顺序发出,接收主机把从网络上接到的
字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
,因此,
网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高
地址。

TCP/IP 协议规定,网络数据流应采用大端字节序即低地址高字节。

为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后
都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);			
uint16_t htons(uint16_t hostshort);			
uint32_t ntohl(uint32_t netlong);			
uint16_t ntohs(uint16_t netshort);			

h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回,如
果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

2 socketaddr 数据结构

很多网络编程函数诞生早于 IPv4 协议,那时候都使用的是 sockaddr 结构体, 为了向前兼容,现在 sockaddr 退化成了(void *)的作用,传递一个地址给
函数,至于这个函数是 sockaddr_in 还是其他的,由地址族确定,然后函数
内部再强制类型转化为所需的地址类型
在这里插入图片描述

struct sockaddr {
	sa_family_t 	sa_family;			/* 地址系列,AF_xxx */
	char 			sa_data[14]; 		/* 14字节的协议地址 */
};
struct sockaddr_in {
	sa_family_t 	sin_family; 		/* 地址族:一般为AF_INET */
	in_port_t 		sin_port;			/* 按网络字节顺序排列的端口*/
	struct in_addr  sin_addr; 			/* 网络地址 */
};

/* 网络地址 */
struct in_addr {
	uint32_t 		s_addr;				/* 按网络字节顺序排列的网络地址 */
};

IPv4 地址用 sockaddr_in 结构体表示,包括 16 位端口号和 32 位 IP 地址
,但是 sock API 的实现早于 ANSI C 标准化,那时还没有 void *类型,
因此这些像 bind 、accept 函数的参数都用 struct sockaddr * 类型表示,
在传递参数之前要强制类型转换一下,例如:

struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)); 

3 IP地址转换函数

对于IP地址"a.b.c.d"
小端字节序和大端字节序分别表示:
在这里插入图片描述

#include <arpa/inet.h>
int          inet_pton(int af, const char *src, void *dst);
const char * inet_ntop(int af, const void *src, char *dst, socklen_t size);

af 取值可选为 AF_INET 和 AF_INET6 ,即对应IPv4 和 IPv6
其中 inet_pton 和 inet_ntop 不仅可以转换 IPv4 的 in_addr,
还可以转换 IPv6的 in6_addr。
因此函数接口是 void *dst 和 void *src。

(四)Socket 编程主要的函数介绍

1 套接字设置 socket()

头文件:

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

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

参数解释:
domain:

AF_INET 		使用 TCP 或 UDP 来传输,用Pv4 的地址
AF_INET6 		与AF_INET类似,不过是来用 IPv6 的地址
AF_UNIX 		本地协议,使用在 Unix 和 Linux 系统上,一般都是当客户端和服
				务器在同一台及其上的时候使用

type:

SOCK_STREAM 	这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。
				这是一个使用最多的socket 类型,这个socket 是使用TCP来进行传输。
SOCK_DGRAM 		这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,
				使用 UDP 来进行它的连接。
SOCK_SEQPACKET 	该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。
				必须把这个包完整的接受才能进行读取。
SOCK_RAW 		socket类型提供单一的网络访问,这个 socket 类型使用 ICMP 公共协议。
			
SOCK_RDM 		这个类型是很少使用的,在大部分的操作系统上没有实现,
				它是提供给数据链路层使用,不保证数据包的顺序

protocol:

传 0 表示使用默认协议。

返回值:
成功:返回指向新创建的 socket 的文件描述符
失败:返回-1,设置 errno

注意事项:

对于 IPv4,domain 参数指定为 AF_INET。对于 TCP 协议,type 参数指定为 SOCK_STREAM,表示面向流的传输协议。如果是UDP协议,则type参数指定为SOCK_DGRAM,表示面向数据报的传输协议。protocol参数的指定为 0 即可。

2 绑定标签 bind()

头文件:

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

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

参数解释:

sockfd:		socket 文件描述符
addr:			构造出 IP 地址加端口号
addrlen:		sizeof(addr)长度

返回值:
成功: 返回 0
失败: 返回-1, 设置 errno

注意事项:
服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知
服务器程序的地址和端口号后就可以向服务器发起连接,因此服务器需要调用
bind 绑定一个固定的网络地址和端口号

bind()的作用是将参数 sockfd 和 addr 绑定在一起,使 sockfd 这个用于网
络通讯的文件描述符监听 addr 所描述的地址和端口号
。前面讲过,struct
sockaddr *是一个通用指针类型,addr 参数实际上可以接受多种协议的 sockaddr
结构体,而它们的长度各不相同,所以需要第三个参数 addrlen 指定结构体的长
度。如:

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);

首先将整个结构体清零,然后设置地址类型为 AF_INET,网络地址为 INADDR_ANY,
这个宏表示本地的任意 IP 地址,因为服务器可能有多个网卡,每个网卡也可能
绑定多个 IP 地址,
这样设置可以在所有的 IP 地址上监听,直到与某个客户端建
立了连接时才确定下来到底用哪个 IP 地址和端口号。

3 设置监听 listen()

头文件:
#include <sys/types.h>
#include <sys/socket.h>

函数原型

int listen(int sockfd, int backlog);

参数解释:

sockfd:		socket 文件描述符
backlog:	在 Linux 系统中,它是指排队等待建立 3 次握手队列长度

注意事项:
查看系统默认 backlog

cat /proc/sys/net/ipv4/tcp_max_syn_backlog

改变 系统限制的 backlog 大小

vim /etc/sysctl.conf

最后添加

net.core.somaxconn = 1024
net.ipv4.tcp_max_syn_backlog = 1024

保存,然后执行

sysctl -p

4 接受连接 accept()

头文件:

#include <sys/types.h>
#include <sys/socket.h>

函数原型:

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

参数解释:

sockdf:		socket 文件描述符
addr:		传出参数,返回链接客户端地址信息,含 IP 地址和端口号
addrlen: 	sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小

返回值:

成功: 返回一个新的 socket 文件描述符,用于和客户端通信,
失败: 返回-1,设置 errno

注意事项:

三次握手完成后,服务器调用 accept()接受连接,如果服务器调用 accept()
时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。addr 是一个
传出参数,accept()返回时传出客户端的地址和端口号
。addrlen 参数是一个传
入传出参数,传入的是调用者提供的缓冲区 addr的长度以避免缓冲区溢出问题,
传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)。
如果给 addr 参数传 NULL,表示不关心客户端的地址。

服务器程序结构是这样的:

while (1) {
	cliaddr_len = sizeof(cliaddr);
	connfd = accept(listenfd, (struct sockaddr *)&cliaddr,&cliaddr_len);
	n = read(connfd, buf, MAXLINE);
	......
	close(connfd);
}

整个是一个 while 死循环,每次循环处理一个客户端连接。由于 cliaddr_len
是传入传出参数,每次调用 accept()之前应该重新赋初值。
accept()的参数
listenfd 是先前的监听文件描述符,而 accept()的返回值是另外一个文件描述
符 connfd,
之后与客户端之间就通过这个 connfd 通讯,最后关闭 connfd 断开
连接,而不关闭 listenfd,再次回到循环开头 listenfd 仍然用作 accept 的参
数。accept()成功返回一个文件描述符,出错返回-1。

5 请求连接 connect()

头文件:
#include <sys/types.h>
#include <sys/socket.h>

函数原型:

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

参数解释:

sockdf:   	socket 文件描述符
addr:		传入参数,指定服务器端地址信息,含 IP 地址和端口号
addrlen:	传入参数,传入 sizeof(addr)大小

返回值:
返回值: 成功返回 0,失败返回-1,设置 errno

注意事项:

客户端需要调用 connect()连接服务器,connect 和 bind 的参数形式一致,
区别在于bind的参数是自己的地址而connect的参数是对方的地址

(五)应用实例

1 服务端demo代码

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <arpa/inet.h>

#define SERVER_PORT 6666

int main(void) {
	//1创建连接的套接字
	int sock;
	sock = socket(AF_INET, SOCK_STREAM, 0);//IPV4,TCP方式,默认协议

	//2设置标签(地址,端口号)
	struct sockaddr_in server_addr;
	bzero(&server_addr, sizeof(server_addr));			//将server_addr的内容清零

	server_addr.sin_family = AF_INET;					//协议组IPV4
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);	//监听本地所有ip
	server_addr.sin_port = htons(SERVER_PORT);			//设置端口号

	//3绑定标签
	bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));

	//4设置监听并指定监听最大数量
	listen(sock, 128);

	//5等待连接
	printf("等待客户端连接请求\n");
	int done = 1;
	while (done) {
		//5-1有连接则重新分配套接字接受连接
		struct sockaddr_in clinet;
		int  client_sock, len;			//客户端的 套接字
		char client_ip[64];				//客户端的 ip
		char buf[256];					//客户端的 发送的内容

		socklen_t client_addr_len;		//套接字地址结构长度
		client_addr_len = sizeof(clinet);

		//5-2接受分配的套接字
		client_sock = accept(sock, (struct sockaddr*)&clinet, &client_addr_len);

		//5-3查看客户端IP和端口号
		inet_ntop(AF_INET, &clinet.sin_addr.s_addr, client_ip, sizeof(client_ip));
		printf("客户端IP:%s	,端口号:%d\n", client_ip, ntohs(clinet.sin_port));

		//5-4客户端发来的数据
		len = read(client_sock, buf, sizeof(buf) - 1);
		buf[len] = '\0';
		printf("收到客户端发来的长度为%d数据%s\n", len, buf);

		//5-4服务端给客户写数据
		len = write(client_sock, buf, len);
		printf("已完成对客户端的写操作\n");
		close(client_sock);

	}
 	close(sock);
	return 0;
}

2 客户端demo代码

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

#define SERVER_PORT 6666
#define SERVER_IP "127.0.0.1"

int main(int argc,char **argv) {
	int sockfd;
	char* message;
	struct sockaddr_in servaddr;
	int n;
	char buf[64];
	if (argc!=2)//参数不合法
	{
		printf("参数不合法!  ./xxxx.exe  message\n");
		exit(-1);
	}
	
	message = argv[1];//获取要发送的消息
	printf("待发送的数据:%s\n",message);

	//创建套接字
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//将server_addr的内存初始化
	memset(&servaddr,'\0',sizeof(struct sockaddr_in));
	servaddr.sin_family = AF_INET;
	
	inet_pton(AF_INET,SERVER_IP,&servaddr.sin_addr);
	servaddr.sin_port=htons(SERVER_PORT);
	//连接服务器
	connect(sockfd, (struct sockaddr*)&servaddr,sizeof(servaddr));
	//写数据
	write(sockfd,message,strlen(message));
	//读数据
	n = read(sockfd,buf,sizeof(buf)-1);
	if (n>0) {
		buf[n] = '\0';
		printf("收到的数据:%s\n",buf);
	}
	else {
	
		perror("error!!!");
	
	}
	printf("finished\n");
	close(sockfd);
	return 0;
}

3演示效果:

在这里插入图片描述

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

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

相关文章

Royal TSX v6.0.1

Royal TSX是一款基于插件的软件&#xff0c;适用于Windows系统&#xff0c;可以用于远程连接和管理服务器。它支持多种连接类型&#xff0c;如RDP、VNC、基于SSH连接的终端&#xff0c;SFTP/FTP/SCP或基于Web的连接管理。 在安装Royal TSX后&#xff0c;需要进行一些基础配置&…

【新手解答2】深入探索 C 语言:一些常见概念的解析

C语言的相关问题解答 写在最前面问题1变量名是否有可能与变量重名&#xff1f;变量名和变量的关系变量名与变量是否会"重名"举例说明结论 变量则是一个地址不变&#xff0c;值时刻在变的“具体数字”变量的地址和值变量名与数据类型具体化示例结论 问题2关于你给我的…

11.8事务

一.Spring实现事务的两种方式 1.通过代码的方式手动实现事务. 2.通过注解的方式实现声明式事务. 二. 1.mysql事务 2. 手动实现事务 3.注解实现事务 使用注解Transactional,可以写在类上或方法上,如果异常,就自动回滚,正常则自动提交. 注意: 如果在代码中添加了try,catch捕…

408—电子笔记分享

一、笔记下载 链接&#xff1a;https://pan.baidu.com/s/1bFz8IX6EkFMWTfY9ozvVpg?pwddeng 提取码&#xff1a;deng b站视频&#xff1a;408-计算机网络-笔记分享_哔哩哔哩_bilibili 包含了408四门科目&#xff08;数据结构、操作系统、计算机组成原理、计算机网络&#xff09…

灭火器二维码巡检卡制作教程

每个消防器材生成独立二维码&#xff0c;取代传统纸质巡检卡&#xff0c;微信扫码巡检&#xff0c;巡检记录汇总后台&#xff0c;随时登录后台查看导出数据&#xff0c;管理人员绑定凡尔码小程序即可随时了解消防巡检完成情况。 生成灭火器巡检码流程图&#xff1a; 1、开通后…

【智能算法】基于黄金正弦和混沌映射思想的改进减法优化器算法

减法优化器&#xff08;Subtraction-Average-Based Optimizer&#xff0c;SABO&#xff09;是2023年刚出的智能优化算法。目前知网中文期刊基本搜不到&#xff0c;并且可以遇见未来一年文章也很少。SABO算法原理简单&#xff0c;算上初始化粒子&#xff0c;总共不超过6个公式。…

解密 sqli靶场第一关:一步一步学习 SQL 注入技术

目录 一、判断是否存在注入点 二、构造类似?id1 --的语句 三、判断数据表中的列数 四、使用union联合查询 五、使用group_concat()函数 六、爆出数据库中的表名 七、爆出users表中的列名 八、爆出users表中的数据 &#x1f308;嗨&#xff01;我是Filotimo__&#x1f308;。很…

阶梯排列硬币

题意&#xff1a; 你总共有 n 枚硬币&#xff0c;并计划将它们按阶梯状排列。对于一个由 k 行组成的阶梯&#xff0c;其第 i 行必须正好有 i 枚硬币。阶梯的最后一行 可能 是不完整的。 给你一个数字 n &#xff0c;计算并返回可形成 完整阶梯行 的总行数。 示例 1&#xff…

python 笔记 根据用户轨迹+基站位置,估计基站轨迹+RSRP

1 问题描述 已知用户实际的轨迹&#xff0c;和基站的位置&#xff0c;能不能得到用户所连接的基站&#xff0c;以及基站的信号强度RSRP&#xff1f; 1.1 几个假设 这里我们做几个假设&#xff1a; 每个用户有80%的概率连接最近的基站&#xff0c;有20%的概率选择其他的基站连…

【SAS Planet 下载地图瓦片】

SAS Planet是一位俄罗斯爱好者创建的的开源应用&#xff0c;该应用可以浏览与下载主流网络地图&#xff0c;包括Google地图、Google地球、Bing地图、Esri 地图、Yandex地图等&#xff0c;支持100多图源。 安装包下载地址&#xff1a;https://www.sasgis.org/download/ github…

用队列和栈分别实现栈和队列

用队列实现栈 题目解读 本题的要求是要用两个队列来实现一个先进后出的栈&#xff0c;并且要有以下功能&#xff1a; 1.将元素压入栈中 2.移除栈顶元素并且返回他 3.返回栈顶元素 4.判断栈是否为空 题目构思和代码实现 我们首先要做的就是将实现队列的代码导入该题&#xff…

【深度学习】如何找到最优学习率

经过了大量炼丹的同学都知道&#xff0c;超参数是一个非常玄乎的东西&#xff0c;比如batch size&#xff0c;学习率等&#xff0c;这些东西的设定并没有什么规律和原因&#xff0c;论文中设定的超参数一般都是靠经验决定的。但是超参数往往又特别重要&#xff0c;比如学习率&a…

扩散模型实战(十二):使用调度器DDIM反转来优化图像编辑

推荐阅读列表&#xff1a; 扩散模型实战&#xff08;一&#xff09;&#xff1a;基本原理介绍 扩散模型实战&#xff08;二&#xff09;&#xff1a;扩散模型的发展 扩散模型实战&#xff08;三&#xff09;&#xff1a;扩散模型的应用 扩散模型实战&#xff08;四&#xff…

python之pyqt专栏4-代码控制部件

通过前面的学习&#xff0c;我们已经回创建新的pyqt项目、对项目结构有了了解、也了解Qt Designer设计UI界面并 把"xx.ui"转换为“xxx.py”。 pyqt模块与类 pyqt6 由模块组成&#xff0c;而模块里面又有很多的类 在pyqt官网Modules — PyQt Documentation v6.6.0页面…

函数的防抖与节流

一、函数防抖 &#xff08;一&#xff09;防抖的理解 防抖就是将所有的触发都取消&#xff0c;在规定的时间结束过后才会执行最后一次&#xff0c;也就是说连续快速的触发只会执行最后一次结果。 也可以理解为游戏里的回城按钮&#xff0c;每点一下就会重新刷新回城进度&…

SSM 框架整合

1 整合配置 1.1 流程 1.2 Spring 整合 MyBatis 1.3 Spring 整合 SpringMVC 1.4 配置代码 JdbcConfig.java public class JdbcConfig {Value("${jdbc.driver}")private String driver;Value("${jdbc.url}")private String url;Value("${jdbc.usern…

【挑战业余一周拿证】CSDN官方课程目录

一、亚马逊云科技简介 二、在云中计算 三、全球基础设施和可靠性 四、联网 五、存储和数据库 六、安全性 七、监控和分析 八、定价和支持 九、迁移和创新 十、云之旅 关注订阅号 CSDN 官方中文视频&#xff08;免费&#xff09;&#xff1a;点击进入 一、亚马逊云科…

Apache POI(处理Miscrosoft Office各种文件格式)

文章目录 一、Apache POI介绍二、应用场景三、使用步骤1.导入maven坐标2.写入代码讲解3.读取代码讲解 总结 一、Apache POI介绍 Apache POI 是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是&#xff0c;我们可以使用 POI 在 Java 程序中对Miscrosoft Office…

N7 LUP.2.3 DRC如何解决?

这个问题在Design Rule中的介绍如下图&#xff1a; 解决办法是od 15 um的范围要加LUP_GR* cell&#xff0c;需要提高密度(加的位置需要符合tcic)去fix。

ubuntu 安装 jetbrains-toolbox

ubuntu 安装 jetbrains-toolbox 官网下载 jetbrains-toolbox jetbrains 官网 jetbrains 官网&#xff1a;https://www.jetbrains.com/ jetbrains-toolbox 官网下载页面 在下载页面点击 Download 安装 jetbrains-toolbox 解压 jetbrains-toolbox 安装包 到指定目录 本案例将…