Linux网络编程1——socket通信
一.网络准备
1.套接字
在TCP/IP 协议中,“ip 地址+TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识,那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因此可以用 socket 来描述网络连接的一对一关系。

简单来说,用数学里面的坐标系类比一下,(IP地址,端口号)表示一个进程即一个套接字,两个套接字连线表示通信。
- 一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)。
- 在通信过程中, 套接字一定是成对出现的。
2.网络字节序列
计算机(主机)对于多字节序列采用的是小端法存储,而网络通信的多字节序列是大端法存储的,因此在进行网络通信时,主机把数据送到客户端的发送缓冲区前需要把数据进行网络字节转换,同样的接受主机在服务器的接收端取出数据后,又要把数据转化成主机字节序列。
#include <arpa/inet.h>uint32_t htonl(uint32_t hostlong); //本地---》网络(IP地址)
uint16_t htons(uint16_t hostshort); //本地---》网络(端口)
uint32_t ntohl(uint32_t netlong); //网络---》本地(IP地址)
uint16_t ntohs(uint16_t netshort); //网络---》本地(端口)
解释:
h表示本地主机(host)to表示变换n表示网络(net)l表示IP地址(IP地址用32位表示)s表示端口
注意:我们平时用点分十进制表示IP地址,所以要想进行网络字节转换,先要使用
atoi把string转化成int
3.IP地址转换函数
为了直接从点分十进制进行转化成网络字节序,所以有了IP地址转换函数。
#include <arpa/inet.h>int inet_pton(int af, const char *src, void *dst); //IP地址---》网络
解释:
af:表示版本协议号,只能取AF_INET表示IPv4或AF_INET6表示IPv6src:传入的IP地址(点分十进制,string类型)dst:传出的转换后的IP地址(网络字节序)- 返回值:1表示成功;0表示异常,说明传入的src不是一个IP地址;-1表示失败
#include <arpa/inet.h>const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); //网络---》IP地址
解释:
src:传入的转换后的IP地址(网络字节序)dst:传出的IP地址(点分十进制,string类型)size:dst缓冲区的大小- 返回值:成功返回dst,失败返回NULL
4.sockaddr数据结构
直接说结论:后面的bind()函数的参数要用到strcut sockaddr这种结构体的指针,但是现在IPv4使用的结构体普遍是strcut sockaddr_in,所以现在的使用方式是我们一般先定义strcut sockaddr_in的结构体,使用的时候进行强转。比如:
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
其余不要管,重点看bind()的第二个参数。
所以,现在我们重点学习一下strcut sockaddr_in结构体:
struct sockaddr_in {__kernel_sa_family_t sin_family; //地址结构类型__be16 sin_port; //端口号(网络字节序)struct in_addr sin_addr; //IP地址(网络字节序)
};struct in_addr { __be32 s_addr; //IP地址(网络字节序)
};
注意:这里的sockaddr_in的第三个参数是一个结构体的形式,所以赋值时需要注意,例如:
struct sockaddr_in addr;
addr.sin_family = AF_INET/AF_INET6;
addr.sin_port = htons(9527);
int dst;
inet_pton(AF_INET,"192.157.22.45",(void *)&dst);
addr.sin_addr.s_addr = dst;
这里使用强转把dst变成void *类型,赋值是不要忘了.s_addr进入对应结构体内部赋值。
一般的时候,我们用的是本地主机写代码,所以可以将:
int dst; inet_pton(AF_INET,"192.157.22.45",(void *)&dst); addr.sin_addr.s_addr = dst;写成
addr.sin_addr.s_addr = htonl(INADDR_ANY);这里的
INADDR_ANY是一种宏,表示取出系统中有效的任意IP地址,但是是二进制类型。
二.网络套接字函数
1.socket模型

socket():创建套接字bind():绑定服务器的IP和端口listen():设置同时监听上限accept():阻塞监听客户端连接connect():客户端绑定IP和端口,进行连接
注意,在一个模型中其实有三个socket套接字,其中服务器刚开始的套接字在accept()函数里作为参数传入,返回另一个套接字来与客户端进行真正的连接。
2.socket函数
函数原型:
#include <sys/socket.h>int socket(int domain, int type, int protocol);
domain:用来指定传输协议,AF_INET、AF_INET6、AF_UNIX(表示本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用)type:用来指定协议类型,可以取SOCK_STREAM表示流式协议或SOCK_DGRAM表示报式协议protocol:传0表示默认协议
type为
SOCK_STREAM且protocol=0表示使用TCP传输,type为SOCK_DGRAM且protocol=0表示使用UDP传输。
3.bind函数
作用:给socket绑定一个地址结构(IP地址+端口)
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket文件描述符,一般取上面socket()函数的返回值*addr:上面介绍过,用来指定地址结构信息addrlen:地址结构大小,总是取sizeof(addr)
4.listen函数
作用:设置同时与服务器建立连接的客户端数量
#include <sys/socket.h>int listen(int sockfd, int backlog);
sockfd:socket文件描述符,一般取上面socket()函数的返回值backlog:连接上限,最大为128
5.accept函数
作用:阻塞等待客户端连接,成功的话,返回一个成功与客户端连接的socket文件描述符
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf: socket文件描述符,一般取上面socket()函数的返回值,相当于把开始的socket传入addr:传出参数,返回成功链接客户端地址信息,含IP地址和端口号addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小- 返回值: 成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置
errno
6.connect函数
作用:使用现有的socket与服务器建立连接
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:socket文件描述符,一般取上面socket()函数的返回值*addr:服务器的地址结构,用来连接addrlen:地址结构大小,总是取sizeof(addr)
7.read函数
read函数用于从socket中读取数据。它的原型如下:
ssize_t read(int fd, void *buffer, size_t count);
fd:是socket的文件描述符,用于标识一个打开的socket。buffer:是一个指针,指向一个缓冲区,该缓冲区用于存储从socket读取的数据。count:指定了buffer的大小,即希望读取的最大字节数。
read函数返回实际读取的字节数。如果read返回0,表示连接已经关闭。如果返回-1,表示发生了错误,此时可以通过errno变量查看错误类型。
8.write函数
write函数用于向socket写入数据。它的原型如下:
ssize_t write(int fd, const void *buffer, size_t count);
fd:同样是socket的文件描述符。buffer:是一个指针,指向包含要发送数据的缓冲区。count:指定了要发送的字节数。write函数返回实际写入的字节数。如果返回-1,表示发生了错误,同样可以通过errno变量查看错误类型。
8.文件描述符
fd_set 是一个数据类型,用于在 select 系统调用中表示一组文件描述符。以下是与 fd_set 相关的一些常用宏函数,它们用于操作 fd_set:
- FD_ZERO:
作用:将 fd_set 清零,即初始化 fd_set,使其不包含任何文件描述符。
使用方法:FD_ZERO(&fdset); - FD_SET:
作用:将一个文件描述符添加到 fd_set 中。
使用方法:FD_SET(fd, &fdset); 其中 fd 是要添加的文件描述符,fdset 是 fd_set 的实例。 - FD_CLR:
作用:从 fd_set 中移除一个文件描述符。
使用方法:FD_CLR(fd, &fdset); 其中 fd 是要移除的文件描述符,fdset 是 fd_set 的实例。 - FD_ISSET:
作用:测试一个文件描述符是否在 fd_set 中。
使用方法:FD_ISSET(fd, &fdset); 如果 fd 在 fdset 中,则返回非零值;否则返回0。
三.实现一个简单的通信
1.通信流程分析
TCP通信流程分析:
server:
1. socket() 创建socket
2. bind() 绑定服务器地址结构
3. listen() 设置监听上限
4. accept() 阻塞监听客户端连接
5. read(fd) 读socket获取客户端数据
6. 小--大写 toupper()
7. write(fd)
8. close(); client:
1. socket() 创建socket
2. connect(); 与服务器建立连接
3. write() 写数据到 socket
4. read() 读转换后的数据。
5. 显示读取结果
6. close()
2.实现服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>#define ser_port 9527
int main()
{//1.socket partint sfd=0,cfd=0;sfd = socket(AF_INET, SOCK_STREAM, 0);if(sfd==-1){printf("socket error\n");}//2.bind partstruct sockaddr_in ser_addr,cet_addr;ser_addr.sin_family=AF_INET;ser_addr.sin_port=htons(ser_port);ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind(sfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));//3.listen partlisten(sfd,20);//4.accept partchar client_ip[BUFSIZ];socklen_t cet_addr_len = sizeof(cet_addr);cfd = accept(sfd,(struct sockaddr *)&cet_addr,&cet_addr_len);if(cfd==-1){printf("accept socket error\n");}//5.read partchar buf[BUFSIZ];while(true){int ret = read(cfd,buf,sizeof(buf));write(STDOUT_FILENO,buf,ret);for(int i=0;i<ret;i++){buf[i]=toupper(buf[i]);}write(cfd,buf,ret);}//6.close partclose(sfd);close(cfd);return 0;
}
3.实现客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define ser_port 9527
int main() {//1.socket partint hfd=0;hfd=socket(AF_INET, SOCK_STREAM, 0);if(hfd==-1){printf("socket error\n");}//2.connect partstruct sockaddr_in cil_addr;cil_addr.sin_family=AF_INET;cil_addr.sin_port=htons(ser_port);inet_pton(AF_INET,"192.168.242.128",&cil_addr.sin_addr.s_addr);connect(hfd,(struct sockaddr *)&cil_addr,sizeof(cil_addr));// 3. Write and Read partchar buf[BUFSIZ];int count = 10;while (count-- > 0) {if (write(hfd, "hello", 5) == -1) {perror("write");close(hfd);exit(EXIT_FAILURE);}int ret = read(hfd, buf, sizeof(buf));if (ret == -1) {perror("read");close(hfd);exit(EXIT_FAILURE);} else if (ret == 0) {printf("Server disconnected\n");break;}write(STDOUT_FILENO, buf, ret);}close(hfd);return 0;
}
四.出错封装函数思想
1.封装思想
上面简单的写了一个实现大小写转换的通信服务器和客户端,但是并不完善,里面的很多函数调用都没有做错误处理,在实际编写过程中并不规范。
我们以accept()函数为例,在上面的例子中,我们是这样写的:
char client_ip[BUFSIZ];socklen_t cet_addr_len = sizeof(cet_addr);cfd = accept(sfd,(struct sockaddr *)&cet_addr,&cet_addr_len);if(cfd==-1){printf("accept socket error\n");}
但是实际过程中的main.cpp代码量其实是很少的,我们可以把accept()函数进行封装成类似自定义函数,存储在另一个源文件中,这样就可以在main.cpp调用自定义的Accept()函数啦。
一般这种错误函数的封装,我们把重新自定义的函数名取为原函数名基础上首字母大写,然后在自定义函数内部实现错误封装,所以上述代码可以改成:
int Accept(int fd, struct sockaddr *sa, socklen_t *salenptr)
{int n;again:if ( (n = accept(fd, sa, salenptr)) < 0) {if ((errno == ECONNABORTED) || (errno == EINTR))goto again;elseperr_exit("accept error");}return n;
}
注意:自定义函数封装在wrcp.cpp文件中,不要忘了对应的wrcp.h头文件。
2.读写函数
我们在C语言文件操作时,学习了一些读写函数,但是强调过只有read()和write()是满足系统调用的(即在socket通信过程中使用),所以平时的读写我们要自定义一些读(写)n字节函数或读(写)n行函数。下面代码仅供参考:
ssize_t Readn(int fd, void *vptr, size_t n)
{size_t nleft;ssize_t nread;char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nread = read(fd, ptr, nleft)) < 0) {if (errno == EINTR)nread = 0;elsereturn -1;} else if (nread == 0)break;nleft -= nread;ptr += nread;}return n - nleft;
}ssize_t Writen(int fd, const void *vptr, size_t n)
{size_t nleft;ssize_t nwritten;const char *ptr;ptr = vptr;nleft = n;while (nleft > 0) {if ( (nwritten = write(fd, ptr, nleft)) <= 0) {if (nwritten < 0 && errno == EINTR)nwritten = 0;elsereturn -1;}nleft -= nwritten;ptr += nwritten;}return n;
}static ssize_t my_read(int fd, char *ptr)
{static int read_cnt;static char *read_ptr;static char read_buf[100];if (read_cnt <= 0) {
again:if ((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0) {if (errno == EINTR)goto again;return -1; } else if (read_cnt == 0)return 0;read_ptr = read_buf;}read_cnt--;*ptr = *read_ptr++;return 1;
}ssize_t Readline(int fd, void *vptr, size_t maxlen)
{ssize_t n, rc;char c, *ptr;ptr = vptr;for (n = 1; n < maxlen; n++) {if ( (rc = my_read(fd, &c)) == 1) {*ptr++ = c;if (c == '\n')break;} else if (rc == 0) {*ptr = 0;return n - 1;} elsereturn -1;}*ptr = 0;return n;
}
相关文章:
Linux网络编程1——socket通信
一.网络准备 1.套接字 在TCP/IP 协议中,“ip 地址TCP 或UDP 端口号”唯一标识网络通讯中的一个进程。“IP 地址端口号”就对应一个socket。欲建立连接的两个进程各自有一个 socket 来标识,那么这两个 socket 组成的 socket pair 就唯一标识一个连接。因…...
【每日一题】LeetCode 1052.爱生气的书店老板(数组、滑动窗口)
【每日一题】LeetCode 1052.爱生气的书店老板(数组、滑动窗口) 题目描述 书店老板的商店每天有不同数量的顾客进入。每分钟,老板可能或可能不会生气。如果老板生气,那一分钟的顾客就会不满意。老板知道一个秘密技巧,…...
IDEA中无法使用 Subversion 命令行客户端 svn Subversion 可执行文件的路径可能是错误的
IDEA中无法使用 Subversion 命令行客户端 svn 我在新电脑上安装好IDEA和SVN后使用IDEA拉取和提交项目时提示无法使用。 解决方案 我这边的问题是在安装TortoiseSVN的时候少启用了一个功能,需要重新安装并把这个功能启用。 在这一步需要把command line client to…...
ThreadLocal 在线程池中的内存泄漏问题
ThreadLocal 是一种非常方便的工具,它为每个线程创建独立的变量副本,避免了线程之间的共享数据问题。然而,在线程池环境中,ThreadLocal 的使用必须非常谨慎,否则可能会引发内存泄漏问题。 为什么 ThreadLocal 可能导致…...
如何编写Prompt,利用AI高效生成图表——图表狐(FoxChart)指南
在数据可视化领域,图表是数据的重要表达方式。为了让更多人能够轻松高校地生成美观、专业的图表,图表狐(FoxChart)应用而生。然而,要想充分发挥AI的潜力,编写合适的Prompt至关重要。本文介绍一些编写Prompt的原则,帮助…...
Redis主从数据同步过程:命令传播、部分重同步、复制偏移量等
请记住胡广一句话,所有的中间件所有的框架都是建立在基础之上,数据结构,计算机网络,计算机原理大伙一定得看透!!~ 1. Redis数据同步 1.1 数据同步过程 大家有没想过为什么Redis多机要进行数据同步&#…...
《JavaEE进阶》----13.<Spring Boot【配置文件】>
本篇博客讲解 1.SpringBoot配置文件的格式以及对应的语法 2.了解两个配置文件格式的差异、优缺点。 我们这里只做简单的介绍。看会,了解,学会读取就行了。 因为配置文件实在太多了,这里只做基础的介绍。 一、配置文件的作用 前言 计算机中有许…...
【练习8】
链接:https://www.nowcoder.com/questionTerminal/e671c6a913d448318a49be87850adbcc 分析: 创建一个二维数组来实现杨辉三角,因为当前元素的值是上一行的当前列与前一列的和,所以创建数组的时候要实现n1,相当于罩子一…...
vivado 时间汇总报告
步骤7:时间汇总报告 定时路径在时钟元素处开始和结束。输入和输出端口不是顺序的 元素,默认情况下,Vivado时序分析不会对进出I/O端口的路径进行计时 设计,除非指定了输入/输出延迟约束。 在此步骤中,您将在Vivado中生成…...
【软考】设计模式之代理模式
目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.代理模式(Proxy Pattern)。2.意图:为其他对象提供一种代理以控制对这个对象的访问。3.通过提供与对象相同的接口来控制对这个对象的访问。4.是设计模…...
3.创建型设计模式详解:生成器模式与原型模式的深度解析
设计模式(Design Patterns)是软件开发中常用的解决方案,帮助开发者处理常见的设计问题。创建型设计模式专注于对象的实例化,旨在提高系统的灵活性和可维护性。在这篇文章中,我们将深入探讨创建型设计模式中的生成器模式…...
goframe结构体标签和命令行标签
元数据gmeta 基础标签 更多了解:https://swagger.io/specification/ g.Meta path:"/profile" method:"get" summary:"展示个人资料页面" tags:"个人" g.Meta mime:"text/html" type:"string" example…...
pytest压力测试:不断发送数据,直到发现数据丢失
示例场景 假设有一个 send_data 函数接受数据并返回成功或失败的状态。 创建一个测试用例,通过逐步增加数据量来测试这个函数,直到返回失败为止。 步骤 定义压力测试函数 定义一个函数。不断发送数据,直到发现数据丢失。 创建 pytest 测试…...
自选择问题和处理效应模型
自选择问题和处理效应模型 DGP 注意: 这里的概率密度超过了1,这是正常的。概率密度的三原则,1是大于等于0;2是积分等于1;对于连续型随机变量,给定一个具体的x值,f(x)并不是该事件发生的概率。而…...
[数据集][目标检测]水面垃圾检测数据集VOC+YOLO格式2027张1类别
数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):2027 标注数量(xml文件个数):2027 标注数量(txt文件个数):2027 标注…...
OpenCV 之 模版匹配多个对象、图片旋转 综合应用
引言 在图像处理和计算机视觉中,模板匹配是一种常用的技术,用于在一幅较大的图像中查找与给定模板图像相似的部分。然而,在实际应用中,目标物体可能会出现在不同的角度,这就需要我们在匹配之前对模板进行旋转处理。本…...
ZooKeeper 中的 Curator 框架解析
Apache ZooKeeper 是一个为分布式应用提供一致性服务的软件。它提供了诸如配置管理、分布式同步、组服务等功能。在使用 ZooKeeper 时,Curator 是一个非常流行的客户端库,它简化了 ZooKeeper 的使用,提供了高级的抽象和丰富的工具。本文将详细…...
机械学习—零基础学习日志(Python做数据分析02)
现在开始使用Python尝试做数据分析。具体参考的网址链接放在了文章末尾。 引言 我通过学习《利用Python进行数据分析》这本书来尝试使用Python做数据分析。书里让下载,anaconda,使用Jupyter来写代码,只是下载一个anaconda的确有点费时间&am…...
BRAM IP Native模式使用
简介 BRAM(Block RAM)是FPGA(Field-Programmable Gate Array)中的一种专用RAM资源,固定分布在FPGA内部的特定位置。该内容主要对BRAM(Block RAM”的缩写)Native模式下IP界面做详细描述和使用…...
react的useRef用什么作用
useRef 是 React 提供的一个钩子,用于在函数组件中创建和管理对 DOM 元素或组件实例的引用。它返回一个包含 current 属性的对象,可以用来存储对某个值的引用,而这个引用在组件的整个生命周期内保持不变。 useRef 的主要用途 1.访问 DOM 元素…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
线程同步:确保多线程程序的安全与高效!
全文目录: 开篇语前序前言第一部分:线程同步的概念与问题1.1 线程同步的概念1.2 线程同步的问题1.3 线程同步的解决方案 第二部分:synchronized关键字的使用2.1 使用 synchronized修饰方法2.2 使用 synchronized修饰代码块 第三部分ÿ…...
论文浅尝 | 基于判别指令微调生成式大语言模型的知识图谱补全方法(ISWC2024)
笔记整理:刘治强,浙江大学硕士生,研究方向为知识图谱表示学习,大语言模型 论文链接:http://arxiv.org/abs/2407.16127 发表会议:ISWC 2024 1. 动机 传统的知识图谱补全(KGC)模型通过…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
2025盘古石杯决赛【手机取证】
前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来,实在找不到,希望有大佬教一下我。 还有就会议时间,我感觉不是图片时间,因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
多种风格导航菜单 HTML 实现(附源码)
下面我将为您展示 6 种不同风格的导航菜单实现,每种都包含完整 HTML、CSS 和 JavaScript 代码。 1. 简约水平导航栏 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport&qu…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...
